├── .editorconfig ├── .env.example ├── .eslintrc.js ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .prettierrc ├── CHANGELOG.md ├── DEVELOPMENT.md ├── LICENSE ├── README.md ├── babel.config.json ├── bin └── deploy-js-bundle-to-filerobot.js ├── index.html ├── jsconfig.json ├── lerna.json ├── package.json ├── packages ├── filerobot-image-editor │ ├── LICENSE │ ├── README.md │ ├── package.json │ └── src │ │ ├── index.d.ts │ │ └── index.js └── react-filerobot-image-editor │ ├── LICENSE │ ├── README.md │ ├── package.json │ └── src │ ├── actions │ ├── addFilter.js │ ├── changePointerIcon.js │ ├── changeRotation.js │ ├── clearAnnotationsSelections.js │ ├── duplicateAnnotations.js │ ├── enableTextContentEdit.js │ ├── hideLoader.js │ ├── index.js │ ├── redo.js │ ├── removeAnnotations.js │ ├── reset.js │ ├── selectAnnotation.js │ ├── selectTab.js │ ├── selectTool.js │ ├── setAnnotation.js │ ├── setCanvasSize.js │ ├── setCrop.js │ ├── setFeedback.js │ ├── setFinetune.js │ ├── setLatestColor.js │ ├── setOriginalImage.js │ ├── setResize.js │ ├── setSaved.js │ ├── setSaving.js │ ├── setShowTabsMenu.js │ ├── setShownImageDimensions.js │ ├── showLoader.js │ ├── toggleFlip.js │ ├── toggleOriginalImageDisplay.js │ ├── undo.js │ ├── updateState.js │ └── zoomCanvas.js │ ├── components │ ├── App │ │ ├── App.styled.js │ │ └── index.jsx │ ├── AssemblyPoint │ │ ├── globalStyles.js │ │ └── index.jsx │ ├── FeedbackPopup │ │ └── index.jsx │ ├── Layers │ │ ├── DesignLayer │ │ │ ├── AnnotationNodes │ │ │ │ ├── AnnotationNodes.constants.js │ │ │ │ ├── ArrowNode.jsx │ │ │ │ ├── EllipseNode.jsx │ │ │ │ ├── ImageNode.jsx │ │ │ │ ├── LineNode.jsx │ │ │ │ ├── MemoizedAnnotation.jsx │ │ │ │ ├── PolygonNode.jsx │ │ │ │ ├── RectNode.jsx │ │ │ │ ├── TextNode.jsx │ │ │ │ └── index.jsx │ │ │ ├── PreviewGroup.jsx │ │ │ ├── index.jsx │ │ │ └── nodesCommonPropTypes.js │ │ ├── TransformersLayer │ │ │ ├── CropTransformer.jsx │ │ │ ├── NodesTransformer.jsx │ │ │ ├── TransformersLayer.utils.js │ │ │ └── index.jsx │ │ └── index.js │ ├── MainCanvas │ │ ├── CanvasNode.jsx │ │ ├── MainCanvas.styled.js │ │ ├── index.jsx │ │ └── touchZoomingEvents.js │ ├── NodeControls │ │ ├── NodeControls.styled.js │ │ └── index.jsx │ ├── Tabs │ │ ├── TabItem.jsx │ │ ├── Tabs.constants.js │ │ ├── Tabs.styled.js │ │ └── index.jsx │ ├── TabsDrawer │ │ └── index.jsx │ ├── ToolsBar │ │ ├── ToolsBar.styled.js │ │ ├── ToolsBarItemButton.jsx │ │ ├── ToolsBarItemOptionsWrapper.jsx │ │ └── index.jsx │ ├── Topbar │ │ ├── BackButton.jsx │ │ ├── CanvasZooming.jsx │ │ ├── CloseButton.jsx │ │ ├── ConfirmationModal.jsx │ │ ├── ImageDimensionsAndDisplayToggle.jsx │ │ ├── RedoButton.jsx │ │ ├── ResetButton.jsx │ │ ├── SaveButton.jsx │ │ ├── Topbar.constants.js │ │ ├── Topbar.styled.js │ │ ├── UndoButton.jsx │ │ └── index.jsx │ ├── common │ │ ├── AnnotationOptions │ │ │ ├── AnnotationOptions.constants.js │ │ │ ├── AnnotationOptions.styled.js │ │ │ ├── OpacityField.jsx │ │ │ ├── PositionFields.jsx │ │ │ ├── ShadowFields.jsx │ │ │ ├── StrokeFields.jsx │ │ │ └── index.jsx │ │ ├── ButtonWithMenu │ │ │ ├── ButtonWithMenu.styled.js │ │ │ └── index.jsx │ │ ├── Carousel │ │ │ ├── Carousel.styled.js │ │ │ └── index.jsx │ │ ├── ColorInput │ │ │ ├── ColorInput.styled.js │ │ │ └── index.jsx │ │ ├── ColorPickerModal │ │ │ ├── ColorPickerModal.styled.js │ │ │ └── index.jsx │ │ ├── HiddenUploadInput │ │ │ ├── HiddenUploadInput.styled.js │ │ │ └── index.jsx │ │ ├── Modal │ │ │ ├── Modal.styled.js │ │ │ └── index.jsx │ │ ├── Separator │ │ │ ├── Separator.styled.js │ │ │ └── index.jsx │ │ ├── Slider │ │ │ ├── Slider.styled.js │ │ │ └── index.jsx │ │ └── Spinner │ │ │ ├── Spinner.styled.js │ │ │ └── index.jsx │ └── tools │ │ ├── Arrow │ │ ├── ArrowButton.jsx │ │ ├── ArrowOptions.jsx │ │ └── index.js │ │ ├── Blur │ │ ├── Blur.jsx │ │ ├── BlurOptions.jsx │ │ └── index.js │ │ ├── Brightness │ │ ├── Brightness.jsx │ │ ├── BrightnessOptions.jsx │ │ └── index.js │ │ ├── Contrast │ │ ├── Contrast.jsx │ │ ├── ContrastOptions.jsx │ │ └── index.js │ │ ├── Crop │ │ ├── Crop.constants.js │ │ ├── Crop.jsx │ │ ├── Crop.styled.js │ │ ├── CropPresetGroup.jsx │ │ ├── CropPresetGroupsFolder.jsx │ │ ├── CropPresetItem.jsx │ │ ├── CropPresetsOption.jsx │ │ └── index.js │ │ ├── Ellipse │ │ ├── EllipseButton.jsx │ │ ├── EllipseOptions.jsx │ │ └── index.js │ │ ├── Filters │ │ ├── FilterItem.jsx │ │ ├── Filters.constants.js │ │ ├── Filters.jsx │ │ ├── Filters.styled.js │ │ └── index.js │ │ ├── Flip │ │ ├── FlipX.jsx │ │ ├── FlipY.jsx │ │ └── index.js │ │ ├── HSV │ │ ├── HSV.jsx │ │ ├── HSVOptions.jsx │ │ └── index.js │ │ ├── Image │ │ ├── Image.styled.js │ │ ├── ImageButton.jsx │ │ ├── ImageControls.jsx │ │ ├── ImageOptions.jsx │ │ ├── ImagesGallery.jsx │ │ └── index.js │ │ ├── Line │ │ ├── LineButton.jsx │ │ ├── LineOptions.jsx │ │ └── index.js │ │ ├── Pen │ │ ├── PenButton.jsx │ │ ├── PenOptions.jsx │ │ └── index.js │ │ ├── Polygon │ │ ├── Polygon.constants.js │ │ ├── PolygonButton.jsx │ │ ├── PolygonOptions.jsx │ │ ├── PolygonSidesField.jsx │ │ └── index.js │ │ ├── Rect │ │ ├── Rect.constants.js │ │ ├── RectButton.jsx │ │ ├── RectCornerField.jsx │ │ ├── RectOptions.jsx │ │ └── index.js │ │ ├── Resize │ │ ├── Resize.jsx │ │ ├── Resize.styled.js │ │ └── index.js │ │ ├── Rotate │ │ ├── Rotate.styled.js │ │ ├── RotateButton.jsx │ │ ├── RotateOptions.jsx │ │ └── index.js │ │ ├── Text │ │ ├── TextButton.jsx │ │ ├── TextOptions │ │ │ ├── TextAlignmentFields.jsx │ │ │ ├── TextControls.jsx │ │ │ ├── TextOptions.constants.js │ │ │ ├── TextOptions.styled.js │ │ │ ├── TextSpacingsFields.jsx │ │ │ ├── handleTextChangeArea.js │ │ │ └── index.jsx │ │ └── index.js │ │ ├── Warmth │ │ ├── Warmth.jsx │ │ ├── WarmthOptions.jsx │ │ └── index.js │ │ ├── Watermark │ │ ├── Watermark.jsx │ │ ├── Watermark.styled.js │ │ ├── WatermarkPadding.jsx │ │ ├── WatermarksGallery.jsx │ │ └── index.jsx │ │ ├── tools.constants.js │ │ └── tools.styled.js │ ├── context │ ├── AppContext.js │ ├── AppProvider.jsx │ ├── AppProviderOverridenValue.jsx │ ├── appReducer.js │ ├── defaultConfig.js │ ├── defaultTranslations.js │ ├── getInitialAppState.js │ └── index.js │ ├── custom │ ├── filters │ │ ├── Aden.js │ │ ├── Amaro.js │ │ ├── Ashby.js │ │ ├── BaseFilters.js │ │ ├── BlackAndWhite.js │ │ ├── Brannan.js │ │ ├── Brooklyn.js │ │ ├── Charmes.js │ │ ├── Clarendon.js │ │ ├── Crema.js │ │ ├── Dogpatch.js │ │ ├── Earlybird.js │ │ ├── Gingham.js │ │ ├── Ginza.js │ │ ├── Hefe.js │ │ ├── Helena.js │ │ ├── Hudson.js │ │ ├── Juno.js │ │ ├── Kelvin.js │ │ ├── Lark.js │ │ ├── LoFi.js │ │ ├── Ludwig.js │ │ ├── Maven.js │ │ ├── Mayfair.js │ │ ├── Moon.js │ │ ├── Nashville.js │ │ ├── NinteenSeventySeven.js │ │ ├── Perpetua.js │ │ ├── Reyes.js │ │ ├── Rise.js │ │ ├── Sierra.js │ │ ├── Skyline.js │ │ ├── Slumber.js │ │ ├── Stinson.js │ │ ├── Sutro.js │ │ ├── Toaster.js │ │ ├── Valencia.js │ │ ├── Vesper.js │ │ ├── Walden.js │ │ ├── Willow.js │ │ ├── XPro2.js │ │ └── index.js │ └── finetunes │ │ ├── CustomThreshold.js │ │ ├── Warmth.js │ │ └── index.js │ ├── hooks │ ├── index.js │ ├── useAnnotation │ │ ├── getBoundingRectUnScaled.js │ │ ├── getNewAnnotationPreview.js │ │ ├── index.js │ │ └── previewThenCallAnnotationAdding.js │ ├── useAnnotationEvents.js │ ├── useAppReducer.js │ ├── useDebouncedCallback.js │ ├── useDrag.js │ ├── useFilter.js │ ├── useFinetune.js │ ├── usePhoneScreen.js │ ├── useResizeObserver.js │ ├── useStore.js │ ├── useTransformedImgData.js │ └── useUpdateEffect.js │ ├── index.d.ts │ ├── index.js │ └── utils │ ├── assignFinetuneNamesToKonva.js │ ├── calculateZoomData.js │ ├── cloudimageQueryToDesignState.js │ ├── compareRatios.js │ ├── constants.js │ ├── cropImage.js │ ├── debounce.js │ ├── deepMerge.js │ ├── extractCurrentDesignState.js │ ├── extractNameFromUrl.js │ ├── filterStrToClass.js │ ├── finetunesStrsToClasses.js │ ├── getCenterRotatedPoint.js │ ├── getDefaultSaveQuality.js │ ├── getDimensionsMinimalRatio.js │ ├── getElemDocumentCoords.js │ ├── getFileFullName.js │ ├── getImageSealingParams.js │ ├── getPointerOffsetPositionBoundedToObject.js │ ├── getProperDimensions.js │ ├── getProperImageToCanvasSpacing.js │ ├── getScrollOffset.js │ ├── getSizeAfterRotation.js │ ├── getZoomFitFactor.js │ ├── imageToBase64.js │ ├── isDefaultZeroValuesOnly.js │ ├── isSameImage.js │ ├── loadImage.js │ ├── mapCropBox.js │ ├── mapNumber.js │ ├── mapPositionStringToPoint.js │ ├── operationsToCloudimageUrl.js │ ├── randomId.js │ ├── restrictNumber.js │ ├── rgbaToHexa.js │ ├── sha1.js │ ├── toPrecisedFloat.js │ └── translator.js ├── public ├── assets │ ├── Ellipse 3.png │ ├── Hollow-Ellipse 3.png │ ├── add-annotation.svg │ ├── adding-icon.png │ ├── arrow-icon.png │ ├── arrow.png │ ├── check-icon.png │ ├── copy-icon.png │ ├── down-arrow-icon.png │ ├── git-stars.svg │ ├── github-logo.svg │ ├── half-circle.png │ ├── image-resize-mode.svg │ ├── loading.svg │ ├── scaleflex-logo.svg │ └── watermark-img.svg ├── demo-config.js ├── init.js └── style.css ├── vite.config.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | 6 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # This env. variables are used in deployment to filerobot CDN. 2 | # Copy the variables and paste them into .env file and replace with proper values 3 | UPLOAD_SECURITY_TEMPLATE_ID=FILEROBOT_KEY_USED_IN_UPLOADING_JS_BUNDLE 4 | PLUGINS_CONTAINER=FILEROBOT_CONTAINER_OF_PLUGINS_FOLDER 5 | PLUGIN_FOLDER=FILEROBOT_PLUGINS_FOLDER -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true, 5 | }, 6 | extends: [ 7 | 'airbnb', 8 | 'plugin:react/recommended', 9 | 'plugin:prettier/recommended', 10 | ], 11 | parserOptions: { 12 | ecmaFeatures: { 13 | jsx: true, 14 | }, 15 | ecmaVersion: 12, 16 | sourceType: 'module', 17 | }, 18 | plugins: ['react', 'prettier'], 19 | rules: { 20 | 'no-unused-vars': 'warn', 21 | 'import/no-cycle': 'off', 22 | 'import/no-extraneous-dependencies': [ 23 | 'error', 24 | { 25 | devDependencies: true, 26 | peerDependencies: true, 27 | }, 28 | ], 29 | 'import/prefer-default-export': 'off', 30 | 'react/prop-types': 'warn', 31 | 'react/jsx-props-no-spreading': 'off', 32 | 'react/jsx-filename-extension': 'error', 33 | 'react/no-array-index-key': 'error', 34 | 'react/prefer-read-only-props': 'error', 35 | 'react/jsx-key': ['error', { checkKeyMustBeforeSpread: true }], 36 | 'react/react-in-jsx-scope': 'off', 37 | 'react/function-component-definition': [ 38 | 2, 39 | { namedComponents: 'arrow-function' }, 40 | ], 41 | }, 42 | settings: { 43 | 'import/resolver': { 44 | node: { 45 | moduleDirectory: [ 46 | 'node_modules', 47 | 'packages/react-filerobot-image-editor/src', 48 | ], 49 | }, 50 | }, 51 | }, 52 | }; 53 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: unseen 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug (required)** 11 | A clear and concise description of what the bug is. 12 | 13 | **Codesandbox demo (required)** 14 | Provide an example link that produces the issue on any instant cloud development (ex. codesandbox). 15 | 16 | **To Reproduce (required)** 17 | Steps to reproduce the behavior: 18 | 1. Go to '...' 19 | 2. Click on '....' 20 | 3. Scroll down to '....' 21 | 4. See error 22 | 23 | **Expected behavior** 24 | A clear and concise description of what you expected to happen. 25 | 26 | **Videos/Screenshots** 27 | If applicable, add screenshots to help explain your problem. 28 | 29 | **Desktop (please complete the following information):** 30 | - OS: [e.g. iOS] 31 | - Browser [e.g. chrome, safari] 32 | - Version [e.g. 22] 33 | 34 | **Smartphone (please complete the following information):** 35 | - Device: [e.g. iPhone6] 36 | - OS: [e.g. iOS8.1] 37 | - Browser [e.g. stock browser, safari] 38 | - Version [e.g. 22] 39 | 40 | **Additional context** 41 | Add any other context about the problem here. 42 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: unseen 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | yarn.lock 4 | package-lock.json 5 | 6 | # Editor folders 7 | .cache 8 | .eslintCache 9 | .vscode 10 | .idea 11 | 12 | # production 13 | dist 14 | lib 15 | build 16 | demo-dist 17 | 18 | # misc 19 | .DS_Store 20 | .env 21 | .env.local 22 | .env.development.local 23 | .env.test.local 24 | .env.production.local 25 | .vite 26 | 27 | # Logs 28 | logs 29 | *.log 30 | npm-debug.log* 31 | yarn-debug.log* 32 | yarn-error.log* 33 | lerna-debug.log* -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "jsxSingleQuote": false, 5 | "endOfLine": "lf", 6 | "semi": true 7 | } -------------------------------------------------------------------------------- /DEVELOPMENT.md: -------------------------------------------------------------------------------- 1 | # Dev. notes for developers in-case of contribution 2 | 3 | #### Glossary 4 | - $reactPackage === packages/react-filerobot-image-editor/ 5 | - 6 | 7 | #### Adding a new property 8 | 9 | - Doing a global search for any property at least should exist 1 time occurrence in each of these 5 files (README.md, $reactPackage/index.d.ts, defaultConfig.js, demo-config.js & -- feature's implementation file --). -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 scaleflex 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "modules": false 7 | } 8 | ], 9 | "@babel/preset-react", 10 | [ 11 | "minify", 12 | { 13 | "builtIns": false 14 | } 15 | ] 16 | ], 17 | "comments": false, 18 | "plugins": [ 19 | [ 20 | "module-resolver", 21 | { 22 | "root": [ 23 | "./src" 24 | ], 25 | "alias": { 26 | "react-filerobot-image-editor/src/": "react-filerobot-image-editor", 27 | "react-filerobot-image-editor/src/utils/deepMerge": "react-filerobot-image-editor/lib/utils/deepMerge", 28 | } 29 | } 30 | ], 31 | [ 32 | "babel-plugin-styled-components" 33 | ], 34 | [ 35 | "@babel/plugin-transform-runtime" 36 | ] 37 | ], 38 | "env": { 39 | "production": { 40 | "plugins": [ 41 | [ 42 | "babel-plugin-styled-components", 43 | { 44 | "pure": true, 45 | "displayName": false 46 | } 47 | ], 48 | [ 49 | "transform-react-remove-prop-types", 50 | { 51 | "removeImport": true 52 | } 53 | ] 54 | ] 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "packages/react-filerobot-image-editor/src", 4 | "jsx": "preserve" 5 | }, 6 | "include": [ 7 | "packages/react-filerobot-image-editor/src" 8 | ] 9 | } -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "npmClient": "yarn", 3 | "useWorkspaces": true, 4 | "packages": ["packages/*"], 5 | "version": "4.9.1", 6 | "command": { 7 | "version": { 8 | "message": "Chore(Release): publish new version %s🔥🚀" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/filerobot-image-editor/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 scaleflex 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /packages/filerobot-image-editor/README.md: -------------------------------------------------------------------------------- 1 | # filerobot-image-editor 2 | 3 | Vanilla Javascript bridged version of filerobot image editor (FIE). 4 | 5 | Visit [main README.md](https://github.com/scaleflex/filerobot-image-editor#readme) for docs. -------------------------------------------------------------------------------- /packages/filerobot-image-editor/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "filerobot-image-editor", 3 | "version": "4.8.1", 4 | "license": "MIT", 5 | "author": "Scaleflex", 6 | "homepage": "https://github.com/scaleflex/filerobot-image-editor#readme", 7 | "main": "lib/index.js", 8 | "types": "lib/index.d.ts", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/scaleflex/filerobot-image-editor#readme" 12 | }, 13 | "keywords": [ 14 | "Image editing javascript library", 15 | "Javascript image editor library", 16 | "image editor", 17 | "filerobot image editor", 18 | "edit image", 19 | "modify image", 20 | "update image", 21 | "finetune", 22 | "filters", 23 | "weatermark", 24 | "resize", 25 | "annotate" 26 | ], 27 | "files": [ 28 | "lib" 29 | ], 30 | "dependencies": { 31 | "@babel/runtime": "^7.17.2", 32 | "react": "^18.2.0", 33 | "react-dom": "^18.2.0", 34 | "react-konva": "^18.2.10", 35 | "styled-components": "^5.3.5" 36 | }, 37 | "peerDependencies": { 38 | "react-filerobot-image-editor": "^4.7.0" 39 | }, 40 | "scripts": { 41 | "build:lib": "rimraf lib && cross-env BABEL_ENV=production NODE_ENV=production babel src -d lib --config-file ../../babel.config.json -D" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/filerobot-image-editor/src/index.d.ts: -------------------------------------------------------------------------------- 1 | import { FilerobotImageEditorConfig, TABS, TOOLS, getCurrentImgDataFunction } from 'react-filerobot-image-editor'; 2 | 3 | declare class FilerobotImageEditor { 4 | TABS: typeof TABS; 5 | TOOLS: typeof TOOLS; 6 | constructor(container: HTMLElement, config: FilerobotImageEditorConfig); 7 | render(additionalConfig?: FilerobotImageEditorConfig): void; 8 | terminate(): void; 9 | getCurrentImgData: getCurrentImgDataFunction; 10 | updateState: (newStatePart: {} | ((currentState: {}) => void)) => void; 11 | } 12 | 13 | export default FilerobotImageEditor; 14 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 scaleflex 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/README.md: -------------------------------------------------------------------------------- 1 | # react-filerobot-image-editor 2 | 3 | React component version of filerobot image editor (FIE). 4 | 5 | Visit [main README.md](https://github.com/scaleflex/filerobot-image-editor#readme) for docs. -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-filerobot-image-editor", 3 | "version": "4.9.1", 4 | "license": "MIT", 5 | "author": "Scaleflex", 6 | "homepage": "https://github.com/scaleflex/filerobot-image-editor#readme", 7 | "main": "./lib/index.js", 8 | "types": "./lib/index.d.ts", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/scaleflex/filerobot-image-editor#readme" 12 | }, 13 | "keywords": [ 14 | "Image editing react component", 15 | "React Image Editor", 16 | "image editor", 17 | "filerobot image editor", 18 | "edit image", 19 | "modify image", 20 | "update image", 21 | "finetune", 22 | "filters", 23 | "weatermark", 24 | "resize", 25 | "annotate" 26 | ], 27 | "files": [ 28 | "lib" 29 | ], 30 | "dependencies": { 31 | "@babel/runtime": "^7.17.2", 32 | "@scaleflex/icons": "2.10.27", 33 | "@scaleflex/ui": "2.10.27", 34 | "konva": "9.3.6", 35 | "prop-types": "15.7.2" 36 | }, 37 | "peerDependencies": { 38 | "react": ">=17.0.0", 39 | "react-dom": ">=17.0.0", 40 | "react-konva": ">=17.0.0", 41 | "styled-components": ">=5.3.5" 42 | }, 43 | "scripts": { 44 | "build:lib": "rimraf lib && cross-env BABEL_ENV=production NODE_ENV=production babel src -d lib --config-file ../../babel.config.json -D" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/actions/addFilter.js: -------------------------------------------------------------------------------- 1 | export const ADD_FILTER = 'ADD_FILTER'; 2 | 3 | const addFilter = (state, payload) => ({ 4 | ...state, 5 | isDesignState: !payload.dismissHistory, // not stored in state, used in reducer to consider in undo/redo stacks 6 | filter: payload.filter || null, 7 | }); 8 | 9 | export default addFilter; 10 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/actions/changePointerIcon.js: -------------------------------------------------------------------------------- 1 | export const CHANGE_POINTER_ICON = 'CHANGE_POINTER_ICON'; 2 | 3 | const changingPointerIcon = (state, payload) => 4 | state.pointerCssIcon !== payload.pointerCssIcon 5 | ? { 6 | ...state, 7 | pointerCssIcon: payload.pointerCssIcon, 8 | } 9 | : state; 10 | 11 | export default changingPointerIcon; 12 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/actions/changeRotation.js: -------------------------------------------------------------------------------- 1 | export const CHANGE_ROTATION = 'CHANGE_ROTATION'; 2 | 3 | const changeRotation = (state, payload) => 4 | state.adjustments.rotation !== payload.rotation 5 | ? { 6 | ...state, 7 | isDesignState: !payload.dismissHistory, 8 | adjustments: { 9 | ...state.adjustments, 10 | rotation: payload.rotation, 11 | }, 12 | } 13 | : state; 14 | 15 | export default changeRotation; 16 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/actions/clearAnnotationsSelections.js: -------------------------------------------------------------------------------- 1 | export const CLEAR_ANNOTATIONS_SELECTIONS = 'CLEAR_ANNOTATIONS_SELECTIONS'; 2 | 3 | const clearAnnotationsSelections = (state) => 4 | state.selectionsIds.length === 0 5 | ? state 6 | : { 7 | ...state, 8 | selectionsIds: [], 9 | }; 10 | 11 | export default clearAnnotationsSelections; 12 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/actions/duplicateAnnotations.js: -------------------------------------------------------------------------------- 1 | /** Internal Dependencies */ 2 | import randomId from 'utils/randomId'; 3 | 4 | export const DUPLICATE_ANNOTATIONS = 'DUPLICATE_ANNOTATIONS'; 5 | 6 | const duplicateAnnotations = (state, payload) => { 7 | const { annotations } = state; 8 | const duplicatedAnnotations = {}; 9 | payload.annotationsIds.forEach((id) => { 10 | const annotation = annotations[id]; 11 | if (annotation) { 12 | const clonedAnnotationId = randomId(annotation.name); 13 | duplicatedAnnotations[clonedAnnotationId] = { 14 | ...annotation, 15 | id: clonedAnnotationId, 16 | x: annotation.x + 20, 17 | y: annotation.y + 20, 18 | }; 19 | } 20 | }); 21 | 22 | return { 23 | ...state, 24 | // not stored in state, used in reducer to consider in undo/redo stacks 25 | isDesignState: !payload.dismissHistory, 26 | annotations: { 27 | ...annotations, 28 | ...duplicatedAnnotations, 29 | }, 30 | }; 31 | }; 32 | 33 | export default duplicateAnnotations; 34 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/actions/enableTextContentEdit.js: -------------------------------------------------------------------------------- 1 | export const ENABLE_TEXT_CONTENT_EDIT = 'ENABLE_TEXT_CONTENT_EDIT'; 2 | 3 | const enableTextContentEdit = (state, payload) => ({ 4 | ...state, 5 | textIdOfEditableContent: payload.textIdOfEditableContent || null, 6 | }); 7 | 8 | export default enableTextContentEdit; 9 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/actions/hideLoader.js: -------------------------------------------------------------------------------- 1 | export const HIDE_LOADER = 'HIDE_LOADER'; 2 | 3 | const hideLoader = (state) => ({ 4 | ...state, 5 | isLoadingGlobally: false, 6 | }); 7 | 8 | export default hideLoader; 9 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/actions/redo.js: -------------------------------------------------------------------------------- 1 | /** Internal Dependencies */ 2 | import extractCurrentDesignState from 'utils/extractCurrentDesignState'; 3 | 4 | export const REDO = 'REDO'; 5 | 6 | const redo = (state) => { 7 | if (state.futureDesignStates && state.futureDesignStates.length > 0) { 8 | const currentDesignState = extractCurrentDesignState(state); 9 | const [presentDesignState, ...newFutureDesignStates] = 10 | state.futureDesignStates; 11 | const newPastDesignStates = [ 12 | currentDesignState, 13 | ...(state.pastDesignStates || []), 14 | ]; 15 | 16 | return { 17 | ...state, 18 | ...presentDesignState, 19 | selectionsIds: [], 20 | pastDesignStates: newPastDesignStates, 21 | futureDesignStates: newFutureDesignStates, 22 | hasUndo: true, 23 | hasRedo: newFutureDesignStates.length > 0, 24 | haveNotSavedChanges: true, 25 | }; 26 | } 27 | 28 | return state; 29 | }; 30 | 31 | export default redo; 32 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/actions/removeAnnotations.js: -------------------------------------------------------------------------------- 1 | export const REMOVE_ANNOTATIONS = 'REMOVE_ANNOTATIONS'; 2 | 3 | const removeAnnotations = (state, payload) => { 4 | const { annotations } = state; 5 | let newSelectionsIds = state.selectionsIds; 6 | 7 | payload.annotationsIds.forEach((id) => { 8 | newSelectionsIds = newSelectionsIds.filter( 9 | (selectionId) => selectionId !== id, 10 | ); 11 | 12 | if (state.designLayer && annotations[id]) { 13 | const annotationNode = state.designLayer.findOne(`#${id}`); 14 | if (annotationNode) { 15 | annotationNode.destroy(); 16 | } 17 | delete annotations[id]; 18 | } 19 | }); 20 | 21 | return { 22 | ...state, 23 | // not stored in state, used in reducer to consider in undo/redo stacks 24 | isDesignState: payload.isDesignState || true, 25 | annotations, 26 | selectionsIds: [], 27 | }; 28 | }; 29 | 30 | export default removeAnnotations; 31 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/actions/reset.js: -------------------------------------------------------------------------------- 1 | /** Internal Dependencies */ 2 | import { DEFAULT_ZOOM_FACTOR } from 'utils/constants'; 3 | import extractCurrentDesignState from 'utils/extractCurrentDesignState'; 4 | 5 | export const RESET = 'RESET'; 6 | 7 | const reset = (state, payload) => { 8 | const resettedDesignState = extractCurrentDesignState( 9 | { 10 | ...payload.config, 11 | imgSrc: state.imgSrc, 12 | }, 13 | true, 14 | ); 15 | 16 | return { 17 | ...state, 18 | ...resettedDesignState, 19 | zoom: { 20 | factor: DEFAULT_ZOOM_FACTOR, 21 | x: null, 22 | y: null, 23 | }, 24 | selectionsIds: [], 25 | isResetted: true, 26 | pastDesignStates: [], 27 | futureDesignStates: [], 28 | hasUndo: false, 29 | hasRedo: false, 30 | haveNotSavedChanges: false, 31 | }; 32 | }; 33 | 34 | export default reset; 35 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/actions/selectAnnotation.js: -------------------------------------------------------------------------------- 1 | export const SELECT_ANNOTATION = 'SELECT_ANNOTATION'; 2 | 3 | const selectAnnotation = (state, payload) => { 4 | if ( 5 | state.selectionsIds.length === 1 && 6 | state.selectionsIds[0] === payload.annotationId 7 | ) { 8 | return state; 9 | } 10 | 11 | let newSelectionsIds; 12 | if (payload.multiple) { 13 | newSelectionsIds = state.selectionsIds.filter( 14 | (id) => id !== payload.annotationId, 15 | ); 16 | 17 | const wasAnnotationAlreadySelected = 18 | newSelectionsIds.length !== state.selectionsIds.length; 19 | if (!wasAnnotationAlreadySelected) { 20 | newSelectionsIds.push(payload.annotationId); 21 | } 22 | } else { 23 | newSelectionsIds = [payload.annotationId]; 24 | } 25 | 26 | return { 27 | ...state, 28 | selectionsIds: newSelectionsIds, 29 | }; 30 | }; 31 | 32 | export default selectAnnotation; 33 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/actions/selectTab.js: -------------------------------------------------------------------------------- 1 | import { TABS_TOOLS } from 'components/tools/tools.constants'; 2 | import { POINTER_ICONS, TABS_IDS } from 'utils/constants'; 3 | 4 | export const SELECT_TAB = 'SELECT_TAB'; 5 | 6 | const selectTab = (state, payload) => 7 | payload.tabId === state.tabId 8 | ? state 9 | : { 10 | ...state, 11 | tabId: payload.tabId, 12 | toolId: TABS_TOOLS[payload.tabId][0], 13 | selectionsIds: [], 14 | pointerCssIcon: 15 | payload.tabId === TABS_IDS.ANNOTATE 16 | ? POINTER_ICONS.DRAW 17 | : POINTER_ICONS.DEFAULT, 18 | }; 19 | 20 | export default selectTab; 21 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/actions/selectTool.js: -------------------------------------------------------------------------------- 1 | export const SELECT_TOOL = 'SELECT_TOOL'; 2 | 3 | const selectTool = (state, payload) => 4 | state.toolId === payload.toolId 5 | ? state 6 | : { 7 | ...state, 8 | toolId: payload.toolId, 9 | selectionsIds: payload.keepSelections ? state.selectionsIds : [], 10 | }; 11 | 12 | export default selectTool; 13 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/actions/setAnnotation.js: -------------------------------------------------------------------------------- 1 | import randomId from 'utils/randomId'; 2 | 3 | export const SET_ANNOTATION = 'SET_ANNOTATION'; 4 | 5 | const setAnnotation = (state, payload = {}) => { 6 | // dismissHistory is used to prevent considering this change in history (undo/redo). 7 | const { 8 | dismissHistory = false, 9 | replaceCurrent = false, 10 | ...newAnnotation 11 | } = payload; 12 | const annotationId = newAnnotation.id ?? randomId(newAnnotation.name); 13 | 14 | const existedAnnotation = state.annotations[annotationId]; 15 | // If annotation not changed don't update it. 16 | if ( 17 | existedAnnotation && 18 | !Object.keys(newAnnotation).some( 19 | (key) => 20 | (newAnnotation[key] || newAnnotation[key] === 0) && 21 | newAnnotation[key] !== existedAnnotation[key], 22 | ) 23 | ) { 24 | return state; 25 | } 26 | 27 | return { 28 | ...state, 29 | isDesignState: !dismissHistory, // not stored in state, used in reducer to consider in undo/redo stacks 30 | annotations: { 31 | ...state.annotations, 32 | [annotationId]: { 33 | ...(replaceCurrent ? {} : existedAnnotation), 34 | ...newAnnotation, 35 | }, 36 | }, 37 | }; 38 | }; 39 | 40 | export default setAnnotation; 41 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/actions/setFeedback.js: -------------------------------------------------------------------------------- 1 | export const SET_FEEDBACK = 'SET_FEEDBACK'; 2 | 3 | const setFeedback = (state, payload) => ({ 4 | ...state, 5 | isLoadingGlobally: false, 6 | feedback: payload.feedback || {}, 7 | }); 8 | 9 | export default setFeedback; 10 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/actions/setFinetune.js: -------------------------------------------------------------------------------- 1 | export const SET_FINETUNE = 'SET_FINETUNE'; 2 | 3 | const setFinetune = (state, payload) => ({ 4 | ...state, 5 | isDesignState: !payload.dismissHistory, // not stored in state, used in reducer to consider in undo/redo stacks 6 | finetunes: 7 | !payload.finetune || state.finetunes.includes(payload.finetune) 8 | ? state.finetunes 9 | : [...state.finetunes, payload.finetune], 10 | finetunesProps: { 11 | ...state.finetunesProps, 12 | ...payload.finetuneProps, 13 | }, 14 | }); 15 | 16 | export default setFinetune; 17 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/actions/setLatestColor.js: -------------------------------------------------------------------------------- 1 | export const SET_LATEST_COLOR = 'SET_LATEST_COLOR'; 2 | 3 | const setLatestColor = (state, payload) => ({ 4 | ...state, 5 | latestColors: { 6 | ...state.latestColors, 7 | ...payload.latestColors, 8 | }, 9 | }); 10 | 11 | export default setLatestColor; 12 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/actions/setOriginalImage.js: -------------------------------------------------------------------------------- 1 | export const SET_ORIGINAL_IMAGE = 'SET_ORIGINAL_IMAGE'; 2 | 3 | const setOriginalImage = (state, payload) => ({ 4 | ...state, 5 | feedback: {}, 6 | originalImage: payload.originalImage, 7 | imgSrc: payload.originalImage.src, 8 | }); 9 | 10 | export default setOriginalImage; 11 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/actions/setResize.js: -------------------------------------------------------------------------------- 1 | export const SET_RESIZE = 'SET_RESIZE'; 2 | 3 | const setResize = (state, payload) => ({ 4 | ...state, 5 | isDesignState: !payload.dismissHistory, 6 | resize: { 7 | ...state.resize, 8 | // width, height, manualChangeDisabled (false by default), ratioUnlocked (locked by default). 9 | ...payload, 10 | manualChangeDisabled: payload.manualChangeDisabled ?? false, 11 | }, 12 | }); 13 | 14 | export default setResize; 15 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/actions/setSaved.js: -------------------------------------------------------------------------------- 1 | export const SET_SAVED = 'SET_SAVED'; 2 | 3 | const setSaved = (state) => 4 | !state.haveNotSavedChanges ? state : { ...state, haveNotSavedChanges: false }; 5 | 6 | export default setSaved; 7 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/actions/setSaving.js: -------------------------------------------------------------------------------- 1 | export const SET_SAVING = 'SET_SAVING'; 2 | 3 | const setSaving = (state, payload) => ({ 4 | ...state, 5 | isSaving: payload.isSaving, 6 | isLoadingGlobally: payload.isSaving, 7 | }); 8 | 9 | export default setSaving; 10 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/actions/setShowTabsMenu.js: -------------------------------------------------------------------------------- 1 | export const SET_SHOWN_TABS_MENU = 'SET_SHOWN_TABS_MENU'; 2 | 3 | const setShowTabsMenu = (state, payload) => ({ 4 | ...state, 5 | showTabsMenu: payload.opened, 6 | }); 7 | 8 | export default setShowTabsMenu; 9 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/actions/setShownImageDimensions.js: -------------------------------------------------------------------------------- 1 | export const SET_SHOWN_IMAGE_DIMENSIONS = 'SET_SHOWN_IMAGE_DIMENSIONS'; 2 | 3 | const setShownImageDimensions = (state, payload) => ({ 4 | ...state, 5 | shownImageDimensions: { 6 | ...state.shownImageDimensions, 7 | ...payload.shownImageDimensions, 8 | }, 9 | designLayer: payload.designLayer || state.designLayer, 10 | previewGroup: payload.previewGroup || state.previewGroup, 11 | }); 12 | 13 | export default setShownImageDimensions; 14 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/actions/showLoader.js: -------------------------------------------------------------------------------- 1 | export const SHOW_LOADER = 'SHOW_LOADER'; 2 | 3 | const showLoader = (state) => ({ 4 | ...state, 5 | isLoadingGlobally: true, 6 | }); 7 | 8 | export default showLoader; 9 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/actions/toggleFlip.js: -------------------------------------------------------------------------------- 1 | /** Internal Dependencies */ 2 | import { FLIP_DIRECTIONS } from 'utils/constants'; 3 | 4 | export const TOGGLE_FLIP = 'TOGGLE_FLIP'; 5 | 6 | const toggleFlip = (state, payload) => { 7 | const flipProperty = `isFlipped${ 8 | payload.direction === FLIP_DIRECTIONS.X ? 'X' : 'Y' 9 | }`; 10 | 11 | return { 12 | ...state, 13 | isDesignState: !payload.dismissHistory, 14 | adjustments: { 15 | ...state.adjustments, 16 | [flipProperty]: !state.adjustments[flipProperty], 17 | }, 18 | }; 19 | }; 20 | 21 | export default toggleFlip; 22 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/actions/toggleOriginalImageDisplay.js: -------------------------------------------------------------------------------- 1 | export const TOGGLE_ORIGINAL_IMAGE_DISPLAY = 'TOGGLE_ORIGINAL_IMAGE_DISPLAY'; 2 | 3 | const toggleOriginalImageDisplay = (state, payload) => ({ 4 | ...state, 5 | isShowOriginalImage: payload.isShow, 6 | }); 7 | 8 | export default toggleOriginalImageDisplay; 9 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/actions/undo.js: -------------------------------------------------------------------------------- 1 | /** Internal Dependencies */ 2 | import extractCurrentDesignState from 'utils/extractCurrentDesignState'; 3 | 4 | export const UNDO = 'UNDO'; 5 | 6 | const undo = (state) => { 7 | if (state.pastDesignStates && state.pastDesignStates.length > 0) { 8 | const currentDesignState = extractCurrentDesignState(state); 9 | const [presentDesignState, ...newPastDesignStates] = state.pastDesignStates; 10 | const newFutureDesignStates = [ 11 | currentDesignState, 12 | ...(state.futureDesignStates || []), 13 | ]; 14 | 15 | return { 16 | ...state, 17 | ...presentDesignState, 18 | selectionsIds: [], 19 | pastDesignStates: newPastDesignStates, 20 | futureDesignStates: newFutureDesignStates, 21 | hasUndo: newPastDesignStates.length > 0, 22 | hasRedo: true, 23 | haveNotSavedChanges: newPastDesignStates.length > 0, 24 | }; 25 | } 26 | 27 | return state; 28 | }; 29 | 30 | export default undo; 31 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/actions/updateState.js: -------------------------------------------------------------------------------- 1 | /** Internal Dependencies */ 2 | import deepMerge from 'utils/deepMerge'; 3 | 4 | export const UPDATE_STATE = 'UPDATE_STATE'; 5 | 6 | const updateState = (state, payloadObjOrFn) => { 7 | const payload = 8 | payloadObjOrFn && typeof payloadObjOrFn === 'function' 9 | ? payloadObjOrFn(state) 10 | : payloadObjOrFn; 11 | return payload ? deepMerge(state, payload) : state; 12 | }; 13 | 14 | export default updateState; 15 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/actions/zoomCanvas.js: -------------------------------------------------------------------------------- 1 | /** Internal Dependencies */ 2 | import calculateZoomData from 'utils/calculateZoomData'; 3 | import { DEFAULT_ZOOM_FACTOR } from 'utils/constants'; 4 | import restrictNumber from 'utils/restrictNumber'; 5 | 6 | export const ZOOM_CANVAS = 'ZOOM_CANVAS'; 7 | 8 | const MIN_ZOOM_FACTOR = 0.03; 9 | const MAX_ZOOM_FACTOR = 60; 10 | 11 | const zoomCanvas = (state, payload) => { 12 | const newZoomFactor = restrictNumber( 13 | parseFloat(payload.factor).toFixed(2), 14 | MIN_ZOOM_FACTOR, 15 | MAX_ZOOM_FACTOR, 16 | ); 17 | 18 | let newZoomData; 19 | 20 | if (payload.preparedDimensions) { 21 | const { preparedDimensions, ...zoomProps } = payload; 22 | newZoomData = zoomProps; 23 | } else { 24 | const newZoomPoint = { 25 | x: 26 | !payload.x && payload.x !== 0 27 | ? state.canvasWidth / 2 28 | : payload.x ?? state.zoom.x, 29 | y: 30 | !payload.y && payload.y !== 0 31 | ? state.canvasHeight / 2 32 | : payload.y ?? state.zoom.y, 33 | }; 34 | 35 | newZoomData = calculateZoomData( 36 | { ...newZoomPoint, factor: newZoomFactor }, 37 | // `isAbsoluteZoom` means we don't depend on the old zoom, and we are going to zoom & pan assuming it's happening for firsst time. 38 | payload.isAbsoluteZoom 39 | ? { factor: DEFAULT_ZOOM_FACTOR, x: null, y: null } 40 | : state.zoom, 41 | state.canvasWidth, 42 | state.canvasHeight, 43 | ); 44 | } 45 | 46 | return newZoomData.factor === state.zoom.factor && 47 | newZoomData.x === state.zoom.x && 48 | newZoomData.y === state.zoom.y 49 | ? state 50 | : { 51 | ...state, 52 | zoom: { ...state.zoom, ...newZoomData }, 53 | }; 54 | }; 55 | 56 | export default zoomCanvas; 57 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/AssemblyPoint/globalStyles.js: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import { createGlobalStyle } from 'styled-components'; 3 | 4 | /** Internal Dependencies */ 5 | import { ROOT_CONTAINER_CLASS_NAME } from 'utils/constants'; 6 | 7 | const FontsFaces = createGlobalStyle` 8 | .${ROOT_CONTAINER_CLASS_NAME} { 9 | font-family: ${({ theme = {} }) => theme.typography?.fontFamily || 'Arial'}; 10 | } 11 | 12 | .SfxModal-Wrapper * { 13 | font-family: ${({ theme = {} }) => theme.typography?.fontFamily || 'Arial'}; 14 | } 15 | `; 16 | 17 | const OverrideDefaultStyles = createGlobalStyle` 18 | .Menu-open { 19 | overflow: visible !important; 20 | } 21 | 22 | .${ROOT_CONTAINER_CLASS_NAME}, #SfxPopper { 23 | box-sizing: border-box; 24 | 25 | .SfxPopper-root .SfxMenu-root { 26 | overflow: visible; 27 | width: max-content; 28 | 29 | .SfxMenuItem-prefix { 30 | margin-right: 6px; 31 | } 32 | } 33 | } 34 | .${ROOT_CONTAINER_CLASS_NAME} *, #SfxPopper * { 35 | box-sizing: border-box; 36 | scrollbar-color: rgba(203, 211, 218, 1) rgba(203, 211, 218, 0.35); 37 | 38 | :not(button) > svg:not([color]) { 39 | color: ${({ theme }) => theme.palette['icons-primary']} 40 | } 41 | 42 | :disabled, [aria-disabled="true"] { 43 | cursor: not-allowed; 44 | } 45 | 46 | &::-webkit-scrollbar { 47 | width: 4px; 48 | height: 4px; 49 | } 50 | 51 | &::-webkit-scrollbar-track { 52 | background: rgba(203, 211, 218, 0.35); 53 | } 54 | 55 | &::-webkit-scrollbar-thumb { 56 | background: rgba(203, 211, 218, 1); 57 | border-radius: 10px; 58 | } 59 | } 60 | `; 61 | 62 | export { FontsFaces, OverrideDefaultStyles }; 63 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/FeedbackPopup/index.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | import Popup from '@scaleflex/ui/core/popup'; 5 | 6 | /** Internal Dependencies */ 7 | import { SET_FEEDBACK } from 'actions'; 8 | import { useStore } from 'hooks'; 9 | import { FEEDBACK_STATUSES } from 'utils/constants'; 10 | 11 | const defaultAnchorOrigin = { 12 | horizontal: 'center', 13 | vertical: 'bottom', 14 | }; 15 | 16 | const ERROR_TO_ROBOT_STATUS = { 17 | [FEEDBACK_STATUSES.ERROR]: 'error', 18 | [FEEDBACK_STATUSES.WARNING]: 'warning', 19 | }; 20 | 21 | const FeedbackPopup = ({ anchorOrigin }) => { 22 | const { feedback = {}, dispatch } = useStore(); 23 | 24 | if (!feedback.message) { 25 | return null; 26 | } 27 | 28 | const onClose = () => { 29 | dispatch({ 30 | type: SET_FEEDBACK, 31 | payload: { 32 | feedback: {}, 33 | }, 34 | }); 35 | }; 36 | 37 | return ( 38 | 47 | ); 48 | }; 49 | 50 | FeedbackPopup.defaultProps = { 51 | anchorOrigin: defaultAnchorOrigin, 52 | }; 53 | 54 | FeedbackPopup.propTypes = { 55 | anchorOrigin: PropTypes.instanceOf(Object), 56 | }; 57 | 58 | export default FeedbackPopup; 59 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/Layers/DesignLayer/AnnotationNodes/AnnotationNodes.constants.js: -------------------------------------------------------------------------------- 1 | /** Internal Dependencies */ 2 | import { TOOLS_IDS } from 'utils/constants'; 3 | import RectNode from './RectNode'; 4 | import EllipseNode from './EllipseNode'; 5 | import PolygonNode from './PolygonNode'; 6 | import TextNode from './TextNode'; 7 | import ImageNode from './ImageNode'; 8 | import LineNode from './LineNode'; 9 | import ArrowNode from './ArrowNode'; 10 | 11 | export const ANNOTATION_NAMES_TO_COMPONENT = { 12 | [TOOLS_IDS.RECT]: RectNode, 13 | [TOOLS_IDS.ELLIPSE]: EllipseNode, 14 | [TOOLS_IDS.POLYGON]: PolygonNode, 15 | [TOOLS_IDS.TEXT]: TextNode, 16 | [TOOLS_IDS.IMAGE]: ImageNode, 17 | [TOOLS_IDS.LINE]: LineNode, 18 | [TOOLS_IDS.ARROW]: ArrowNode, 19 | [TOOLS_IDS.PEN]: LineNode, 20 | }; 21 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/Layers/DesignLayer/AnnotationNodes/EllipseNode.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | import { Ellipse } from 'react-konva'; 5 | 6 | /** Internal Dependencies */ 7 | import nodesCommonPropTypes from '../nodesCommonPropTypes'; 8 | 9 | const EllipseNode = ({ 10 | id, 11 | name, 12 | fill, 13 | x, 14 | y, 15 | radiusX, 16 | radiusY, 17 | scaleX, 18 | scaleY, 19 | rotation, 20 | annotationEvents, 21 | stroke, 22 | strokeWidth, 23 | shadowOffsetX, 24 | shadowOffsetY, 25 | shadowBlur, 26 | shadowColor, 27 | shadowOpacity, 28 | opacity, 29 | ...otherProps 30 | }) => ( 31 | 55 | ); 56 | 57 | EllipseNode.defaultProps = { 58 | ...nodesCommonPropTypes.defaults, 59 | fill: '#000', 60 | radiusX: 0, 61 | radiusY: 0, 62 | }; 63 | 64 | EllipseNode.propTypes = { 65 | ...nodesCommonPropTypes.definitions, 66 | x: PropTypes.number.isRequired, 67 | y: PropTypes.number.isRequired, 68 | annotationEvents: PropTypes.instanceOf(Object).isRequired, 69 | radiusX: PropTypes.number, 70 | radiusY: PropTypes.number, 71 | fill: PropTypes.string, 72 | }; 73 | 74 | export default EllipseNode; 75 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/Layers/DesignLayer/AnnotationNodes/LineNode.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | import { Line } from 'react-konva'; 5 | 6 | /** Internal Dependencies */ 7 | import nodesCommonPropTypes from '../nodesCommonPropTypes'; 8 | 9 | const LineNode = ({ 10 | id, 11 | name, 12 | scaleX, 13 | scaleY, 14 | rotation, 15 | annotationEvents, 16 | points, 17 | lineCap, 18 | stroke, 19 | strokeWidth, 20 | shadowOffsetX, 21 | shadowOffsetY, 22 | shadowBlur, 23 | shadowColor, 24 | shadowOpacity, 25 | tension, 26 | opacity, 27 | ...otherProps 28 | }) => ( 29 | 52 | ); 53 | 54 | LineNode.defaultProps = { 55 | ...nodesCommonPropTypes.defaults, 56 | stroke: '#000000', 57 | strokeWidth: 1, 58 | lineCap: 'butt', // butt/round/square 59 | annotationEvents: {}, 60 | tension: undefined, 61 | }; 62 | 63 | LineNode.propTypes = { 64 | ...nodesCommonPropTypes.definitions, 65 | points: PropTypes.instanceOf(Array).isRequired, 66 | annotationEvents: PropTypes.instanceOf(Object), 67 | lineCap: PropTypes.string, 68 | tension: PropTypes.number, 69 | }; 70 | 71 | export default LineNode; 72 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/Layers/DesignLayer/AnnotationNodes/MemoizedAnnotation.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React, { memo } from 'react'; 3 | import PropTypes from 'prop-types'; 4 | 5 | /** Internal Dependencies */ 6 | import { ANNOTATION_NAMES_TO_COMPONENT } from './AnnotationNodes.constants'; 7 | 8 | const MemoizedAnnotation = ({ 9 | annotation, 10 | annotationEvents, 11 | selectionsIds, 12 | }) => { 13 | const AnnotationComponent = ANNOTATION_NAMES_TO_COMPONENT[annotation.name]; 14 | if (!AnnotationComponent) return null; 15 | 16 | return ( 17 | 23 | ); 24 | }; 25 | 26 | MemoizedAnnotation.propTypes = { 27 | annotation: PropTypes.instanceOf(Object).isRequired, 28 | annotationEvents: PropTypes.instanceOf(Object).isRequired, 29 | selectionsIds: PropTypes.instanceOf(Object).isRequired, 30 | }; 31 | 32 | export default memo(MemoizedAnnotation); 33 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/Layers/DesignLayer/AnnotationNodes/PolygonNode.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | import { RegularPolygon } from 'react-konva'; 5 | 6 | /** Internal Dependencies */ 7 | import nodesCommonPropTypes from '../nodesCommonPropTypes'; 8 | 9 | const PolygonNode = ({ 10 | id, 11 | name, 12 | fill, 13 | x, 14 | y, 15 | radius, 16 | scaleX, 17 | scaleY, 18 | rotation, 19 | sides, 20 | annotationEvents, 21 | stroke, 22 | strokeWidth, 23 | shadowOffsetX, 24 | shadowOffsetY, 25 | shadowBlur, 26 | shadowColor, 27 | shadowOpacity, 28 | opacity, 29 | ...otherProps 30 | }) => ( 31 | 55 | ); 56 | 57 | PolygonNode.defaultProps = { 58 | ...nodesCommonPropTypes.defaults, 59 | fill: '#000', 60 | sides: 3, 61 | }; 62 | 63 | PolygonNode.propTypes = { 64 | ...nodesCommonPropTypes.definitions, 65 | x: PropTypes.number.isRequired, 66 | y: PropTypes.number.isRequired, 67 | annotationEvents: PropTypes.instanceOf(Object).isRequired, 68 | radius: PropTypes.number.isRequired, 69 | fill: PropTypes.string, 70 | sides: PropTypes.number, 71 | }; 72 | 73 | export default PolygonNode; 74 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/Layers/DesignLayer/AnnotationNodes/RectNode.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | import { Rect } from 'react-konva'; 5 | 6 | /** Internal Dependencies */ 7 | import nodesCommonPropTypes from '../nodesCommonPropTypes'; 8 | 9 | const RectNode = ({ 10 | id, 11 | name, 12 | fill, 13 | x, 14 | y, 15 | width, 16 | height, 17 | scaleX, 18 | scaleY, 19 | rotation, 20 | annotationEvents, 21 | stroke, 22 | strokeWidth, 23 | shadowOffsetX, 24 | shadowOffsetY, 25 | shadowBlur, 26 | shadowColor, 27 | shadowOpacity, 28 | opacity, 29 | cornerRadius, 30 | ...otherProps 31 | }) => ( 32 | 55 | ); 56 | 57 | RectNode.defaultProps = { 58 | ...nodesCommonPropTypes.defaults, 59 | fill: '#000', 60 | cornerRadius: 0, 61 | width: 0, 62 | height: 0, 63 | }; 64 | 65 | RectNode.propTypes = { 66 | ...nodesCommonPropTypes.definitions, 67 | x: PropTypes.number.isRequired, 68 | y: PropTypes.number.isRequired, 69 | annotationEvents: PropTypes.instanceOf(Object).isRequired, 70 | width: PropTypes.number, 71 | height: PropTypes.number, 72 | fill: PropTypes.string, 73 | cornerRadius: PropTypes.number, 74 | }; 75 | 76 | export default RectNode; 77 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/Layers/DesignLayer/AnnotationNodes/index.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React, { useMemo } from 'react'; 3 | 4 | /** Internal Dependencies */ 5 | import { useAnnotationEvents, useStore } from 'hooks'; 6 | import MemoizedAnnotation from './MemoizedAnnotation'; 7 | 8 | const AnnotationNodes = () => { 9 | const { annotations = {}, selectionsIds = [] } = useStore(); 10 | const annotationEvents = useAnnotationEvents(); 11 | 12 | return useMemo( 13 | () => 14 | Object.values(annotations).map((annotation) => ( 15 | 21 | )), 22 | [annotations, annotationEvents, selectionsIds], 23 | ); 24 | }; 25 | 26 | export default AnnotationNodes; 27 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/Layers/DesignLayer/PreviewGroup.jsx: -------------------------------------------------------------------------------- 1 | import React, { forwardRef } from 'react'; 2 | import { Group } from 'react-konva'; 3 | 4 | const PreviewGroup = (props, ref) => { 5 | return ; 6 | }; 7 | 8 | export default forwardRef(PreviewGroup); 9 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/Layers/DesignLayer/nodesCommonPropTypes.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | 3 | const nodesCommonPropTypes = { 4 | definitions: { 5 | id: PropTypes.string.isRequired, 6 | name: PropTypes.string.isRequired, 7 | rotation: PropTypes.number, 8 | scaleX: PropTypes.number, 9 | scaleY: PropTypes.number, 10 | stroke: PropTypes.string, 11 | strokeWidth: PropTypes.number, 12 | shadowOffsetX: PropTypes.number, 13 | shadowOffsetY: PropTypes.number, 14 | shadowBlur: PropTypes.number, 15 | shadowColor: PropTypes.string, 16 | shadowOpacity: PropTypes.number, 17 | opacity: PropTypes.number, 18 | }, 19 | defaults: { 20 | rotation: 0, 21 | scaleX: 1, 22 | scaleY: 1, 23 | stroke: undefined, 24 | strokeWidth: undefined, 25 | shadowOffsetX: undefined, 26 | shadowOffsetY: undefined, 27 | shadowBlur: undefined, 28 | shadowColor: undefined, 29 | shadowOpacity: undefined, 30 | opacity: 1, 31 | }, 32 | }; 33 | 34 | export default nodesCommonPropTypes; 35 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/Layers/TransformersLayer/index.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import { Layer } from 'react-konva'; 4 | 5 | /** Internal Dependencies */ 6 | import { useStore } from 'hooks'; 7 | import { TOOLS_IDS, TRANSFORMERS_LAYER_ID } from 'utils/constants'; 8 | import CropTransformer from './CropTransformer'; 9 | import NodesTransformer from './NodesTransformer'; 10 | 11 | const TransformersLayer = () => { 12 | const { toolId, shownImageDimensions } = useStore(); 13 | 14 | return ( 15 | 20 | 21 | {toolId === TOOLS_IDS.CROP && } 22 | 23 | ); 24 | }; 25 | 26 | export default TransformersLayer; 27 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/Layers/index.js: -------------------------------------------------------------------------------- 1 | export { default as DesignLayer } from './DesignLayer'; 2 | 3 | export { default as TransformersLayer } from './TransformersLayer'; 4 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/MainCanvas/MainCanvas.styled.js: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import { Stage } from 'react-konva'; 3 | import styled from 'styled-components'; 4 | 5 | const CanvasContainer = styled.div` 6 | width: 100%; 7 | position: relative; 8 | // backup for flex-grow, 94px, 12px = toolsbar's maxheight, app container padding. 9 | height: calc(100% - 112px - 16px); 10 | overflow: hidden; 11 | min-height: 250px; 12 | padding: 16px; 13 | flex-grow: 1; 14 | `; 15 | 16 | const StyledOrignalImage = styled.img` 17 | max-width: 98%; 18 | max-height: 98%; 19 | box-shadow: 0 0 0 5px rgba(0, 0, 0, 0.1); 20 | position: absolute; 21 | top: 50%; 22 | left: 50%; 23 | transform: translate(-50%, -50%); 24 | z-index: 2; 25 | `; 26 | 27 | const StyledCanvasNode = styled(Stage)` 28 | outline: none; 29 | background: ${({ theme }) => theme.palette['bg-hover']}; 30 | `; 31 | 32 | export { CanvasContainer, StyledOrignalImage, StyledCanvasNode }; 33 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/NodeControls/NodeControls.styled.js: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import styled from 'styled-components'; 3 | 4 | const StyledNodeControls = styled.div( 5 | ({ theme, top, left }) => ` 6 | position: absolute; 7 | z-index: 1; 8 | background: ${theme.palette['bg-secondary']}; 9 | border-radius: 2px; 10 | display: flex; 11 | align-items: center; 12 | justify-content: center; 13 | box-shadow: 0px 1px 2px ${theme.palette['light-shadow']}; 14 | top: ${(top || 0) + 8}px; 15 | left: ${(left || 0) + 4}px; 16 | transform: translateX(-50%); 17 | height: 32px; 18 | `, 19 | ); 20 | 21 | export { StyledNodeControls }; 22 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/Tabs/TabItem.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React, { useCallback, memo } from 'react'; 3 | import PropTypes from 'prop-types'; 4 | 5 | /** Internal Dependencies */ 6 | import { StyledTabItem, StyledTabItemLabel } from './Tabs.styled'; 7 | 8 | const TabItem = ({ id, label, Icon, isSelected, onClick }) => { 9 | const handleClick = useCallback(() => { 10 | if (typeof onClick === 'function') { 11 | onClick(id); 12 | } 13 | }, [id]); 14 | 15 | return ( 16 | 21 | 22 | {label && ( 23 | 24 | {label} 25 | 26 | )} 27 | 28 | ); 29 | }; 30 | 31 | TabItem.defaultProps = { 32 | isSelected: false, 33 | onClick: undefined, 34 | label: undefined, 35 | }; 36 | 37 | TabItem.propTypes = { 38 | id: PropTypes.string.isRequired, 39 | label: PropTypes.string, 40 | Icon: PropTypes.oneOfType([ 41 | PropTypes.node, 42 | PropTypes.func, 43 | PropTypes.instanceOf(Object), 44 | ]).isRequired, 45 | onClick: PropTypes.func, 46 | isSelected: PropTypes.bool, 47 | }; 48 | 49 | export default memo(TabItem); 50 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/Tabs/Tabs.constants.js: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import { 3 | FineTune, 4 | Annotate, 5 | CropFrame, 6 | ImageFilters, 7 | Watermark, 8 | Resize, 9 | } from '@scaleflex/icons'; 10 | 11 | /** Internal Dependencies */ 12 | import { TABS_IDS } from 'utils/constants'; 13 | 14 | export const AVAILABLE_TABS = [ 15 | { 16 | id: TABS_IDS.ADJUST, 17 | labelKey: 'adjustTab', 18 | icon: CropFrame, 19 | }, 20 | { 21 | id: TABS_IDS.FINETUNE, 22 | labelKey: 'finetuneTab', 23 | icon: FineTune, 24 | }, 25 | { 26 | id: TABS_IDS.FILTERS, 27 | labelKey: 'filtersTab', 28 | icon: ImageFilters, 29 | hideFn: ({ useCloudimage }) => useCloudimage, 30 | }, 31 | { 32 | id: TABS_IDS.WATERMARK, 33 | labelKey: 'watermarkTab', 34 | icon: Watermark, 35 | }, 36 | { 37 | id: TABS_IDS.ANNOTATE, 38 | labelKey: 'annotateTabLabel', 39 | icon: Annotate, 40 | hideFn: ({ useCloudimage }) => useCloudimage, 41 | }, 42 | { 43 | id: TABS_IDS.RESIZE, 44 | labelKey: 'resizeTab', 45 | icon: Resize, 46 | }, 47 | ]; 48 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/Tabs/Tabs.styled.js: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import styled from 'styled-components'; 3 | import Label from '@scaleflex/ui/core/label'; 4 | import { Color as PC } from '@scaleflex/ui/utils/types/palette'; 5 | import { FontVariant as FV } from '@scaleflex/ui/utils/types/typography'; 6 | 7 | const StyledTabItem = styled.div( 8 | ({ theme }) => ` 9 | width: 72px; 10 | min-height: 66px; 11 | padding: 4px 2px; 12 | border-radius: 4px; 13 | display: flex; 14 | flex-direction: column; 15 | gap: 6px; 16 | background: ${theme.palette[PC.BackgroundStateless]}; 17 | align-items: center; 18 | justify-content: center; 19 | 20 | [data-phone='true'] & { 21 | margin-bottom: 0; 22 | height: 50px; 23 | border-radius: 0; 24 | } 25 | 26 | svg { 27 | color: ${theme.palette[PC.IconsPrimary]}; 28 | } 29 | 30 | &, 31 | * { 32 | cursor: pointer; 33 | } 34 | 35 | &:hover { 36 | background: ${theme.palette['bg-primary-active']}; 37 | } 38 | 39 | &[aria-selected='true'] { 40 | background: ${theme.palette['bg-primary-active']}; 41 | 42 | * { 43 | color: ${theme.palette['accent-primary-active']}; 44 | } 45 | } 46 | `, 47 | ); 48 | 49 | const StyledTabItemLabel = styled(Label)( 50 | ({ theme }) => ` 51 | color: ${theme.palette[PC.TextPrimary]}; 52 | ${theme.typography.font[FV.LabelSmall]}; 53 | font-size: 12px; 54 | line-height: 14px; 55 | 56 | span { 57 | white-space: normal; 58 | } 59 | 60 | [data-phone='true'] & { 61 | font-size: 10px; 62 | } 63 | `, 64 | ); 65 | 66 | export { StyledTabItem, StyledTabItemLabel }; 67 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/TabsDrawer/index.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | import { 5 | DrawerBody, 6 | DrawerHeader, 7 | DrawerList, 8 | } from '@scaleflex/ui/core/drawer'; 9 | import { Menu } from '@scaleflex/icons'; 10 | import { Button } from '@scaleflex/ui/core'; 11 | 12 | /** Internal Dependencies */ 13 | import { useStore } from 'hooks'; 14 | import { StyledDrawer } from 'components/App/App.styled'; 15 | import Tabs from 'components/Tabs'; 16 | 17 | const TabsDrawer = ({ toggleMainMenu }) => { 18 | const { t, showTabsMenu } = useStore(); 19 | 20 | return ( 21 | toggleMainMenu(false)} 25 | disablePortal 26 | > 27 | 28 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | ); 43 | }; 44 | 45 | TabsDrawer.defaultProps = { 46 | toggleMainMenu: () => {}, 47 | }; 48 | 49 | TabsDrawer.propTypes = { 50 | toggleMainMenu: PropTypes.func, 51 | }; 52 | 53 | export default TabsDrawer; 54 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/ToolsBar/ToolsBarItemButton.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React, { memo } from 'react'; 3 | import PropTypes from 'prop-types'; 4 | 5 | /** Internal Dependencies */ 6 | import { usePhoneScreen } from 'hooks'; 7 | import { 8 | StyledToolsBarItemButton, 9 | StyledToolsBarItemButtonLabel, 10 | } from './ToolsBar.styled'; 11 | 12 | const ToolsBarItemButton = ({ 13 | id, 14 | label, 15 | onClick, 16 | Icon, 17 | isSelected, 18 | children, 19 | className, 20 | }) => { 21 | const isPhoneScreen = usePhoneScreen(320); 22 | 23 | const handleClick = (e) => { 24 | onClick(id, e); 25 | }; 26 | 27 | return ( 28 | 34 | 35 | {label && ( 36 | 37 | {label} 38 | 39 | )} 40 | {children} 41 | 42 | ); 43 | }; 44 | 45 | ToolsBarItemButton.defaultProps = { 46 | isSelected: false, 47 | id: undefined, 48 | children: null, 49 | label: '', 50 | }; 51 | 52 | ToolsBarItemButton.propTypes = { 53 | children: PropTypes.node, 54 | id: PropTypes.string, 55 | label: PropTypes.string, 56 | onClick: PropTypes.func.isRequired, 57 | className: PropTypes.string.isRequired, 58 | isSelected: PropTypes.bool, 59 | Icon: PropTypes.oneOfType([ 60 | PropTypes.node, 61 | PropTypes.func, 62 | PropTypes.instanceOf(Object), 63 | ]).isRequired, 64 | }; 65 | 66 | export default memo(ToolsBarItemButton); 67 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/ToolsBar/ToolsBarItemOptionsWrapper.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | 5 | /** Internal Dependencies */ 6 | import { StyledToolsBarItemOptionsWrapper } from './ToolsBar.styled'; 7 | 8 | const ToolsBarItemOptionsWrapper = ({ children, isPhoneScreen }) => ( 9 | 14 | {children} 15 | 16 | ); 17 | 18 | ToolsBarItemOptionsWrapper.defaultProps = { 19 | children: undefined, 20 | isPhoneScreen: false, 21 | }; 22 | 23 | ToolsBarItemOptionsWrapper.propTypes = { 24 | children: PropTypes.node, 25 | isPhoneScreen: PropTypes.bool, 26 | }; 27 | 28 | export default ToolsBarItemOptionsWrapper; 29 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/Topbar/BackButton.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import { Button } from '@scaleflex/ui/core'; 4 | import ArrowLeftOutline from '@scaleflex/icons/arrow-left-outline'; 5 | 6 | /** Internal Dependencies */ 7 | import { usePhoneScreen, useStore } from 'hooks'; 8 | import { StyledBackButtonLabel } from './Topbar.styled'; 9 | import ConfirmationModal from './ConfirmationModal'; 10 | 11 | const BackButton = () => { 12 | const { t } = useStore(); 13 | const isPhone = usePhoneScreen(); 14 | 15 | return ( 16 | 17 | 25 | 26 | ); 27 | }; 28 | 29 | export default BackButton; 30 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/Topbar/CloseButton.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import CrossOutline from '@scaleflex/icons/cross-outline'; 4 | 5 | /** Internal Dependencies */ 6 | import { useStore } from 'hooks'; 7 | import Separator from 'components/common/Separator'; 8 | import { StyledCloseButton } from './Topbar.styled'; 9 | import ConfirmationModal from './ConfirmationModal'; 10 | 11 | const CloseButton = () => { 12 | const { 13 | config: { onClose }, 14 | } = useStore(); 15 | 16 | if (typeof onClose !== 'function') { 17 | return null; 18 | } 19 | 20 | return ( 21 | <> 22 | 23 | 24 | 29 | 30 | 31 | 32 | 33 | ); 34 | }; 35 | 36 | export default CloseButton; 37 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/Topbar/RedoButton.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React, { useCallback } from 'react'; 3 | import PropTypes from 'prop-types'; 4 | import Redo from '@scaleflex/icons/redo'; 5 | 6 | /** Internal Dependencies */ 7 | import { REDO } from 'actions'; 8 | import { useStore } from 'hooks'; 9 | import { StyledHistoryButton } from './Topbar.styled'; 10 | 11 | const RedoButton = ({ margin }) => { 12 | const { dispatch, hasRedo = false, t } = useStore(); 13 | const dispatchRedo = useCallback(() => { 14 | dispatch({ type: REDO }); 15 | }, []); 16 | 17 | return ( 18 | 27 | 28 | 29 | ); 30 | }; 31 | 32 | RedoButton.defaultProps = { 33 | margin: undefined, 34 | }; 35 | 36 | RedoButton.propTypes = { 37 | margin: PropTypes.string, 38 | }; 39 | 40 | export default RedoButton; 41 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/Topbar/ResetButton.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | import Reset from '@scaleflex/icons/reset'; 5 | 6 | /** Internal Dependencies */ 7 | import { useStore } from 'hooks'; 8 | import { StyledHistoryButton } from './Topbar.styled'; 9 | import ConfirmationModal from './ConfirmationModal'; 10 | 11 | const ResetButton = ({ margin }) => { 12 | const { isResetted = true, feedback, t } = useStore(); 13 | 14 | const isBlockerError = feedback.duration === 0; 15 | 16 | return ( 17 | 18 | 26 | 27 | 28 | 29 | ); 30 | }; 31 | 32 | ResetButton.defaultProps = { 33 | margin: undefined, 34 | }; 35 | 36 | ResetButton.propTypes = { 37 | margin: PropTypes.string, 38 | }; 39 | 40 | export default ResetButton; 41 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/Topbar/Topbar.constants.js: -------------------------------------------------------------------------------- 1 | export const ZOOM_FACTORS_PRESETS = [ 2 | { 3 | labelKey: 'fitSize', 4 | factor: 'fit', 5 | }, 6 | { 7 | labelKey: 'actualSize', 8 | factor: 1, 9 | }, 10 | { 11 | label: '25%', 12 | factor: 0.25, 13 | }, 14 | { 15 | label: '50%', 16 | factor: 0.5, 17 | }, 18 | { 19 | label: '75%', 20 | factor: 0.75, 21 | }, 22 | { 23 | label: '125%', 24 | factor: 1.25, 25 | }, 26 | { 27 | label: '170%', 28 | factor: 1.7, 29 | }, 30 | { 31 | label: '300%', 32 | factor: 3, 33 | }, 34 | { 35 | label: '500%', 36 | factor: 5, 37 | }, 38 | { 39 | label: '1000%', 40 | factor: 10, 41 | }, 42 | ]; 43 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/Topbar/UndoButton.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React, { useCallback } from 'react'; 3 | import PropTypes from 'prop-types'; 4 | import Undo from '@scaleflex/icons/undo'; 5 | 6 | /** Internal Dependencies */ 7 | import { UNDO } from 'actions'; 8 | import { useStore } from 'hooks'; 9 | import { StyledHistoryButton } from './Topbar.styled'; 10 | 11 | const UndoButton = ({ margin }) => { 12 | const { dispatch, hasUndo = false, t, feedback } = useStore(); 13 | const isBlockerError = feedback.duration === 0; 14 | const dispatchUndo = useCallback(() => { 15 | dispatch({ type: UNDO }); 16 | }, []); 17 | 18 | return ( 19 | 28 | 29 | 30 | ); 31 | }; 32 | 33 | UndoButton.defaultProps = { 34 | margin: undefined, 35 | }; 36 | 37 | UndoButton.propTypes = { 38 | margin: PropTypes.string, 39 | }; 40 | 41 | export default UndoButton; 42 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/common/AnnotationOptions/AnnotationOptions.constants.js: -------------------------------------------------------------------------------- 1 | /** Internal Dependencies */ 2 | import { POSITIONS } from 'utils/constants'; 3 | 4 | export const AVAILABLE_POSITIONS = Object.values(POSITIONS); 5 | 6 | export const posCssRotateDegFromRightSide = { 7 | [POSITIONS.TOP_LEFT]: -145, 8 | [POSITIONS.TOP_CENTER]: -90, 9 | [POSITIONS.TOP_RIGHT]: -45, 10 | [POSITIONS.MIDDLE_LEFT]: 180, 11 | [POSITIONS.MIDDLE_CENTER]: 0, 12 | [POSITIONS.MIDDLE_RIGHT]: 0, 13 | [POSITIONS.BOTTOM_LEFT]: 135, 14 | [POSITIONS.BOTTOM_CENTER]: 90, 15 | [POSITIONS.BOTTOM_RIGHT]: 45, 16 | }; 17 | 18 | export const POPPABLE_OPTIONS = { 19 | OPACITY: 'opacity', 20 | STROKE: 'stroke', 21 | SHADOW: 'shadow', 22 | POSITION: 'position', 23 | }; 24 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/common/AnnotationOptions/OpacityField.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | 5 | /** Internal Dependencies */ 6 | import restrictNumber from 'utils/restrictNumber'; 7 | import { Label } from '@scaleflex/ui/core'; 8 | import { 9 | StyledSpacedOptionFields, 10 | StyledIconLabel, 11 | StyledOptionPopupContent, 12 | } from './AnnotationOptions.styled'; 13 | import Slider from '../Slider'; 14 | 15 | const MIN_PERCENTANGE = 0; 16 | const MAX_PERCENTANGE = 1; 17 | 18 | const OpacityField = ({ annotation, updateAnnotation, t }) => { 19 | const { opacity } = annotation; 20 | const opacityValue = Math.round(opacity * 100); 21 | 22 | const changeOpacity = (newOpactiy) => { 23 | updateAnnotation({ 24 | opacity: restrictNumber( 25 | newOpactiy / 100, 26 | MIN_PERCENTANGE, 27 | MAX_PERCENTANGE, 28 | ), 29 | }); 30 | }; 31 | 32 | return ( 33 | 34 | 35 | 36 | 42 | {`${opacityValue}%`} 43 | 44 | 45 | ); 46 | }; 47 | 48 | OpacityField.propTypes = { 49 | annotation: PropTypes.instanceOf(Object).isRequired, 50 | t: PropTypes.func.isRequired, 51 | updateAnnotation: PropTypes.func.isRequired, 52 | }; 53 | 54 | export default OpacityField; 55 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/common/AnnotationOptions/StrokeFields.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | 5 | /** Internal Dependencies */ 6 | import restrictNumber from 'utils/restrictNumber'; 7 | import ColorInput from 'components/common/ColorInput'; 8 | import { StyledSpacedOptionFields } from './AnnotationOptions.styled'; 9 | import Slider from '../Slider'; 10 | 11 | const MIN_PERCENTANGE = 0; 12 | const MAX_PERCENTANGE = 100; 13 | 14 | const StrokeFields = ({ annotation, updateAnnotation }) => { 15 | const { stroke, strokeWidth } = annotation; 16 | 17 | const changeStrokeWidth = (newStrokeWidth) => { 18 | updateAnnotation({ 19 | strokeWidth: restrictNumber( 20 | newStrokeWidth, 21 | MIN_PERCENTANGE, 22 | MAX_PERCENTANGE, 23 | ), 24 | }); 25 | }; 26 | 27 | const changeStrokeColor = (newStrokeColor) => { 28 | updateAnnotation({ stroke: newStrokeColor }); 29 | }; 30 | 31 | return ( 32 | 33 | 39 | 44 | 45 | ); 46 | }; 47 | 48 | StrokeFields.propTypes = { 49 | annotation: PropTypes.instanceOf(Object).isRequired, 50 | updateAnnotation: PropTypes.func.isRequired, 51 | }; 52 | 53 | export default StrokeFields; 54 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/common/ButtonWithMenu/ButtonWithMenu.styled.js: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import styled from 'styled-components'; 3 | import Button from '@scaleflex/ui/core/button'; 4 | import { Color as PC } from '@scaleflex/ui/utils/types/palette/color'; 5 | import { Menu, MenuItem, MenuItemIcon } from '@scaleflex/ui/core'; 6 | 7 | const StyledButtonWrapper = styled.div` 8 | display: flex; 9 | align-items: center; 10 | margin-left: ${({ noMargin }) => (noMargin ? '0' : '12px')}; 11 | flex-shrink: 0; 12 | `; 13 | 14 | const StyledMainButton = styled(Button)` 15 | flex-grow: 1; 16 | justify-content: center; 17 | align-items: center; 18 | `; 19 | 20 | const StyledMenu = styled(Menu)` 21 | padding: 8px; 22 | background-color: ${({ theme: { palette } }) => 23 | palette[PC.BackgroundStateless]}; 24 | `; 25 | 26 | const StyledMenuItem = styled(MenuItem)` 27 | border-radius: 4px; 28 | `; 29 | 30 | const StyledMenuIcon = styled(MenuItemIcon)` 31 | display: flex; 32 | align-items: center; 33 | `; 34 | 35 | export { 36 | StyledButtonWrapper, 37 | StyledMainButton, 38 | StyledMenu, 39 | StyledMenuItem, 40 | StyledMenuIcon, 41 | }; 42 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/common/Carousel/Carousel.styled.js: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import styled, { css } from 'styled-components'; 3 | import { Color as PC } from '@scaleflex/ui/utils/types/palette'; 4 | 5 | const StyledCarouselWrapper = styled.div` 6 | max-width: 680px; 7 | min-width: 150px; 8 | position: relative; 9 | overflow: hidden; 10 | touch-action: pan-y pinch-zoom; 11 | `; 12 | 13 | const StyledCarousel = styled.ul` 14 | padding: 0; 15 | margin: 0; 16 | white-space: nowrap; 17 | overflow: hidden; 18 | `; 19 | 20 | const StyledCarouselItem = styled.li` 21 | padding: 4px; 22 | display: inline-block; 23 | list-style-type: none; 24 | user-select: none; 25 | `; 26 | 27 | const arrowsCommonStyles = css` 28 | position: absolute; 29 | top: 0; 30 | height: 100%; 31 | width: 60px; 32 | cursor: pointer; 33 | display: flex; 34 | align-items: center; 35 | justify-content: center; 36 | z-index: 1; 37 | 38 | svg { 39 | color: ${({ theme: { palette } }) => palette[PC.IconsSecondary]}; 40 | } 41 | `; 42 | 43 | const StyledPrevArrowWrapper = styled.div` 44 | ${arrowsCommonStyles} 45 | left: 0; 46 | justify-content: flex-start; 47 | background: linear-gradient( 48 | 90deg, 49 | #ffffff 1.56%, 50 | rgba(255, 255, 255, 0.89) 52.4%, 51 | rgba(255, 255, 255, 0.532165) 76.04%, 52 | rgba(255, 255, 255, 0) 100% 53 | ); 54 | `; 55 | 56 | const StyledNextArrowWrapper = styled.div` 57 | ${arrowsCommonStyles} 58 | right: 0; 59 | justify-content: flex-end; 60 | background: linear-gradient( 61 | 270deg, 62 | #ffffff 1.56%, 63 | rgba(255, 255, 255, 0.89) 52.4%, 64 | rgba(255, 255, 255, 0.532165) 76.04%, 65 | rgba(255, 255, 255, 0) 100% 66 | ); 67 | `; 68 | 69 | export { 70 | StyledCarouselWrapper, 71 | StyledCarousel, 72 | StyledCarouselItem, 73 | StyledPrevArrowWrapper, 74 | StyledNextArrowWrapper, 75 | }; 76 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/common/ColorInput/ColorInput.styled.js: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import styled from 'styled-components'; 3 | import { Color as PC } from '@scaleflex/ui/utils/types/palette'; 4 | 5 | const StyledPickerTrigger = styled.div.attrs(({ $color }) => ({ 6 | style: { 7 | background: 8 | $color === 'rgba(0,0,0,0)' 9 | ? 'repeating-conic-gradient(#5d6d7e 0% 25%, transparent 0% 50%) 50% / 8px 8px' 10 | : $color, 11 | }, 12 | }))` 13 | background: ${({ theme }) => theme.palette['icons-primary']}; 14 | border-radius: 4px; 15 | width: 32px; 16 | height: 32px; 17 | border: 1px solid ${({ theme }) => theme.palette[PC.BorderPrimaryStateless]}; 18 | cursor: pointer; 19 | box-sizing: border-box; 20 | `; 21 | 22 | export { StyledPickerTrigger }; 23 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/common/ColorPickerModal/ColorPickerModal.styled.js: -------------------------------------------------------------------------------- 1 | import { Modal, ModalActions as SfxModalActions } from '@scaleflex/ui/core'; 2 | import styled from 'styled-components'; 3 | import { Color as PC } from '@scaleflex/ui/utils/types/palette'; 4 | 5 | const ColorPickerModal = styled(Modal)` 6 | max-width: 350px; 7 | `; 8 | 9 | const ColorPickerWrap = styled.div` 10 | .SfxColorPicker-root { 11 | max-width: 100%; 12 | padding: 0; 13 | box-shadow: none; 14 | border: none; 15 | ${({ hideModalTitle }) => hideModalTitle && 'padding-top: 12px;'} 16 | } 17 | 18 | .SfxColorPicker-action { 19 | display: flex; 20 | gap: 12px; 21 | 22 | .SfxColorPicker-select { 23 | width: 100px; 24 | } 25 | .SfxInput-root { 26 | width: 190px !important; 27 | } 28 | } 29 | 30 | .SfxColorPicker-icon { 31 | color: ${({ theme: { palette } }) => palette[PC.IconsPrimary]}; 32 | } 33 | 34 | .SfxColorPicker-range-picker, 35 | .SfxColorPicker-bar-wrapper { 36 | width: 100%; 37 | } 38 | `; 39 | 40 | const ModalActions = styled(SfxModalActions)` 41 | gap: 12px; 42 | padding: 24px; 43 | 44 | .SfxButton-root { 45 | flex: 1; 46 | margin: 0; 47 | height: 40px; 48 | } 49 | `; 50 | 51 | const Styled = { 52 | ColorPickerModal, 53 | ColorPickerWrap, 54 | ModalActions, 55 | }; 56 | 57 | export default Styled; 58 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/common/HiddenUploadInput/HiddenUploadInput.styled.js: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import styled from 'styled-components'; 3 | 4 | const StyledHiddenUploadInput = styled.input` 5 | display: none; 6 | width: 1px; 7 | height: 1px; 8 | position: absolute; 9 | z-index: -1; 10 | `; 11 | 12 | export { StyledHiddenUploadInput }; 13 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/common/HiddenUploadInput/index.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React, { forwardRef } from 'react'; 3 | 4 | /** Internal Dependencies */ 5 | import { StyledHiddenUploadInput } from './HiddenUploadInput.styled'; 6 | 7 | const HiddenUploadInput = (props, ref) => { 8 | return ; 9 | }; 10 | 11 | export default forwardRef(HiddenUploadInput); 12 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/common/Modal/Modal.styled.js: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import styled, { css } from 'styled-components'; 3 | import modalTitle from '@scaleflex/ui/core/modal-title'; 4 | import { Modal, ModalActions } from '@scaleflex/ui/core'; 5 | import { Color as PC } from '@scaleflex/ui/utils/types/palette'; 6 | import { FontVariant as FV } from '@scaleflex/ui/utils/types/typography'; 7 | 8 | const StyledModal = styled(Modal)` 9 | width: ${({ width }) => width || '300px'}; 10 | max-width: unset; 11 | `; 12 | 13 | const StyledModalTitle = styled(modalTitle)( 14 | ({ theme, isWarning }) => css` 15 | padding-bottom: 0; 16 | 17 | .SfxModalTitle-Icon { 18 | background-color: ${isWarning && theme.palette[PC.Orange_0_1_Overlay]}; 19 | } 20 | 21 | .SfxModalTitle-LabelPrimary { 22 | margin-bottom: 24px; 23 | ${theme.typography.font[FV.TitleH3]}; 24 | } 25 | 26 | .SfxModalTitle-LabelSecondary { 27 | ${theme.typography.font[FV.TextLarge]}; 28 | text-align: center; 29 | } 30 | `, 31 | ); 32 | 33 | const StyledModalActions = styled(ModalActions)` 34 | gap: 12px; 35 | padding: 24px; 36 | 37 | .SfxButton-root { 38 | flex: 1; 39 | margin: 0; 40 | height: 40px; 41 | } 42 | `; 43 | 44 | export { StyledModal, StyledModalTitle, StyledModalActions }; 45 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/common/Separator/Separator.styled.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { Color as PC } from '@scaleflex/ui/utils/types/palette'; 3 | 4 | const StyledSeparator = styled.div` 5 | display: inline-block; 6 | height: ${(props) => props.height}; 7 | width: ${(props) => props.width}; 8 | border-radius: 1px; 9 | background: ${({ theme: { palette } }) => palette[PC.BordersSecondary]}; 10 | `; 11 | 12 | export { StyledSeparator }; 13 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/common/Separator/index.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | 5 | /** Internal Dependencies */ 6 | import { StyledSeparator } from './Separator.styled'; 7 | 8 | const Separator = ({ height, width }) => ( 9 | 10 | ); 11 | 12 | Separator.defaultProps = { 13 | height: '24px', 14 | width: '1px', 15 | }; 16 | 17 | Separator.propTypes = { 18 | height: PropTypes.string, 19 | width: PropTypes.string, 20 | }; 21 | 22 | export default Separator; 23 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/common/Slider/Slider.styled.js: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import styled from 'styled-components'; 3 | import Slider from '@scaleflex/ui/core/slider'; 4 | import { Color as PC } from '@scaleflex/ui/utils/types/palette'; 5 | 6 | const StyledSlider = styled(Slider)` 7 | width: ${({ width }) => width || '104px'}; 8 | max-width: ${({ width }) => width || '104px'}; 9 | user-select: none; 10 | padding: 0; 11 | margin-bottom: ${({ noMargin }) => (noMargin ? '' : '16px')}; 12 | 13 | .SfxSlider-thumb { 14 | background-color: ${({ theme: { palette } }) => 15 | palette[PC.AccentStateless]}; 16 | } 17 | 18 | .SfxSlider-Track { 19 | height: 2px; 20 | color: ${({ theme: { palette } }) => palette[PC.AccentStateless]}; 21 | } 22 | 23 | .SfxSlider-rail { 24 | height: 2px; 25 | background-color: ${({ theme: { palette } }) => palette[PC.BordersItem]}; 26 | } 27 | `; 28 | 29 | export { StyledSlider }; 30 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/common/Slider/index.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | 5 | /** Internal Dependencies */ 6 | import { StyledSlider } from './Slider.styled'; 7 | 8 | const Slider = ({ onChange, ...props }) => { 9 | return ( 10 | (onChange ? onChange(val) : undefined)} 13 | hideAnnotation 14 | labelTooltip="auto" 15 | {...props} 16 | /> 17 | ); 18 | }; 19 | 20 | Slider.propTypes = { 21 | onChange: PropTypes.func.isRequired, 22 | }; 23 | 24 | export default Slider; 25 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/common/Spinner/Spinner.styled.js: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import { Loading } from '@scaleflex/icons'; 3 | import styled, { keyframes } from 'styled-components'; 4 | import { Color as PC } from '@scaleflex/ui/utils/types/palette'; 5 | 6 | const spin = keyframes` 7 | to { transform: rotate(360deg); } 8 | `; 9 | 10 | const StyledSpinnerWrapper = styled.div` 11 | background: ${({ theme: { palette } }) => palette[PC.BackgroundStateless]}; 12 | display: flex; 13 | align-items: center; 14 | justify-content: center; 15 | position: absolute; 16 | z-index: 11111; 17 | top: 0; 18 | bottom: 0; 19 | right: 0; 20 | left: 0; 21 | flex-direction: column; 22 | user-select: none; 23 | `; 24 | 25 | const StyledSpinner = styled(Loading)` 26 | animation: ${spin} 1.2s infinite; 27 | `; 28 | 29 | export { StyledSpinnerWrapper, StyledSpinner }; 30 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/common/Spinner/index.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | 5 | /** Internal Dependencies */ 6 | import { Color as PC } from '@scaleflex/ui/utils/types/palette'; 7 | 8 | import { StyledSpinnerWrapper, StyledSpinner } from './Spinner.styled'; 9 | 10 | const Spinner = ({ theme }) => { 11 | return ( 12 | 13 | 14 | 15 | ); 16 | }; 17 | 18 | Spinner.defaultProps = { 19 | theme: {}, 20 | }; 21 | 22 | Spinner.propTypes = { 23 | theme: PropTypes.instanceOf(Object), 24 | }; 25 | 26 | export default Spinner; 27 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Arrow/ArrowButton.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | import { ArrowTool as ArrowIcon } from '@scaleflex/icons/arrow-tool'; 5 | 6 | /** Internal Dependencies */ 7 | import ToolsBarItemButton from 'components/ToolsBar/ToolsBarItemButton'; 8 | import { TOOLS_IDS } from 'utils/constants'; 9 | 10 | const ArrowButton = ({ selectTool, isSelected, t }) => ( 11 | 19 | ); 20 | 21 | ArrowButton.defaultProps = { 22 | isSelected: false, 23 | }; 24 | 25 | ArrowButton.propTypes = { 26 | selectTool: PropTypes.func.isRequired, 27 | isSelected: PropTypes.bool, 28 | t: PropTypes.func.isRequired, 29 | }; 30 | 31 | export default ArrowButton; 32 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Arrow/ArrowOptions.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | 5 | /** Internal Dependencies */ 6 | import { useAnnotation } from 'hooks'; 7 | import { TOOLS_IDS } from 'utils/constants'; 8 | import AnnotationOptions from 'components/common/AnnotationOptions'; 9 | 10 | const ArrowOptions = ({ t }) => { 11 | const [arrow, saveArrow] = useAnnotation({ 12 | name: TOOLS_IDS.ARROW, 13 | }); 14 | 15 | return ( 16 | 24 | ); 25 | }; 26 | 27 | ArrowOptions.propTypes = { 28 | t: PropTypes.func.isRequired, 29 | }; 30 | 31 | export default ArrowOptions; 32 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Arrow/index.js: -------------------------------------------------------------------------------- 1 | export { default as ArrowButton } from './ArrowButton'; 2 | 3 | export { default as ArrowOptions } from './ArrowOptions'; 4 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Blur/Blur.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | import { Blur as BlurIcon } from '@scaleflex/icons/blur'; 5 | 6 | /** Internal Dependencies */ 7 | import ToolsBarItemButton from 'components/ToolsBar/ToolsBarItemButton'; 8 | import { TOOLS_IDS } from 'utils/constants'; 9 | 10 | const Blur = ({ selectTool, isSelected, t }) => ( 11 | 19 | ); 20 | 21 | Blur.defaultProps = { 22 | isSelected: false, 23 | }; 24 | 25 | Blur.propTypes = { 26 | selectTool: PropTypes.func.isRequired, 27 | isSelected: PropTypes.bool, 28 | t: PropTypes.func.isRequired, 29 | }; 30 | 31 | export default Blur; 32 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Blur/index.js: -------------------------------------------------------------------------------- 1 | export { default as Blur } from './Blur'; 2 | 3 | export { default as BlurOptions } from './BlurOptions'; 4 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Brightness/Brightness.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | import { Brightness as BrightnessIcon } from '@scaleflex/icons/brightness'; 5 | 6 | /** Internal Dependencies */ 7 | import ToolsBarItemButton from 'components/ToolsBar/ToolsBarItemButton'; 8 | import { TOOLS_IDS } from 'utils/constants'; 9 | 10 | const Brightness = ({ selectTool, isSelected, t }) => ( 11 | 19 | ); 20 | 21 | Brightness.defaultProps = { 22 | isSelected: false, 23 | }; 24 | 25 | Brightness.propTypes = { 26 | selectTool: PropTypes.func.isRequired, 27 | isSelected: PropTypes.bool, 28 | t: PropTypes.func.isRequired, 29 | }; 30 | 31 | export default Brightness; 32 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Brightness/index.js: -------------------------------------------------------------------------------- 1 | export { default as Brightness } from './Brightness'; 2 | 3 | export { default as BrightnessOptions } from './BrightnessOptions'; 4 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Contrast/Contrast.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | import { Contrast as ContrastIcon } from '@scaleflex/icons/contrast'; 5 | 6 | /** Internal Dependencies */ 7 | import ToolsBarItemButton from 'components/ToolsBar/ToolsBarItemButton'; 8 | import { TOOLS_IDS } from 'utils/constants'; 9 | 10 | const Contrast = ({ selectTool, isSelected, t }) => ( 11 | 19 | ); 20 | 21 | Contrast.defaultProps = { 22 | isSelected: false, 23 | }; 24 | 25 | Contrast.propTypes = { 26 | selectTool: PropTypes.func.isRequired, 27 | isSelected: PropTypes.bool, 28 | t: PropTypes.func.isRequired, 29 | }; 30 | 31 | export default Contrast; 32 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Contrast/index.js: -------------------------------------------------------------------------------- 1 | export { default as Contrast } from './Contrast'; 2 | 3 | export { default as ContrastOptions } from './ContrastOptions'; 4 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Crop/Crop.constants.js: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import Custom from '@scaleflex/icons/custom'; 3 | import Ellipse from '@scaleflex/icons/ellipse'; 4 | import Landscape from '@scaleflex/icons/landscape'; 5 | import Portrait from '@scaleflex/icons/portrait'; 6 | import ImageOutline from '@scaleflex/icons/image-outline'; 7 | 8 | /** Internal Dependencies */ 9 | import { CUSTOM_CROP, ELLIPSE_CROP, ORIGINAL_CROP } from 'utils/constants'; 10 | import toPrecisedFloat from 'utils/toPrecisedFloat'; 11 | 12 | export const DEFAULT_CROP_PRESETS = [ 13 | { 14 | titleKey: 'custom', 15 | ratio: CUSTOM_CROP, 16 | icon: Custom, 17 | hide: ({ lockCropAreaAt } = {}) => lockCropAreaAt, 18 | }, 19 | { 20 | titleKey: 'original', 21 | ratio: ORIGINAL_CROP, 22 | icon: ImageOutline, 23 | }, 24 | { 25 | titleKey: 'landscape', 26 | descriptionKey: '16:9', 27 | ratio: toPrecisedFloat(16 / 9), 28 | icon: Landscape, 29 | }, 30 | { 31 | titleKey: 'portrait', 32 | descriptionKey: '9:16', 33 | ratio: toPrecisedFloat(9 / 16), 34 | icon: Portrait, 35 | }, 36 | { 37 | titleKey: 'ellipse', 38 | ratio: ELLIPSE_CROP, 39 | icon: Ellipse, 40 | }, 41 | ]; 42 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Crop/Crop.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React, { useState } from 'react'; 3 | import PropTypes from 'prop-types'; 4 | import { Crop as CropIcon } from '@scaleflex/icons/crop'; 5 | 6 | /** Internal Dependencies */ 7 | import { useStore } from 'hooks'; 8 | import ToolsBarItemButton from 'components/ToolsBar/ToolsBarItemButton'; 9 | import { TOOLS_IDS } from 'utils/constants'; 10 | import { StyledToolsBarItemButtonLabel } from 'components/ToolsBar/ToolsBar.styled'; 11 | import CropPresetsOption from './CropPresetsOption'; 12 | 13 | const Crop = ({ selectTool, isSelected }) => { 14 | const { config, t } = useStore(); 15 | const [anchorEl, setAnchorEl] = useState(); 16 | 17 | const selectToolAndShowPresets = (toolId, e) => { 18 | selectTool(toolId); 19 | setAnchorEl(e.currentTarget); 20 | }; 21 | 22 | const closeCropPresets = () => { 23 | setAnchorEl(null); 24 | }; 25 | 26 | return ( 27 | 34 | {!config[TOOLS_IDS.CROP].noPresets ? ( 35 | 36 | ) : ( 37 | 38 | {t('cropTool')} 39 | 40 | )} 41 | 42 | ); 43 | }; 44 | 45 | Crop.defaultProps = { 46 | isSelected: false, 47 | }; 48 | 49 | Crop.propTypes = { 50 | selectTool: PropTypes.func.isRequired, 51 | isSelected: PropTypes.bool, 52 | }; 53 | 54 | export default Crop; 55 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Crop/index.js: -------------------------------------------------------------------------------- 1 | export { default as Crop } from './Crop'; 2 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Ellipse/EllipseButton.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | import { Ellipse as EllipseIcon } from '@scaleflex/icons/ellipse'; 5 | 6 | /** Internal Dependencies */ 7 | import ToolsBarItemButton from 'components/ToolsBar/ToolsBarItemButton'; 8 | import { TOOLS_IDS } from 'utils/constants'; 9 | 10 | const EllipseButton = ({ selectTool, isSelected, t }) => ( 11 | 19 | ); 20 | 21 | EllipseButton.defaultProps = { 22 | isSelected: false, 23 | }; 24 | 25 | EllipseButton.propTypes = { 26 | selectTool: PropTypes.func.isRequired, 27 | isSelected: PropTypes.bool, 28 | t: PropTypes.func.isRequired, 29 | }; 30 | 31 | export default EllipseButton; 32 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Ellipse/EllipseOptions.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | 5 | /** Internal Dependencies */ 6 | import { useAnnotation } from 'hooks'; 7 | import { TOOLS_IDS } from 'utils/constants'; 8 | import AnnotationOptions from 'components/common/AnnotationOptions'; 9 | 10 | const EllipseOptions = ({ t }) => { 11 | const [ellipse, saveEllipse] = useAnnotation({ 12 | name: TOOLS_IDS.ELLIPSE, 13 | }); 14 | 15 | return ( 16 | 22 | ); 23 | }; 24 | 25 | EllipseOptions.propTypes = { 26 | t: PropTypes.func.isRequired, 27 | }; 28 | export default EllipseOptions; 29 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Ellipse/index.js: -------------------------------------------------------------------------------- 1 | export { default as EllipseButton } from './EllipseButton'; 2 | 3 | export { default as EllipseOptions } from './EllipseOptions'; 4 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Filters/Filters.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | 4 | /** Internal Dependencies */ 5 | import { useFilter, useStore } from 'hooks'; 6 | import Carousel from 'components/common/Carousel'; 7 | import FilterItem from './FilterItem'; 8 | import { AVAILABLE_FILTERS } from './Filters.constants'; 9 | 10 | const style = { maxWidth: '100%', width: '100%' }; 11 | 12 | const Filters = () => { 13 | const { originalImage } = useStore(); 14 | const [appliedFilter, applyFilter] = useFilter(); 15 | 16 | return ( 17 | 18 | {AVAILABLE_FILTERS.map((filter) => ( 19 | 27 | ))} 28 | 29 | ); 30 | }; 31 | export default Filters; 32 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Filters/Filters.styled.js: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import Label from '@scaleflex/ui/core/label'; 3 | import { FontVariant as FV } from '@scaleflex/ui/utils/types/typography'; 4 | import { Color as PC } from '@scaleflex/ui/utils/types/palette'; 5 | import { Stage } from 'react-konva'; 6 | import styled, { css } from 'styled-components'; 7 | 8 | const StyledFilterItem = styled.div` 9 | display: inline-flex; 10 | flex-direction: column; 11 | align-items: center; 12 | justify-content: center; 13 | padding: 0px 2px; 14 | gap: 6px; 15 | cursor: pointer; 16 | border-radius: 2px; 17 | 18 | canvas { 19 | border-radius: 2px; 20 | } 21 | `; 22 | 23 | const FilterItemPreview = styled(Stage)` 24 | [aria-selected='true'] & { 25 | padding: 1px; 26 | border: 1px solid ${({ theme }) => theme.palette['accent-primary-active']}; 27 | border-radius: 2px; 28 | } 29 | `; 30 | 31 | const FilterItemLabel = styled(Label)( 32 | ({ theme }) => css` 33 | color: ${theme.palette[PC.TextPrimary]}; 34 | ${theme.typography.font[FV.LabelExtraSmallUp]}; 35 | 36 | [aria-selected='true'] & { 37 | color: ${theme.palette['accent-primary-active']}; 38 | } 39 | `, 40 | ); 41 | 42 | export { StyledFilterItem, FilterItemPreview, FilterItemLabel }; 43 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Filters/index.js: -------------------------------------------------------------------------------- 1 | export { default as Filters } from './Filters'; 2 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Flip/index.js: -------------------------------------------------------------------------------- 1 | export { default as FlipX } from './FlipX'; 2 | 3 | export { default as FlipY } from './FlipY'; 4 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/HSV/HSV.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | import { Saturation as SaturationIcon } from '@scaleflex/icons/saturation'; 5 | 6 | /** Internal Dependencies */ 7 | import ToolsBarItemButton from 'components/ToolsBar/ToolsBarItemButton'; 8 | import { TOOLS_IDS } from 'utils/constants'; 9 | 10 | const HSV = ({ selectTool, isSelected, t }) => ( 11 | 19 | ); 20 | 21 | HSV.defaultProps = { 22 | isSelected: false, 23 | }; 24 | 25 | HSV.propTypes = { 26 | selectTool: PropTypes.func.isRequired, 27 | isSelected: PropTypes.bool, 28 | t: PropTypes.func.isRequired, 29 | }; 30 | 31 | export default HSV; 32 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/HSV/index.js: -------------------------------------------------------------------------------- 1 | export { default as HSV } from './HSV'; 2 | 3 | export { default as HSVOptions } from './HSVOptions'; 4 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Image/Image.styled.js: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import styled from 'styled-components'; 3 | 4 | const StyledImagesGallery = styled.div` 5 | background: ${({ theme }) => theme.palette['bg-secondary']}; 6 | box-shadow: 0px 1px 2px ${({ theme }) => theme.palette['light-shadow']}; 7 | border-radius: 4px; 8 | padding: 8px; 9 | overflow-y: auto; 10 | max-height: 350px; 11 | max-width: 300px; 12 | `; 13 | 14 | const StyledImageWrapper = styled.div` 15 | display: inline-flex; 16 | align-items: center; 17 | justify-content: center; 18 | width: 60px; 19 | height: 60px; 20 | margin: 4px; 21 | padding: 4px; 22 | cursor: pointer; 23 | border-radius: 4px; 24 | border: 2px solid ${({ theme }) => theme.palette['bg-primary-active']}; 25 | user-select: none; 26 | 27 | :hover { 28 | border-color: ${({ theme }) => theme.palette['accent-primary-active']}; 29 | } 30 | 31 | img { 32 | width: 100%; 33 | height: 100%; 34 | object-fit: contain; 35 | } 36 | `; 37 | 38 | export { StyledImagesGallery, StyledImageWrapper }; 39 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Image/ImageButton.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | import { ImageOutline as ImageIcon } from '@scaleflex/icons/image-outline'; 5 | 6 | /** Internal Dependencies */ 7 | import ToolsBarItemButton from 'components/ToolsBar/ToolsBarItemButton'; 8 | import { TOOLS_IDS } from 'utils/constants'; 9 | 10 | const ImageButton = ({ selectTool, isSelected, t }) => ( 11 | 19 | ); 20 | 21 | ImageButton.defaultProps = { 22 | isSelected: false, 23 | }; 24 | 25 | ImageButton.propTypes = { 26 | selectTool: PropTypes.func.isRequired, 27 | isSelected: PropTypes.bool, 28 | t: PropTypes.func.isRequired, 29 | }; 30 | 31 | export default ImageButton; 32 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Image/ImageControls.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | 5 | /** Internal Dependencies */ 6 | import AnnotationOptions from 'components/common/AnnotationOptions'; 7 | 8 | const ImageControls = ({ image, saveImage, children, t }) => ( 9 | 16 | {children} 17 | 18 | ); 19 | 20 | ImageControls.defaultProps = { 21 | children: null, 22 | }; 23 | 24 | ImageControls.propTypes = { 25 | image: PropTypes.instanceOf(Object).isRequired, 26 | saveImage: PropTypes.func.isRequired, 27 | children: PropTypes.node, 28 | t: PropTypes.func.isRequired, 29 | }; 30 | 31 | export default ImageControls; 32 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Image/ImagesGallery.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | import Popper from '@scaleflex/ui/core/popper'; 5 | 6 | /** Internal Dependencies */ 7 | import { StyledImagesGallery, StyledImageWrapper } from './Image.styled'; 8 | 9 | const ImagesGallery = ({ gallery, anchorEl, onClose, onSelect }) => ( 10 | 18 | 19 | {gallery.map(({ originalUrl, previewUrl }) => ( 20 | onSelect(originalUrl)} 23 | > 24 | {previewUrl} 30 | 31 | ))} 32 | 33 | 34 | ); 35 | ImagesGallery.defaultProps = { 36 | gallery: [], 37 | anchorEl: null, 38 | }; 39 | 40 | ImagesGallery.propTypes = { 41 | onClose: PropTypes.func.isRequired, 42 | onSelect: PropTypes.func.isRequired, 43 | gallery: PropTypes.arrayOf(Object), 44 | anchorEl: PropTypes.instanceOf(Object), 45 | }; 46 | 47 | export default ImagesGallery; 48 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Image/index.js: -------------------------------------------------------------------------------- 1 | export { default as ImageButton } from './ImageButton'; 2 | 3 | export { default as ImageOptions } from './ImageOptions'; 4 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Line/LineButton.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | import Line from '@scaleflex/icons/line'; 5 | 6 | /** Internal Dependencies */ 7 | import ToolsBarItemButton from 'components/ToolsBar/ToolsBarItemButton'; 8 | import { TOOLS_IDS } from 'utils/constants'; 9 | 10 | const LineButton = ({ selectTool, isSelected, t }) => ( 11 | 19 | ); 20 | 21 | LineButton.defaultProps = { 22 | isSelected: false, 23 | }; 24 | 25 | LineButton.propTypes = { 26 | selectTool: PropTypes.func.isRequired, 27 | isSelected: PropTypes.bool, 28 | t: PropTypes.func.isRequired, 29 | }; 30 | 31 | export default LineButton; 32 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Line/LineOptions.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | 5 | /** Internal Dependencies */ 6 | import { useAnnotation } from 'hooks'; 7 | import { TOOLS_IDS } from 'utils/constants'; 8 | import AnnotationOptions from 'components/common/AnnotationOptions'; 9 | 10 | const LineOptions = ({ t }) => { 11 | const [line, saveLine] = useAnnotation({ 12 | name: TOOLS_IDS.LINE, 13 | }); 14 | 15 | return ( 16 | 24 | ); 25 | }; 26 | 27 | LineOptions.propTypes = { 28 | t: PropTypes.func.isRequired, 29 | }; 30 | 31 | export default LineOptions; 32 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Line/index.js: -------------------------------------------------------------------------------- 1 | export { default as LineButton } from './LineButton'; 2 | 3 | export { default as LineOptions } from './LineOptions'; 4 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Pen/PenButton.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | import { Annotation as PenIcon } from '@scaleflex/icons/annotation'; 5 | 6 | /** Internal Dependencies */ 7 | import ToolsBarItemButton from 'components/ToolsBar/ToolsBarItemButton'; 8 | import { TOOLS_IDS } from 'utils/constants'; 9 | 10 | const PenButton = ({ selectTool, isSelected, t }) => ( 11 | 19 | ); 20 | 21 | PenButton.defaultProps = { 22 | isSelected: false, 23 | }; 24 | 25 | PenButton.propTypes = { 26 | selectTool: PropTypes.func.isRequired, 27 | isSelected: PropTypes.bool, 28 | t: PropTypes.func.isRequired, 29 | }; 30 | 31 | export default PenButton; 32 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Pen/index.js: -------------------------------------------------------------------------------- 1 | export { default as PenButton } from './PenButton'; 2 | 3 | export { default as PenOptions } from './PenOptions'; 4 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Polygon/Polygon.constants.js: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import PolygonSides from '@scaleflex/icons/polygon-sides'; 3 | 4 | /** Internal Dependencies */ 5 | import PolygonSidesField from './PolygonSidesField'; 6 | 7 | export const SIDES_NUMBER = 'sides-number'; 8 | 9 | export const POLYGON_POPPABLE_OPTIONS = [ 10 | { 11 | titleKey: 'sides', 12 | name: SIDES_NUMBER, 13 | Icon: PolygonSides, 14 | }, 15 | ]; 16 | 17 | export const polygonOptionsPopupComponents = { 18 | [SIDES_NUMBER]: PolygonSidesField, 19 | }; 20 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Polygon/PolygonButton.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | import { Polygon as PolygonIcon } from '@scaleflex/icons/polygon'; 5 | 6 | /** Internal Dependencies */ 7 | import ToolsBarItemButton from 'components/ToolsBar/ToolsBarItemButton'; 8 | import { TOOLS_IDS } from 'utils/constants'; 9 | 10 | const PolygonButton = ({ selectTool, isSelected, t }) => ( 11 | 19 | ); 20 | 21 | PolygonButton.defaultProps = { 22 | isSelected: false, 23 | }; 24 | 25 | PolygonButton.propTypes = { 26 | selectTool: PropTypes.func.isRequired, 27 | isSelected: PropTypes.bool, 28 | t: PropTypes.func.isRequired, 29 | }; 30 | 31 | export default PolygonButton; 32 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Polygon/PolygonOptions.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | 5 | /** Internal Dependencies */ 6 | import { useAnnotation } from 'hooks'; 7 | import { TOOLS_IDS } from 'utils/constants'; 8 | import AnnotationOptions from 'components/common/AnnotationOptions'; 9 | import { 10 | polygonOptionsPopupComponents, 11 | POLYGON_POPPABLE_OPTIONS, 12 | } from './Polygon.constants'; 13 | 14 | const PolygonOptions = ({ t }) => { 15 | const [polygon, savePolygon] = useAnnotation({ 16 | name: TOOLS_IDS.POLYGON, 17 | }); 18 | 19 | return ( 20 | 29 | ); 30 | }; 31 | 32 | PolygonOptions.propTypes = { 33 | t: PropTypes.func.isRequired, 34 | }; 35 | 36 | export default PolygonOptions; 37 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Polygon/PolygonSidesField.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | import Label from '@scaleflex/ui/core/label'; 5 | 6 | /** InternalDependencies */ 7 | import { StyledSpacedOptionFields } from 'components/common/AnnotationOptions/AnnotationOptions.styled'; 8 | import restrictNumber from 'utils/restrictNumber'; 9 | import Slider from 'components/common/Slider'; 10 | 11 | const MIN_VALUE = 3; 12 | const MAX_VALUE = 25; 13 | 14 | const PolygonSidesField = ({ 15 | annotation: polygon, 16 | updateAnnotation: updatePolygon, 17 | t, 18 | }) => { 19 | const { sides } = polygon; 20 | 21 | const updateSidesNumber = (newSidesNumber) => { 22 | updatePolygon({ 23 | sides: restrictNumber(newSidesNumber, MIN_VALUE, MAX_VALUE), 24 | }); 25 | }; 26 | 27 | return ( 28 | 29 | 30 | 38 | 39 | ); 40 | }; 41 | 42 | PolygonSidesField.propTypes = { 43 | annotation: PropTypes.instanceOf(Object).isRequired, 44 | updateAnnotation: PropTypes.func.isRequired, 45 | t: PropTypes.func.isRequired, 46 | }; 47 | 48 | export default PolygonSidesField; 49 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Polygon/index.js: -------------------------------------------------------------------------------- 1 | export { default as PolygonButton } from './PolygonButton'; 2 | 3 | export { default as PolygonOptions } from './PolygonOptions'; 4 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Rect/Rect.constants.js: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import RadiusCorner from '@scaleflex/icons/radius-corner'; 3 | 4 | /** Internal Dependencies */ 5 | import RectCornerField from './RectCornerField'; 6 | 7 | export const CORNER_RADIUS = 'corner-radius'; 8 | 9 | export const RECT_POPPABLE_OPTIONS = [ 10 | { 11 | titleKey: 'cornerRadius', 12 | name: CORNER_RADIUS, 13 | Icon: RadiusCorner, 14 | }, 15 | ]; 16 | 17 | export const rectOptionsPopupComponents = { 18 | [CORNER_RADIUS]: RectCornerField, 19 | }; 20 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Rect/RectButton.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | import { CropLandscape as RectIcon } from '@scaleflex/icons/crop-landscape'; 5 | 6 | /** Internal Dependencies */ 7 | import ToolsBarItemButton from 'components/ToolsBar/ToolsBarItemButton'; 8 | import { TOOLS_IDS } from 'utils/constants'; 9 | 10 | const RectButton = ({ selectTool, isSelected, t }) => ( 11 | 19 | ); 20 | 21 | RectButton.defaultProps = { 22 | isSelected: false, 23 | }; 24 | 25 | RectButton.propTypes = { 26 | selectTool: PropTypes.func.isRequired, 27 | isSelected: PropTypes.bool, 28 | t: PropTypes.func.isRequired, 29 | }; 30 | 31 | export default RectButton; 32 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Rect/RectCornerField.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | import Label from '@scaleflex/ui/core/label'; 5 | 6 | /** InternalDependencies */ 7 | import { StyledSpacedOptionFields } from 'components/common/AnnotationOptions/AnnotationOptions.styled'; 8 | import restrictNumber from 'utils/restrictNumber'; 9 | import Slider from 'components/common/Slider'; 10 | 11 | const MIN_VALUE = 0; 12 | const MAX_VALUE = 150; 13 | 14 | const RectCornerField = ({ 15 | annotation: rect, 16 | updateAnnotation: updateRect, 17 | t, 18 | }) => { 19 | const { cornerRadius } = rect; 20 | 21 | const updateCornerRadius = (newCornerRadius) => { 22 | updateRect({ 23 | cornerRadius: restrictNumber(newCornerRadius, MIN_VALUE, MAX_VALUE), 24 | }); 25 | }; 26 | 27 | return ( 28 | 29 | 30 | 38 | 39 | ); 40 | }; 41 | 42 | RectCornerField.propTypes = { 43 | annotation: PropTypes.instanceOf(Object).isRequired, 44 | updateAnnotation: PropTypes.func.isRequired, 45 | t: PropTypes.func.isRequired, 46 | }; 47 | 48 | export default RectCornerField; 49 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Rect/RectOptions.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | 5 | /** Internal Dependencies */ 6 | import { useAnnotation } from 'hooks'; 7 | import { TOOLS_IDS } from 'utils/constants'; 8 | import AnnotationOptions from 'components/common/AnnotationOptions'; 9 | import { 10 | rectOptionsPopupComponents, 11 | RECT_POPPABLE_OPTIONS, 12 | } from './Rect.constants'; 13 | 14 | const RectOptions = ({ t }) => { 15 | const [rect, saveRect] = useAnnotation({ 16 | name: TOOLS_IDS.RECT, 17 | }); 18 | 19 | return ( 20 | 28 | ); 29 | }; 30 | 31 | RectOptions.propTypes = { 32 | t: PropTypes.func.isRequired, 33 | }; 34 | 35 | export default RectOptions; 36 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Rect/index.js: -------------------------------------------------------------------------------- 1 | export { default as RectButton } from './RectButton'; 2 | 3 | export { default as RectOptions } from './RectOptions'; 4 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Resize/Resize.styled.js: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import styled, { css } from 'styled-components'; 3 | import IconButton from '@scaleflex/ui/core/icon-button'; 4 | import InputGroup from '@scaleflex/ui/core/input-group'; 5 | import { Color as PC } from '@scaleflex/ui/utils/types/palette'; 6 | import { FontVariant as FV } from '@scaleflex/ui/utils/types/typography'; 7 | 8 | const StyledResizeWrapper = styled.div` 9 | display: flex; 10 | justify-content: ${({ alignment }) => alignment || 'center'}; 11 | align-items: flex-end; 12 | gap: 3px; 13 | flex-wrap: wrap; 14 | `; 15 | 16 | const StyledResizeInput = styled(InputGroup)( 17 | ({ theme }) => css` 18 | width: 106px; 19 | max-width: 106px; 20 | margin-top: 4px; 21 | 22 | .SfxInput-Base { 23 | width: 100%; 24 | min-width: 100%; 25 | max-width: 100%; 26 | } 27 | 28 | span { 29 | color: ${theme.palette[PC.TextSecondary]}; 30 | ${theme.typography.font[FV.LabelMedium]}; 31 | } 32 | `, 33 | ); 34 | 35 | const StyledRatioLockIcon = styled(IconButton)` 36 | svg { 37 | margin-bottom: 1px; 38 | } 39 | `; 40 | 41 | const StyledResetButton = styled(IconButton)` 42 | margin-left: 12px; 43 | `; 44 | 45 | export { 46 | StyledResizeWrapper, 47 | StyledResizeInput, 48 | StyledRatioLockIcon, 49 | StyledResetButton, 50 | }; 51 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Resize/index.js: -------------------------------------------------------------------------------- 1 | export { default as Resize } from './Resize'; 2 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Rotate/Rotate.styled.js: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import styled from 'styled-components'; 3 | import { IconButton, RotationSlider } from '@scaleflex/ui/core'; 4 | import { Color as PC } from '@scaleflex/ui/utils/types/palette'; 5 | 6 | const StyledRotationOptions = styled.div` 7 | display: flex; 8 | align-items: center; 9 | gap: 16px; 10 | `; 11 | 12 | const StyledRotationSlider = styled(RotationSlider)` 13 | .SfxRotationSlider-control { 14 | width: 1px; 15 | height: 10px; 16 | background-color: ${({ theme: { palette } }) => palette[PC.IconsSecondary]}; 17 | 18 | &:before { 19 | box-shadow: unset; 20 | } 21 | } 22 | 23 | .SfxRotationSlider-mark, 24 | .SfxRotationSlider-small-dot-wrapper { 25 | padding: 0; 26 | 27 | .SfxRotationSlider-mark-text { 28 | top: 10px; 29 | } 30 | 31 | .SfxRotationSlider-big-dot { 32 | width: 4px; 33 | height: 4px; 34 | } 35 | 36 | .SfxRotationSlider-small-dot { 37 | width: 1px; 38 | height: 1px; 39 | } 40 | } 41 | 42 | .SfxRotationSlider-list { 43 | gap: 4px; 44 | } 45 | `; 46 | 47 | const StyledRotateButton = styled(IconButton)``; 48 | 49 | export { StyledRotationOptions, StyledRotationSlider, StyledRotateButton }; 50 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Rotate/RotateButton.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | import { RotationLeft as RotateIcon } from '@scaleflex/icons/rotation-left'; 5 | 6 | /** Internal Dependencies */ 7 | import ToolsBarItemButton from 'components/ToolsBar/ToolsBarItemButton'; 8 | import { TOOLS_IDS } from 'utils/constants'; 9 | 10 | const RotateButton = ({ selectTool, isSelected, t }) => ( 11 | 19 | ); 20 | 21 | RotateButton.defaultProps = { 22 | isSelected: false, 23 | }; 24 | 25 | RotateButton.propTypes = { 26 | selectTool: PropTypes.func.isRequired, 27 | isSelected: PropTypes.bool, 28 | t: PropTypes.func.isRequired, 29 | }; 30 | 31 | export default RotateButton; 32 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Rotate/index.js: -------------------------------------------------------------------------------- 1 | export { default as RotateButton } from './RotateButton'; 2 | 3 | export { default as RotateOptions } from './RotateOptions'; 4 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Text/TextButton.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | import { Text as TextIcon } from '@scaleflex/icons/text'; 5 | 6 | /** Internal Dependencies */ 7 | import ToolsBarItemButton from 'components/ToolsBar/ToolsBarItemButton'; 8 | import { TOOLS_IDS } from 'utils/constants'; 9 | 10 | const TextButton = ({ selectTool, isSelected, t }) => ( 11 | 19 | ); 20 | 21 | TextButton.defaultProps = { 22 | isSelected: false, 23 | }; 24 | 25 | TextButton.propTypes = { 26 | selectTool: PropTypes.func.isRequired, 27 | isSelected: PropTypes.bool, 28 | t: PropTypes.func.isRequired, 29 | }; 30 | 31 | export default TextButton; 32 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Text/TextOptions/TextAlignmentFields.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | 5 | /** Internal Dependencies */ 6 | import { 7 | StyledSpacedOptionFields, 8 | StyledIconWrapper, 9 | } from 'components/common/AnnotationOptions/AnnotationOptions.styled'; 10 | import { TextAlignCenter, TextAlignLeft } from '@scaleflex/icons'; 11 | 12 | const rightAlignmentCssTransform = { transform: 'scaleX(-1)' }; 13 | 14 | const TextAlignmentFields = ({ 15 | annotation: text, 16 | updateAnnotation: updateText, 17 | }) => { 18 | const { align } = text; 19 | 20 | const changeHorizontalAlignment = (newHorizonalAlignment) => { 21 | updateText({ align: newHorizonalAlignment }); 22 | }; 23 | 24 | return ( 25 | 26 | changeHorizontalAlignment('left')} 28 | active={align === 'left'} 29 | > 30 | 31 | 32 | changeHorizontalAlignment('center')} 34 | active={align === 'center'} 35 | > 36 | 37 | 38 | changeHorizontalAlignment('right')} 40 | active={align === 'right'} 41 | > 42 | 43 | 44 | 45 | ); 46 | }; 47 | 48 | TextAlignmentFields.propTypes = { 49 | annotation: PropTypes.instanceOf(Object).isRequired, 50 | updateAnnotation: PropTypes.func.isRequired, 51 | }; 52 | 53 | export default TextAlignmentFields; 54 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Text/TextOptions/TextOptions.constants.js: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import { TextAlignCenter } from '@scaleflex/icons'; 3 | import Spacing from '@scaleflex/icons/spacing'; 4 | 5 | /** Internal Dependencies */ 6 | import TextSpacingsFields from './TextSpacingsFields'; 7 | import TextAlignmentFields from './TextAlignmentFields'; 8 | 9 | export const TEXT_ALIGNMENT = 'text-alignment'; 10 | export const TEXT_SPACINGS = 'text-spacings'; 11 | 12 | export const TEXT_POPPABLE_OPTIONS = [ 13 | { 14 | titleKey: 'textAlignment', 15 | name: TEXT_ALIGNMENT, 16 | Icon: TextAlignCenter, 17 | }, 18 | { 19 | titleKey: 'textSpacings', 20 | name: TEXT_SPACINGS, 21 | Icon: Spacing, 22 | }, 23 | ]; 24 | 25 | export const textOptionsPopupComponents = { 26 | [TEXT_ALIGNMENT]: TextAlignmentFields, 27 | [TEXT_SPACINGS]: TextSpacingsFields, 28 | }; 29 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Text/TextOptions/TextOptions.styled.js: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import styled from 'styled-components'; 3 | import Input from '@scaleflex/ui/core/input'; 4 | import Select from '@scaleflex/ui/core/select'; 5 | 6 | const StyledFontFamilySelect = styled(Select)` 7 | width: 160px; 8 | `; 9 | 10 | const StyledFontSizeInput = styled(Input)` 11 | width: 72px; 12 | `; 13 | 14 | const StyledToolsWrapper = styled.div` 15 | display: flex; 16 | `; 17 | 18 | export { StyledFontFamilySelect, StyledFontSizeInput, StyledToolsWrapper }; 19 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Text/TextOptions/TextSpacingsFields.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | import Label from '@scaleflex/ui/core/label'; 5 | 6 | /** Internal Dependencies */ 7 | import restrictNumber from 'utils/restrictNumber'; 8 | import { StyledSpacedOptionFields } from 'components/common/AnnotationOptions/AnnotationOptions.styled'; 9 | import Slider from 'components/common/Slider'; 10 | 11 | const MIN_VALUE = 0; 12 | const MAX_VALUE = 100; 13 | const SLIDER_STEP = 1; 14 | 15 | const TextSpacingsFields = ({ 16 | annotation: text, 17 | updateAnnotation: updateText, 18 | t, 19 | }) => { 20 | const { letterSpacing, lineHeight } = text; 21 | 22 | const updateValue = (prop, val) => { 23 | updateText({ [prop]: restrictNumber(val, MIN_VALUE, MAX_VALUE) }); 24 | }; 25 | 26 | return ( 27 | 28 | 29 | updateValue('letterSpacing', val)} 33 | value={letterSpacing} 34 | step={SLIDER_STEP} 35 | /> 36 | 37 | updateValue('lineHeight', val)} 41 | value={lineHeight} 42 | step={SLIDER_STEP} 43 | /> 44 | 45 | ); 46 | }; 47 | 48 | TextSpacingsFields.propTypes = { 49 | annotation: PropTypes.instanceOf(Object).isRequired, 50 | updateAnnotation: PropTypes.func.isRequired, 51 | t: PropTypes.func.isRequired, 52 | }; 53 | 54 | export default TextSpacingsFields; 55 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Text/TextOptions/index.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | 5 | /** Internal Dependencies */ 6 | import { useAnnotation } from 'hooks'; 7 | import { TOOLS_IDS } from 'utils/constants'; 8 | import TextControls from './TextControls'; 9 | 10 | const TextOptions = ({ t }) => { 11 | const [text, saveText] = useAnnotation({ name: TOOLS_IDS.TEXT }); 12 | 13 | return ; 14 | }; 15 | 16 | TextOptions.propTypes = { 17 | t: PropTypes.func.isRequired, 18 | }; 19 | 20 | export default TextOptions; 21 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Text/index.js: -------------------------------------------------------------------------------- 1 | export { default as TextButton } from './TextButton'; 2 | 3 | export { default as TextOptions } from './TextOptions'; 4 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Warmth/Warmth.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | import { Temprature as WarmthIcon } from '@scaleflex/icons/tempreture'; 5 | 6 | /** Internal Dependencies */ 7 | import ToolsBarItemButton from 'components/ToolsBar/ToolsBarItemButton'; 8 | import { TOOLS_IDS } from 'utils/constants'; 9 | 10 | const Warmth = ({ selectTool, isSelected, t }) => ( 11 | 19 | ); 20 | 21 | Warmth.defaultProps = { 22 | isSelected: false, 23 | }; 24 | 25 | Warmth.propTypes = { 26 | selectTool: PropTypes.func.isRequired, 27 | isSelected: PropTypes.bool, 28 | t: PropTypes.func.isRequired, 29 | }; 30 | 31 | export default Warmth; 32 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Warmth/index.js: -------------------------------------------------------------------------------- 1 | export { default as Warmth } from './Warmth'; 2 | 3 | export { default as WarmthOptions } from './WarmthOptions'; 4 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Watermark/Watermark.styled.js: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import styled from 'styled-components'; 3 | 4 | const StyledWatermarkWrapper = styled.div` 5 | display: flex; 6 | align-items: center; 7 | justify-content: center; 8 | flex-wrap: wrap; 9 | gap: 8px; 10 | overflow: hidden; 11 | 12 | ${({ noWrap }) => (noWrap ? 'flex-wrap: nowrap;' : '')}; 13 | `; 14 | 15 | const StyledControlsWrapper = styled.div` 16 | margin-bottom: 8px; 17 | `; 18 | 19 | const StyledWatermarkGalleryItem = styled.div( 20 | ({ theme }) => ` 21 | padding: 6px 4px; 22 | border: 1px solid ${theme.palette['borders-secondary']}; 23 | width: fit-content; 24 | height: 32px; 25 | border-radius: 2px; 26 | overflow: hidden; 27 | cursor: pointer; 28 | border-radius: 4px; 29 | 30 | :hover { 31 | background: ${theme.palette['bg-primary-active']}; 32 | } 33 | 34 | &[aria-selected='true'] { 35 | background: ${theme.palette['bg-primary-active']}; 36 | border-color: ${theme.palette['accent-primary-active']}; 37 | } 38 | 39 | img { 40 | max-width: 100%; 41 | max-height: 100%; 42 | } 43 | `, 44 | ); 45 | 46 | export { 47 | StyledWatermarkWrapper, 48 | StyledControlsWrapper, 49 | StyledWatermarkGalleryItem, 50 | }; 51 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/Watermark/index.jsx: -------------------------------------------------------------------------------- 1 | export { default as Watermark } from './Watermark'; 2 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/components/tools/tools.styled.js: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import { Input, Label } from '@scaleflex/ui/core'; 3 | import styled from 'styled-components'; 4 | import { FontVariant as FV } from '@scaleflex/ui/utils/types/typography'; 5 | 6 | const StyledHSVOptions = styled.div` 7 | display: flex; 8 | width: 100%; 9 | justify-content: space-between; 10 | gap: 10px; 11 | 12 | .SfxSlider-root { 13 | min-width: 100px; 14 | } 15 | 16 | ${({ isPhoneScreen }) => 17 | isPhoneScreen && 18 | ` 19 | flex-direction: column; 20 | 21 | .SfxSlider-root { 22 | min-width: 230px; 23 | } 24 | `} 25 | `; 26 | 27 | const StyledSliderContainer = styled.div` 28 | display: flex; 29 | flex-direction: column; 30 | `; 31 | 32 | const StyledSliderLabel = styled(Label)` 33 | ${({ theme }) => theme.typography.font[FV.LabelExtraSmallUp]}; 34 | `; 35 | 36 | const StyledSliderWrapper = styled.div` 37 | display: flex; 38 | align-items: center; 39 | `; 40 | 41 | const StyledSliderInput = styled(Input)` 42 | display: inline-block; 43 | width: 40px; 44 | height: 28px; 45 | padding: 6px 2px; 46 | margin-left: 10px; 47 | border: none; 48 | 49 | .SfxInput-Base { 50 | text-align: center; 51 | width: 100%; 52 | min-width: 100%; 53 | max-width: 100%; 54 | } 55 | `; 56 | 57 | export { 58 | StyledHSVOptions, 59 | StyledSliderContainer, 60 | StyledSliderLabel, 61 | StyledSliderWrapper, 62 | StyledSliderInput, 63 | }; 64 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/context/AppContext.js: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import { createContext } from 'react'; 3 | 4 | const AppContext = createContext({}); 5 | 6 | export default AppContext; 7 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/context/AppProvider.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React, { useCallback, useEffect, useMemo } from 'react'; 3 | import PropTypes from 'prop-types'; 4 | import { useTheme } from '@scaleflex/ui/theme/hooks'; 5 | 6 | /** Internal Dependencies */ 7 | import { useAppReducer } from 'hooks'; 8 | import { translate, updateTranslations } from 'utils/translator'; 9 | import appReducer from './appReducer'; 10 | import AppContext from './AppContext'; 11 | import getInitialAppState from './getInitialAppState'; 12 | 13 | let isFieMounted = true; 14 | 15 | const AppProvider = ({ children, config = {} }) => { 16 | const [state, _dispatch] = useAppReducer( 17 | appReducer, 18 | getInitialAppState(config), 19 | config, 20 | ); 21 | 22 | useEffect(() => { 23 | isFieMounted = true; 24 | 25 | return () => { 26 | isFieMounted = false; 27 | }; 28 | }, []); 29 | 30 | const dispatch = useCallback( 31 | (...args) => { 32 | if (isFieMounted) { 33 | _dispatch(...args); 34 | } 35 | }, 36 | [_dispatch], 37 | ); 38 | 39 | useEffect(() => { 40 | updateTranslations(config.translations, config.language); 41 | }, [config.useBackendTranslations, config.language, config.translations]); 42 | 43 | const theme = useTheme(); 44 | const providedValue = useMemo( 45 | () => ({ 46 | ...state, 47 | config, 48 | theme, 49 | dispatch, 50 | t: translate, 51 | }), 52 | [config, state], 53 | ); 54 | 55 | return ( 56 | {children} 57 | ); 58 | }; 59 | 60 | AppProvider.defaultProps = { 61 | config: {}, 62 | }; 63 | 64 | AppProvider.propTypes = { 65 | children: PropTypes.node.isRequired, 66 | config: PropTypes.instanceOf(Object), 67 | }; 68 | 69 | export default AppProvider; 70 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/context/AppProviderOverridenValue.jsx: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | 5 | /** Internal Dependencies */ 6 | import AppContext from './AppContext'; 7 | 8 | // This component is used in repassing the state to react-konva's modules 9 | // As it has issue in context bridging. 10 | const AppProviderOverridenValue = ({ children, overridingValue }) => ( 11 | {children} 12 | ); 13 | 14 | AppProviderOverridenValue.propTypes = { 15 | children: PropTypes.node.isRequired, 16 | overridingValue: PropTypes.instanceOf(Object).isRequired, 17 | }; 18 | 19 | export default AppProviderOverridenValue; 20 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/context/appReducer.js: -------------------------------------------------------------------------------- 1 | import actions from 'actions'; 2 | 3 | const appReducer = (state, action) => 4 | actions[action.type] 5 | ? actions[action.type](state, action.payload) || state 6 | : state; 7 | 8 | export default appReducer; 9 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/context/index.js: -------------------------------------------------------------------------------- 1 | import AppContext from './AppContext'; 2 | 3 | export default AppContext; 4 | 5 | export { default as AppProvider } from './AppProvider'; 6 | 7 | export { default as AppProviderOverridenValue } from './AppProviderOverridenValue'; 8 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/Aden.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from './BaseFilters'; 2 | 3 | const COLOR_FILTER_CONST = [228, 130, 225, 0.13]; 4 | const SATURATION_CONST = -0.2; 5 | 6 | /** 7 | * Aden Filter. 8 | * @function 9 | * @param {Object} imageData 10 | * @example 11 | * node.cache(); 12 | * node.filters([Aden]); 13 | */ 14 | function Aden(imageData) { 15 | BaseFilters.apply( 16 | imageData, 17 | BaseFilters.colorFilter(COLOR_FILTER_CONST), 18 | BaseFilters.saturation(SATURATION_CONST), 19 | ); 20 | } 21 | 22 | Aden.filterName = 'Aden'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 23 | 24 | export default Aden; 25 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/Amaro.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from './BaseFilters'; 2 | 3 | const SATURATION_CONST = 0.3; 4 | const BRIGHTNESS_CONST = 0.15; 5 | 6 | /** 7 | * Amaro Filter. 8 | * @function 9 | * @param {Object} imageData 10 | * @example 11 | * node.cache(); 12 | * node.filters([Amaro]); 13 | */ 14 | function Amaro(imageData) { 15 | BaseFilters.apply( 16 | imageData, 17 | BaseFilters.saturation(SATURATION_CONST), 18 | BaseFilters.brightness(BRIGHTNESS_CONST), 19 | ); 20 | } 21 | 22 | Amaro.filterName = 'Amaro'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 23 | 24 | export default Amaro; 25 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/Ashby.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from './BaseFilters'; 2 | 3 | const COLOR_FILTER_CONST = [255, 160, 25, 0.1]; 4 | const BRIGHTNESS_CONST = 0.1; 5 | 6 | /** 7 | * Ashby Filter. 8 | * @function 9 | * @param {Object} imageData 10 | * @example 11 | * node.cache(); 12 | * node.filters([Ashby]); 13 | */ 14 | function Ashby(imageData) { 15 | BaseFilters.apply( 16 | imageData, 17 | BaseFilters.colorFilter(COLOR_FILTER_CONST), 18 | BaseFilters.brightness(BRIGHTNESS_CONST), 19 | ); 20 | } 21 | 22 | Ashby.filterName = 'Ashby'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 23 | 24 | export default Ashby; 25 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/BlackAndWhite.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from "./BaseFilters"; 2 | 3 | /** 4 | * BlackAndWhite Filter. 5 | * @function 6 | * @param {Object} imageData 7 | * @example 8 | * node.cache(); 9 | * node.filters([BlackAndWhite]); 10 | */ 11 | function BlackAndWhite(imageData) { 12 | const thresholdValue = 100; 13 | BaseFilters.apply( 14 | imageData, 15 | (pixels) => { 16 | const isWhite = 17 | (pixels[0] + pixels[1] + pixels[2]) / 3 > thresholdValue; 18 | const val = isWhite ? 255 : 0; 19 | return [val, val, val]; 20 | }, 21 | ); 22 | } 23 | 24 | BlackAndWhite.filterName = 'BlackAndWhite'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 25 | 26 | export default BlackAndWhite; 27 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/Brannan.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from './BaseFilters'; 2 | 3 | const CONTRAST_CONST = 0.2; 4 | const COLOR_FILTER_CONST = [140, 10, 185, 0.1]; 5 | 6 | /** 7 | * Brannan Filter. 8 | * @function 9 | * @param {Object} imageData 10 | * @example 11 | * node.cache(); 12 | * node.filters([Brannan]); 13 | */ 14 | function Brannan(imageData) { 15 | BaseFilters.apply( 16 | imageData, 17 | BaseFilters.contrast(CONTRAST_CONST), 18 | BaseFilters.colorFilter(COLOR_FILTER_CONST), 19 | ); 20 | } 21 | 22 | Brannan.filterName = 'Brannan'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 23 | 24 | export default Brannan; 25 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/Brooklyn.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from './BaseFilters'; 2 | 3 | const COLOR_FILTER_CONST = [25, 240, 252, 0.05]; 4 | const SEPIA_CONST = 0.3; 5 | 6 | /** 7 | * Brooklyn Filter. 8 | * @function 9 | * @param {Object} imageData 10 | * @example 11 | * node.cache(); 12 | * node.filters([Brooklyn]); 13 | */ 14 | function Brooklyn(imageData) { 15 | BaseFilters.apply( 16 | imageData, 17 | BaseFilters.colorFilter(COLOR_FILTER_CONST), 18 | BaseFilters.sepia(SEPIA_CONST), 19 | ); 20 | } 21 | 22 | Brooklyn.filterName = 'Brooklyn'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 23 | 24 | export default Brooklyn; 25 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/Charmes.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from './BaseFilters'; 2 | 3 | const COLOR_FILTER_CONST = [255, 50, 80, 0.12]; 4 | const CONTRAST_CONST = 0.05; 5 | 6 | /** 7 | * Charmes Filter. 8 | * @function 9 | * @param {Object} imageData 10 | * @example 11 | * node.cache(); 12 | * node.filters([Charmes]); 13 | */ 14 | function Charmes(imageData) { 15 | BaseFilters.apply( 16 | imageData, 17 | BaseFilters.colorFilter(COLOR_FILTER_CONST), 18 | BaseFilters.contrast(CONTRAST_CONST), 19 | ); 20 | } 21 | 22 | Charmes.filterName = 'Charmes'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 23 | 24 | export default Charmes; 25 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/Clarendon.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from './BaseFilters'; 2 | 3 | const BRIGHTNESS_CONST = 0.1; 4 | const CONTRAST_CONST = 0.1; 5 | const SATURATION_CONST = 0.15; 6 | 7 | /** 8 | * Clarendon Filter. 9 | * @function 10 | * @param {Object} imageData 11 | * @example 12 | * node.cache(); 13 | * node.filters([Clarendon]); 14 | */ 15 | function Clarendon(imageData) { 16 | BaseFilters.apply( 17 | imageData, 18 | BaseFilters.brightness(BRIGHTNESS_CONST), 19 | BaseFilters.contrast(CONTRAST_CONST), 20 | BaseFilters.saturation(SATURATION_CONST), 21 | ); 22 | } 23 | 24 | Clarendon.filterName = 'Clarendon'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 25 | 26 | export default Clarendon; 27 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/Crema.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from './BaseFilters'; 2 | 3 | const ADJUST_RGB_CONST = [1.04, 1, 1.02]; 4 | const SATURATION_CONST = -0.05; 5 | 6 | /** 7 | * Crema Filter. 8 | * @function 9 | * @param {Object} imageData 10 | * @example 11 | * node.cache(); 12 | * node.filters([Crema]); 13 | */ 14 | function Crema(imageData) { 15 | BaseFilters.apply( 16 | imageData, 17 | BaseFilters.adjustRGB(ADJUST_RGB_CONST), 18 | BaseFilters.saturation(SATURATION_CONST), 19 | ); 20 | } 21 | 22 | Crema.filterName = 'Crema'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 23 | 24 | export default Crema; 25 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/Dogpatch.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from './BaseFilters'; 2 | 3 | const CONTRAST_CONST = 0.15; 4 | const BRIGHTNESS_CONST = 0.1; 5 | 6 | /** 7 | * Dogpatch Filter. 8 | * @function 9 | * @param {Object} imageData 10 | * @example 11 | * node.cache(); 12 | * node.filters([Dogpatch]); 13 | */ 14 | function Dogpatch(imageData) { 15 | BaseFilters.apply( 16 | imageData, 17 | BaseFilters.contrast(CONTRAST_CONST), 18 | BaseFilters.brightness(BRIGHTNESS_CONST), 19 | ); 20 | } 21 | 22 | Dogpatch.filterName = 'Dogpatch'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 23 | 24 | export default Dogpatch; 25 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/Earlybird.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from './BaseFilters'; 2 | 3 | const COLOR_FILTER_CONST = [255, 165, 40, 0.2]; 4 | 5 | /** 6 | * Earlybird Filter. 7 | * @function 8 | * @param {Object} imageData 9 | * @example 10 | * node.cache(); 11 | * node.filters([Earlybird]); 12 | */ 13 | function Earlybird(imageData) { 14 | BaseFilters.apply( 15 | imageData, 16 | BaseFilters.colorFilter(COLOR_FILTER_CONST), 17 | ); 18 | } 19 | 20 | Earlybird.filterName = 'Earlybird'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 21 | 22 | export default Earlybird; 23 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/Gingham.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from './BaseFilters'; 2 | 3 | const SEPIA_CONST = 0.04; 4 | const CONTRAST_CONST = -0.15; 5 | 6 | /** 7 | * Gingham Filter. 8 | * @function 9 | * @param {Object} imageData 10 | * @example 11 | * node.cache(); 12 | * node.filters([Gingham]); 13 | */ 14 | function Gingham(imageData) { 15 | BaseFilters.apply( 16 | imageData, 17 | BaseFilters.sepia(SEPIA_CONST), 18 | BaseFilters.contrast(CONTRAST_CONST), 19 | ); 20 | } 21 | 22 | Gingham.filterName = 'Gingham'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 23 | 24 | export default Gingham; 25 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/Ginza.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from './BaseFilters'; 2 | 3 | const SEPIA_CONST = 0.06; 4 | const BRIGHTNESS_CONST = 0.1; 5 | 6 | /** 7 | * Ginza Filter. 8 | * @function 9 | * @param {Object} imageData 10 | * @example 11 | * node.cache(); 12 | * node.filters([Ginza]); 13 | */ 14 | function Ginza(imageData) { 15 | BaseFilters.apply( 16 | imageData, 17 | BaseFilters.sepia(SEPIA_CONST), 18 | BaseFilters.brightness(BRIGHTNESS_CONST), 19 | ); 20 | } 21 | 22 | Ginza.filterName = 'Ginza'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 23 | 24 | export default Ginza; 25 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/Hefe.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from './BaseFilters'; 2 | 3 | const CONTRAST_CONST = 0.1; 4 | const SATURATION_CONST = 0.15; 5 | 6 | /** 7 | * Hefe Filter. 8 | * @function 9 | * @param {Object} imageData 10 | * @example 11 | * node.cache(); 12 | * node.filters([Hefe]); 13 | */ 14 | function Hefe(imageData) { 15 | BaseFilters.apply( 16 | imageData, 17 | BaseFilters.contrast(CONTRAST_CONST), 18 | BaseFilters.saturation(SATURATION_CONST), 19 | ); 20 | } 21 | 22 | Hefe.filterName = 'Hefe'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 23 | 24 | export default Hefe; 25 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/Helena.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from './BaseFilters'; 2 | 3 | const COLOR_FILTER_CONST = [208, 208, 86, 0.2]; 4 | const CONTRAST_CONST = 0.15; 5 | 6 | /** 7 | * Helena Filter. 8 | * @function 9 | * @param {Object} imageData 10 | * @example 11 | * node.cache(); 12 | * node.filters([Helena]); 13 | */ 14 | function Helena(imageData) { 15 | BaseFilters.apply( 16 | imageData, 17 | BaseFilters.colorFilter(COLOR_FILTER_CONST), 18 | BaseFilters.contrast(CONTRAST_CONST), 19 | ); 20 | } 21 | 22 | Helena.filterName = 'Helena'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 23 | 24 | export default Helena; 25 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/Hudson.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from './BaseFilters'; 2 | 3 | const ADJUST_RGB_CONST = [1, 1, 1.25]; 4 | const CONTRAST_CONST = 0.1; 5 | const BRIGHTNESS_CONST = 0.15; 6 | 7 | /** 8 | * Hudson Filter. 9 | * @function 10 | * @param {Object} imageData 11 | * @example 12 | * node.cache(); 13 | * node.filters([Hudson]); 14 | */ 15 | function Hudson(imageData) { 16 | BaseFilters.apply( 17 | imageData, 18 | BaseFilters.adjustRGB(ADJUST_RGB_CONST), 19 | BaseFilters.contrast(CONTRAST_CONST), 20 | BaseFilters.brightness(BRIGHTNESS_CONST), 21 | ); 22 | } 23 | 24 | Hudson.filterName = 'Hudson'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 25 | 26 | export default Hudson; 27 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/Juno.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from './BaseFilters'; 2 | 3 | const ADJUST_RGB_CONST = [1.01, 1.04, 1]; 4 | const SATURATION_CONST = 0.3; 5 | 6 | /** 7 | * Juno Filter. 8 | * @function 9 | * @param {Object} imageData 10 | * @example 11 | * node.cache(); 12 | * node.filters([Juno]); 13 | */ 14 | function Juno(imageData) { 15 | BaseFilters.apply( 16 | imageData, 17 | BaseFilters.adjustRGB(ADJUST_RGB_CONST), 18 | BaseFilters.saturation(SATURATION_CONST), 19 | ); 20 | } 21 | 22 | Juno.filterName = 'Juno'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 23 | 24 | export default Juno; 25 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/Kelvin.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from './BaseFilters'; 2 | 3 | const COLOR_FILTER_CONST = [255, 140, 0, 0.1]; 4 | const ADJUST_RGB_CONST = [1.15, 1.05, 1]; 5 | const SATURATION_CONST = 0.35; 6 | 7 | /** 8 | * Kelvin Filter. 9 | * @function 10 | * @param {Object} imageData 11 | * @example 12 | * node.cache(); 13 | * node.filters([Kelvin]); 14 | */ 15 | function Kelvin(imageData) { 16 | BaseFilters.apply( 17 | imageData, 18 | BaseFilters.colorFilter(COLOR_FILTER_CONST), 19 | BaseFilters.adjustRGB(ADJUST_RGB_CONST), 20 | BaseFilters.saturation(SATURATION_CONST), 21 | ); 22 | } 23 | 24 | Kelvin.filterName = 'Kelvin'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 25 | 26 | export default Kelvin; 27 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/Lark.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from './BaseFilters'; 2 | 3 | const BRIGHTNESS_CONST = 0.08; 4 | const ADJUST_RGB_CONST = [1, 1.03, 1.05]; 5 | const SATURATION_CONST = 0.12; 6 | 7 | /** 8 | * Lark Filter. 9 | * @function 10 | * @param {Object} imageData 11 | * @example 12 | * node.cache(); 13 | * node.filters([Lark]); 14 | */ 15 | function Lark(imageData) { 16 | BaseFilters.apply( 17 | imageData, 18 | BaseFilters.brightness(BRIGHTNESS_CONST), 19 | BaseFilters.adjustRGB(ADJUST_RGB_CONST), 20 | BaseFilters.saturation(SATURATION_CONST), 21 | ); 22 | } 23 | 24 | Lark.filterName = 'Lark'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 25 | 26 | export default Lark; 27 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/LoFi.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from './BaseFilters'; 2 | 3 | const CONTRAST_CONST = 0.15; 4 | const SATURATION_CONST = 0.2; 5 | 6 | /** 7 | * LoFi Filter. 8 | * @function 9 | * @param {Object} imageData 10 | * @example 11 | * node.cache(); 12 | * node.filters([LoFi]); 13 | */ 14 | function LoFi(imageData) { 15 | BaseFilters.apply( 16 | imageData, 17 | BaseFilters.contrast(CONTRAST_CONST), 18 | BaseFilters.saturation(SATURATION_CONST), 19 | ); 20 | } 21 | 22 | LoFi.filterName = 'LoFi'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 23 | 24 | export default LoFi; 25 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/Ludwig.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from './BaseFilters'; 2 | 3 | const BRIGHTNESS_CONST = 0.05; 4 | const SATURATION_CONST = -0.03; 5 | 6 | /** 7 | * Ludwig Filter. 8 | * @function 9 | * @param {Object} imageData 10 | * @example 11 | * node.cache(); 12 | * node.filters([Ludwig]); 13 | */ 14 | function Ludwig(imageData) { 15 | BaseFilters.apply( 16 | imageData, 17 | BaseFilters.brightness(BRIGHTNESS_CONST), 18 | BaseFilters.saturation(SATURATION_CONST), 19 | ); 20 | } 21 | 22 | Ludwig.filterName = 'Ludwig'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 23 | 24 | export default Ludwig; 25 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/Maven.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from './BaseFilters'; 2 | 3 | const COLOR_FILTER_CONST = [225, 240, 0, 0.1]; 4 | const SATURATION_CONST = 0.25; 5 | const CONTRAST_CONST = 0.05; 6 | 7 | /** 8 | * Maven Filter. 9 | * @function 10 | * @param {Object} imageData 11 | * @example 12 | * node.cache(); 13 | * node.filters([Maven]); 14 | */ 15 | function Maven(imageData) { 16 | BaseFilters.apply( 17 | imageData, 18 | BaseFilters.colorFilter(COLOR_FILTER_CONST), 19 | BaseFilters.saturation(SATURATION_CONST), 20 | BaseFilters.contrast(CONTRAST_CONST), 21 | ); 22 | } 23 | 24 | Maven.filterName = 'Maven'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 25 | 26 | export default Maven; 27 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/Mayfair.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from './BaseFilters'; 2 | 3 | const COLOR_FILTER_CONST = [230, 115, 108, 0.05]; 4 | const SATURATION_CONST = 0.15; 5 | 6 | /** 7 | * Mayfair Filter. 8 | * @function 9 | * @param {Object} imageData 10 | * @example 11 | * node.cache(); 12 | * node.filters([Mayfair]); 13 | */ 14 | function Mayfair(imageData) { 15 | BaseFilters.apply( 16 | imageData, 17 | BaseFilters.colorFilter(COLOR_FILTER_CONST), 18 | BaseFilters.saturation(SATURATION_CONST), 19 | ); 20 | } 21 | 22 | Mayfair.filterName = 'Mayfair'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 23 | 24 | export default Mayfair; 25 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/Moon.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from './BaseFilters'; 2 | 3 | const BRIGHTNESS_CONST = 0.1; 4 | 5 | /** 6 | * Moon Filter. 7 | * @function 8 | * @param {Object} imageData 9 | * @example 10 | * node.cache(); 11 | * node.filters([Moon]); 12 | */ 13 | function Moon(imageData) { 14 | BaseFilters.apply( 15 | imageData, 16 | BaseFilters.grayscale(), 17 | BaseFilters.brightness(BRIGHTNESS_CONST), 18 | ); 19 | 20 | const pixels = imageData.data; // [0, 1, 2, 3,...] => [r, g, b, a, ...] 21 | const len = pixels.length; 22 | } 23 | 24 | Moon.filterName = 'Moon'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 25 | 26 | export default Moon; 27 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/Nashville.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from './BaseFilters'; 2 | 3 | const COLOR_FILTER_CONST = [220, 115, 188, 0.12]; 4 | const CONTRAST_CONST = -0.05; 5 | 6 | /** 7 | * Nashville Filter. 8 | * @function 9 | * @param {Object} imageData 10 | * @example 11 | * node.cache(); 12 | * node.filters([Nashville]); 13 | */ 14 | function Nashville(imageData) { 15 | BaseFilters.apply( 16 | imageData, 17 | BaseFilters.colorFilter(COLOR_FILTER_CONST), 18 | BaseFilters.contrast(CONTRAST_CONST), 19 | ); 20 | } 21 | 22 | Nashville.filterName = 'Nashville'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 23 | 24 | export default Nashville; 25 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/NinteenSeventySeven.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from './BaseFilters'; 2 | 3 | const COLOR_FILTER_CONST = [255, 25, 0, 0.15]; 4 | const BRIGHTNESS_CONST = 0.1; 5 | 6 | /** 7 | * NinteenSeventySeven Filter. 8 | * @function 9 | * @param {Object} imageData 10 | * @example 11 | * node.cache(); 12 | * node.filters([NinteenSeventySeven]); 13 | */ 14 | function NinteenSeventySeven(imageData) { 15 | BaseFilters.apply( 16 | imageData, 17 | BaseFilters.colorFilter(COLOR_FILTER_CONST), 18 | BaseFilters.brightness(BRIGHTNESS_CONST), 19 | ); 20 | } 21 | 22 | NinteenSeventySeven.filterName = 'NinteenSeventySeven'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 23 | 24 | export default NinteenSeventySeven; 25 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/Perpetua.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from './BaseFilters'; 2 | 3 | const ADJUST_RGB_CONST = [1.05, 1.1, 1]; 4 | 5 | /** 6 | * Perpetua Filter. 7 | * @function 8 | * @param {Object} imageData 9 | * @example 10 | * node.cache(); 11 | * node.filters([Perpetua]); 12 | */ 13 | function Perpetua(imageData) { 14 | BaseFilters.apply( 15 | imageData, 16 | BaseFilters.adjustRGB(ADJUST_RGB_CONST), 17 | ); 18 | } 19 | 20 | Perpetua.filterName = 'Perpetua'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 21 | 22 | export default Perpetua; 23 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/Reyes.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from './BaseFilters'; 2 | 3 | const SEPIA_CONST = 0.4; 4 | const BRIGHTNESS_CONST = 0.13; 5 | const CONTRAST_CONST = -0.05; 6 | 7 | /** 8 | * Reyes Filter. 9 | * @function 10 | * @param {Object} imageData 11 | * @example 12 | * node.cache(); 13 | * node.filters([Reyes]); 14 | */ 15 | function Reyes(imageData) { 16 | BaseFilters.apply( 17 | imageData, 18 | BaseFilters.sepia(SEPIA_CONST), 19 | BaseFilters.brightness(BRIGHTNESS_CONST), 20 | BaseFilters.contrast(CONTRAST_CONST), 21 | ); 22 | } 23 | 24 | Reyes.filterName = 'Reyes'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 25 | 26 | export default Reyes; 27 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/Rise.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from './BaseFilters'; 2 | 3 | const COLOR_FILTER_CONST = [255, 170, 0, 0.1]; 4 | const BRIGHTNESS_CONST = 0.09; 5 | const SATURATION_CONST = 0.1; 6 | 7 | /** 8 | * Rise Filter. 9 | * @function 10 | * @param {Object} imageData 11 | * @example 12 | * node.cache(); 13 | * node.filters([Rise]); 14 | */ 15 | function Rise(imageData) { 16 | BaseFilters.apply( 17 | imageData, 18 | BaseFilters.colorFilter(COLOR_FILTER_CONST), 19 | BaseFilters.brightness(BRIGHTNESS_CONST), 20 | BaseFilters.saturation(SATURATION_CONST), 21 | ); 22 | } 23 | 24 | Rise.filterName = 'Rise'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 25 | 26 | export default Rise; 27 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/Sierra.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from './BaseFilters'; 2 | 3 | const CONTRAST_CONST = -0.15; 4 | const SATURATION_CONST = 0.1; 5 | 6 | /** 7 | * Sierra Filter. 8 | * @function 9 | * @param {Object} imageData 10 | * @example 11 | * node.cache(); 12 | * node.filters([Sierra]); 13 | */ 14 | function Sierra(imageData) { 15 | BaseFilters.apply( 16 | imageData, 17 | BaseFilters.contrast(CONTRAST_CONST), 18 | BaseFilters.saturation(SATURATION_CONST), 19 | ); 20 | } 21 | 22 | Sierra.filterName = 'Sierra'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 23 | 24 | export default Sierra; 25 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/Skyline.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from './BaseFilters'; 2 | 3 | const SATURATION_CONST = 0.35; 4 | const BRIGHTNESS_CONST = 0.1; 5 | 6 | /** 7 | * Skyline Filter. 8 | * @function 9 | * @param {Object} imageData 10 | * @example 11 | * node.cache(); 12 | * node.filters([Skyline]); 13 | */ 14 | function Skyline(imageData) { 15 | BaseFilters.apply( 16 | imageData, 17 | BaseFilters.saturation(SATURATION_CONST), 18 | BaseFilters.brightness(BRIGHTNESS_CONST), 19 | ); 20 | } 21 | 22 | Skyline.filterName = 'Skyline'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 23 | 24 | export default Skyline; 25 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/Slumber.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from './BaseFilters'; 2 | 3 | const BRIGHTNESS_CONST = 0.1; 4 | const SATURATION_CONST = -0.5; 5 | 6 | /** 7 | * Slumber Filter. 8 | * @function 9 | * @param {Object} imageData 10 | * @example 11 | * node.cache(); 12 | * node.filters([Slumber]); 13 | */ 14 | function Slumber(imageData) { 15 | BaseFilters.apply( 16 | imageData, 17 | BaseFilters.brightness(BRIGHTNESS_CONST), 18 | BaseFilters.saturation(SATURATION_CONST), 19 | ); 20 | } 21 | 22 | Slumber.filterName = 'Slumber'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 23 | 24 | export default Slumber; 25 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/Stinson.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from './BaseFilters'; 2 | 3 | const BRIGHTNESS_CONST = 0.1; 4 | const SEPIA_CONST = 0.3; 5 | 6 | /** 7 | * Stinson Filter. 8 | * @function 9 | * @param {Object} imageData 10 | * @example 11 | * node.cache(); 12 | * node.filters([Stinson]); 13 | */ 14 | function Stinson(imageData) { 15 | BaseFilters.apply( 16 | imageData, 17 | BaseFilters.brightness(BRIGHTNESS_CONST), 18 | BaseFilters.sepia(SEPIA_CONST), 19 | ); 20 | } 21 | 22 | Stinson.filterName = 'Stinson'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 23 | 24 | export default Stinson; 25 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/Sutro.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from './BaseFilters'; 2 | 3 | const BRIGHTNESS_CONST = -0.1; 4 | const SATURATION_CONST = -0.1; 5 | 6 | /** 7 | * Sutro Filter. 8 | * @function 9 | * @param {Object} imageData 10 | * @example 11 | * node.cache(); 12 | * node.filters([Sutro]); 13 | */ 14 | function Sutro(imageData) { 15 | BaseFilters.apply( 16 | imageData, 17 | BaseFilters.brightness(BRIGHTNESS_CONST), 18 | BaseFilters.saturation(SATURATION_CONST), 19 | ); 20 | } 21 | 22 | Sutro.filterName = 'Sutro'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 23 | 24 | export default Sutro; 25 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/Toaster.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from './BaseFilters'; 2 | 3 | const SEPIA_CONST = 0.1; 4 | const COLOR_FILTER_CONST = [255, 145, 0, 0.2]; 5 | 6 | /** 7 | * Toaster Filter. 8 | * @function 9 | * @param {Object} imageData 10 | * @example 11 | * node.cache(); 12 | * node.filters([Toaster]); 13 | */ 14 | function Toaster(imageData) { 15 | BaseFilters.apply( 16 | imageData, 17 | BaseFilters.sepia(SEPIA_CONST), 18 | BaseFilters.colorFilter(COLOR_FILTER_CONST), 19 | ); 20 | } 21 | 22 | Toaster.filterName = 'Toaster'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 23 | 24 | export default Toaster; 25 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/Valencia.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from './BaseFilters'; 2 | 3 | const COLOR_FILTER_CONST = [255, 225, 80, 0.08]; 4 | const SATURATION_CONST = 0.1; 5 | const CONTRAST_CONST = 0.05; 6 | 7 | /** 8 | * Valencia Filter. 9 | * @function 10 | * @param {Object} imageData 11 | * @example 12 | * node.cache(); 13 | * node.filters([Valencia]); 14 | */ 15 | function Valencia(imageData) { 16 | BaseFilters.apply( 17 | imageData, 18 | BaseFilters.colorFilter(COLOR_FILTER_CONST), 19 | BaseFilters.saturation(SATURATION_CONST), 20 | BaseFilters.contrast(CONTRAST_CONST), 21 | ); 22 | } 23 | 24 | Valencia.filterName = 'Valencia'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 25 | 26 | export default Valencia; 27 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/Vesper.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from './BaseFilters'; 2 | 3 | const COLOR_FILTER_CONST = [255, 225, 0, 0.05]; 4 | const BRIGHTNESS_CONST = 0.06; 5 | const CONTRAST_CONST = 0.06; 6 | 7 | /** 8 | * Vesper Filter. 9 | * @function 10 | * @param {Object} imageData 11 | * @example 12 | * node.cache(); 13 | * node.filters([Vesper]); 14 | */ 15 | function Vesper(imageData) { 16 | BaseFilters.apply( 17 | imageData, 18 | BaseFilters.colorFilter(COLOR_FILTER_CONST), 19 | BaseFilters.brightness(BRIGHTNESS_CONST), 20 | BaseFilters.contrast(CONTRAST_CONST), 21 | ); 22 | } 23 | 24 | Vesper.filterName = 'Vesper'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 25 | 26 | export default Vesper; 27 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/Walden.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from './BaseFilters'; 2 | 3 | const BRIGHTNESS_CONST = 0.1; 4 | const COLOR_FILTER_CONST = [255, 255, 0, 0.2]; 5 | 6 | /** 7 | * Walden Filter. 8 | * @function 9 | * @param {Object} imageData 10 | * @example 11 | * node.cache(); 12 | * node.filters([Walden]); 13 | */ 14 | function Walden(imageData) { 15 | BaseFilters.apply( 16 | imageData, 17 | BaseFilters.brightness(BRIGHTNESS_CONST), 18 | BaseFilters.colorFilter(COLOR_FILTER_CONST), 19 | ); 20 | } 21 | 22 | Walden.filterName = 'Walden'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 23 | 24 | export default Walden; 25 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/Willow.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from './BaseFilters'; 2 | 3 | const COLOR_FILTER_CONST = [100, 28, 210, 0.03]; 4 | const BRIGHTNESS_CONST = 0.1; 5 | 6 | /** 7 | * Willow Filter. 8 | * @function 9 | * @param {Object} imageData 10 | * @example 11 | * node.cache(); 12 | * node.filters([Willow]); 13 | */ 14 | function Willow(imageData) { 15 | BaseFilters.apply( 16 | imageData, 17 | BaseFilters.grayscale(), 18 | BaseFilters.colorFilter(COLOR_FILTER_CONST), 19 | BaseFilters.brightness(BRIGHTNESS_CONST), 20 | ); 21 | } 22 | 23 | Willow.filterName = 'Willow'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 24 | 25 | export default Willow; 26 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/filters/XPro2.js: -------------------------------------------------------------------------------- 1 | import BaseFilters from './BaseFilters'; 2 | 3 | const COLOR_FILTER_CONST = [255, 255, 0, 0.07]; 4 | const SATURATION_CONST = 0.2; 5 | const CONTRAST_CONST = 0.15; 6 | 7 | /** 8 | * XPro2 Filter. 9 | * @function 10 | * @param {Object} imageData 11 | * @example 12 | * node.cache(); 13 | * node.filters([XPro2]); 14 | */ 15 | function XPro2(imageData) { 16 | BaseFilters.apply( 17 | imageData, 18 | BaseFilters.colorFilter(COLOR_FILTER_CONST), 19 | BaseFilters.saturation(SATURATION_CONST), 20 | BaseFilters.contrast(CONTRAST_CONST), 21 | ); 22 | } 23 | 24 | XPro2.filterName = 'XPro2'; // We assign the filter name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 25 | 26 | export default XPro2; 27 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/finetunes/CustomThreshold.js: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import Konva from 'konva'; 3 | import { Factory as KonvaFactory } from 'konva/lib/Factory'; 4 | import { getNumberValidator as konvaGetNumberValidator } from 'konva/lib/Validators'; 5 | 6 | /** 7 | * CustomThreshold Filter. 8 | * @function 9 | * @param {Object} imageData 10 | * @example 11 | * node.cache(); 12 | * node.filters([CustomThreshold]); 13 | * node.threshold(100); 14 | */ 15 | function CustomThreshold(imageData) { 16 | const thresholdValue = this.threshold(); 17 | const isZeroThreshold = thresholdValue === 0; 18 | const pixels = imageData.data; // [0, 1, 2, 3,...] => [r, g, b, a, ...] 19 | const len = pixels.length; 20 | 21 | for (let i = 0; i < len; i += 4) { 22 | if (!isZeroThreshold) { 23 | pixels[i] = pixels[i] >= thresholdValue ? 255 : 0; 24 | pixels[i + 1] = pixels[i + 1] >= thresholdValue ? 255 : 0; 25 | pixels[i + 2] = pixels[i + 2] >= thresholdValue ? 255 : 0; 26 | } 27 | } 28 | } 29 | 30 | CustomThreshold.finetuneName = 'CustomThreshold'; // We assign the finetune name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 31 | 32 | export default CustomThreshold; 33 | 34 | /** 35 | * adds threshold parameter (0 - 255), 0 means no value... 255 max value. 36 | */ 37 | KonvaFactory.addGetterSetter( 38 | Konva.Image, 39 | 'threshold', 40 | 0, 41 | konvaGetNumberValidator, 42 | KonvaFactory.afterSetFilter, 43 | ); 44 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/finetunes/Warmth.js: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import Konva from 'konva'; 3 | import { Factory as KonvaFactory } from 'konva/lib/Factory'; 4 | import { getNumberValidator as konvaGetNumberValidator } from 'konva/lib/Validators'; 5 | 6 | /** 7 | * Warmth Filter. 8 | * @function 9 | * @param {Object} imageData 10 | * @example 11 | * node.cache(); 12 | * node.filters([Warmth]); 13 | * node.warmth(100); 14 | * Red (r) > Blue (b) means warmer effect 15 | * Red (r) < Blue (b) means cooler effect 16 | */ 17 | function Warmth(imageData) { 18 | const warmthValue = this.warmth(); 19 | const pixels = imageData.data; // [0, 1, 2, 3,...] => [r, g, b, a, ...] 20 | const len = pixels.length; 21 | 22 | for (let i = 0; i < len; i += 4) { 23 | // red 24 | pixels[i] += warmthValue; 25 | // blue 26 | pixels[i + 2] -= warmthValue; 27 | } 28 | } 29 | 30 | Warmth.finetuneName = 'Warmth'; // We assign the finetune name here instead of using the fn. name as on prod. code the fn. name is optimized that might cause bug in that case. 31 | 32 | export default Warmth; 33 | 34 | /** 35 | * adds warmth parameter (0 - 200), 0 means no value... 200 max value. 36 | */ 37 | KonvaFactory.addGetterSetter( 38 | Konva.Image, 39 | 'warmth', 40 | 0, 41 | konvaGetNumberValidator(), 42 | KonvaFactory.afterSetFilter, 43 | ); 44 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/custom/finetunes/index.js: -------------------------------------------------------------------------------- 1 | export { default as Warmth } from './Warmth'; 2 | 3 | export { default as CustomThreshold } from './CustomThreshold'; 4 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/hooks/index.js: -------------------------------------------------------------------------------- 1 | export { default as useFinetune } from './useFinetune'; 2 | 3 | export { default as useFilter } from './useFilter'; 4 | 5 | export { default as useAnnotation } from './useAnnotation'; 6 | 7 | export { default as useAppReducer } from './useAppReducer'; 8 | 9 | export { default as useAnnotationEvents } from './useAnnotationEvents'; 10 | 11 | export { default as useResizeObserver } from './useResizeObserver'; 12 | 13 | export { default as useDebouncedCallback } from './useDebouncedCallback'; 14 | 15 | export { default as useStore } from './useStore'; 16 | 17 | export { default as useDrag } from './useDrag'; 18 | 19 | export { default as usePhoneScreen } from './usePhoneScreen'; 20 | 21 | export { default as useTransformedImgData } from './useTransformedImgData'; 22 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/hooks/useAnnotation/getBoundingRectUnScaled.js: -------------------------------------------------------------------------------- 1 | const getBoundingRectUnScaled = ( 2 | pointerOffsets = {}, 3 | pointerDown = {}, 4 | previewGroup, 5 | ) => { 6 | const boundingRect = {}; 7 | const parentAttrs = previewGroup.parent.attrs; 8 | boundingRect.x = 9 | Math.min(pointerOffsets.offsetX, pointerDown.startedX) - 10 | parentAttrs.xPadding || 0; 11 | boundingRect.y = 12 | Math.min(pointerOffsets.offsetY, pointerDown.startedY) - 13 | parentAttrs.yPadding || 0; 14 | boundingRect.width = pointerOffsets.offsetX - pointerDown.startedX; 15 | boundingRect.height = pointerOffsets.offsetY - pointerDown.startedY; 16 | boundingRect.startedX = pointerDown.startedX - parentAttrs.xPadding || 0; 17 | boundingRect.startedY = pointerDown.startedY - parentAttrs.yPadding || 0; 18 | 19 | return boundingRect; 20 | }; 21 | 22 | export default getBoundingRectUnScaled; 23 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/hooks/useDebouncedCallback.js: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import { useCallback } from 'react'; 3 | 4 | /** Internal Dependencies */ 5 | import debounce from 'utils/debounce'; 6 | 7 | const useDebouncedCallback = (func, timeout, dependencies = []) => 8 | useCallback(debounce(func, timeout), dependencies); 9 | 10 | export default useDebouncedCallback; 11 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/hooks/useDrag.js: -------------------------------------------------------------------------------- 1 | const useDrag = (onMove, onStart, onEnd) => { 2 | const onDragging = (e) => { 3 | if (typeof onMove === 'function') { 4 | onMove(e.touches?.[0] || e); 5 | } 6 | }; 7 | 8 | const disableSliding = (e) => { 9 | document.removeEventListener('mousemove', onDragging); 10 | document.removeEventListener('mouseup', disableSliding); 11 | document.removeEventListener('mouseleave', disableSliding); 12 | document.removeEventListener('touchmove', onDragging); 13 | document.removeEventListener('touchend', disableSliding); 14 | document.removeEventListener('touchcancel', disableSliding); 15 | 16 | if (typeof onEnd === 'function') { 17 | onEnd(e.touches?.[0] || e); 18 | } 19 | }; 20 | 21 | const enableDrag = (e) => { 22 | document.addEventListener('mousemove', onDragging); 23 | document.addEventListener('mouseup', disableSliding); 24 | document.addEventListener('mouseleave', disableSliding); 25 | document.addEventListener('touchmove', onDragging); 26 | document.addEventListener('touchend', disableSliding); 27 | document.addEventListener('touchcancel', disableSliding); 28 | 29 | if (typeof onStart === 'function') { 30 | onStart(e.touches?.[0] || e); 31 | } 32 | }; 33 | 34 | return { 35 | onMouseDown: enableDrag, 36 | onTouchStart: enableDrag, 37 | }; 38 | }; 39 | 40 | export default useDrag; 41 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/hooks/useFilter.js: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import { useCallback, useMemo } from 'react'; 3 | 4 | /** Internal Dependencies */ 5 | import { ADD_FILTER } from 'actions'; 6 | import useStore from './useStore'; 7 | 8 | const useFilter = () => { 9 | const { dispatch, filter } = useStore(); 10 | 11 | const setFilter = useCallback((filterToApply) => { 12 | dispatch({ 13 | type: ADD_FILTER, 14 | payload: { 15 | filter: filterToApply, 16 | }, 17 | }); 18 | }, []); 19 | 20 | return useMemo(() => [filter, setFilter], [filter]); 21 | }; 22 | 23 | export default useFilter; 24 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/hooks/useFinetune.js: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import { useCallback, useEffect, useMemo } from 'react'; 3 | 4 | /** Internal Dependencies */ 5 | import { SET_FINETUNE } from 'actions'; 6 | import isDefaultZeroValuesOnly from 'utils/isDefaultZeroValuesOnly'; 7 | import useStore from './useStore'; 8 | 9 | const useFinetune = (finetune, initialProps) => { 10 | const { dispatch, finetunes, finetunesProps } = useStore(); 11 | 12 | const setFinetuneWithProps = useCallback((newFinetuneProps) => { 13 | dispatch({ 14 | type: SET_FINETUNE, 15 | payload: { 16 | finetune, 17 | finetuneProps: newFinetuneProps, 18 | }, 19 | }); 20 | }, []); 21 | 22 | useEffect(() => { 23 | if (!finetunes.includes(finetune) && !isDefaultZeroValuesOnly(initialProps, finetunesProps)) { 24 | // initialProps first if we've any similar prop set before w/ diff. val don't override. 25 | setFinetuneWithProps({ 26 | ...initialProps, 27 | ...finetunesProps, 28 | }); 29 | } 30 | }, []); 31 | 32 | return useMemo( 33 | () => [finetunesProps, setFinetuneWithProps], 34 | [finetunesProps], 35 | ); 36 | }; 37 | 38 | export default useFinetune; 39 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/hooks/usePhoneScreen.js: -------------------------------------------------------------------------------- 1 | const usePhoneScreen = (screenWidth = 438) => 2 | window.matchMedia(`(max-width: ${screenWidth}px)`).matches; 3 | 4 | export default usePhoneScreen; 5 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/hooks/useStore.js: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import { useContext } from 'react'; 3 | 4 | /** Internal Dependencies */ 5 | import AppContext from 'context'; 6 | 7 | const useStore = () => useContext(AppContext); 8 | export default useStore; 9 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/hooks/useUpdateEffect.js: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import { useEffect, useRef } from 'react'; 3 | 4 | const useUpdateEffect = (effectCallback, dependencies) => { 5 | const isFirstRender = useRef(true); 6 | 7 | useEffect( 8 | () => () => { 9 | isFirstRender.current = true; 10 | }, 11 | [], 12 | ); 13 | 14 | useEffect(() => { 15 | if (isFirstRender.current) { 16 | isFirstRender.current = false; 17 | } else if (typeof effectCallback === 'function') { 18 | return effectCallback(); 19 | } 20 | 21 | return undefined; 22 | }, dependencies); 23 | }; 24 | 25 | export default useUpdateEffect; 26 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/index.js: -------------------------------------------------------------------------------- 1 | /** Internal Dependencies */ 2 | import FilerobotImageEditor from 'components/AssemblyPoint'; 3 | import { TABS_IDS, TOOLS_IDS } from 'utils/constants'; 4 | 5 | export { TABS_IDS as TABS, TOOLS_IDS as TOOLS }; 6 | 7 | export default FilerobotImageEditor; 8 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/utils/assignFinetuneNamesToKonva.js: -------------------------------------------------------------------------------- 1 | const assignFinetuneNamesToKonva = () => { 2 | Object.keys(Konva.Filters).forEach((key) => Konva.Filters[key].finetuneName = key) 3 | } 4 | 5 | export default assignFinetuneNamesToKonva; 6 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/utils/calculateZoomData.js: -------------------------------------------------------------------------------- 1 | import { DEFAULT_ZOOM_FACTOR } from './constants'; 2 | 3 | const calculateZoomData = (newZoom, oldZoom, canvasWidth, canvasHeight) => { 4 | if (newZoom.factor === DEFAULT_ZOOM_FACTOR) { 5 | return { 6 | x: 0, 7 | y: 0, 8 | factor: DEFAULT_ZOOM_FACTOR, 9 | }; 10 | } 11 | 12 | const isZoomIn = newZoom.factor > oldZoom.factor; 13 | const mousePointTo = { 14 | x: (newZoom.x - oldZoom.x || 0) / oldZoom.factor, 15 | y: (newZoom.y - oldZoom.y || 0) / oldZoom.factor, 16 | }; 17 | 18 | const newPos = { 19 | x: newZoom.x - mousePointTo.x * newZoom.factor, 20 | y: newZoom.y - mousePointTo.y * newZoom.factor, 21 | }; 22 | if (!isZoomIn || oldZoom.factor !== 1) { 23 | newPos.x = Math.min( 24 | 0, 25 | Math.max(newPos.x, canvasWidth * (1 - oldZoom.factor)), 26 | ); 27 | newPos.y = Math.min( 28 | 0, 29 | Math.max(newPos.y, canvasHeight * (1 - oldZoom.factor)), 30 | ); 31 | } 32 | 33 | if (newZoom.factor < 1) { 34 | const initialAndScaledWidthDiff = 35 | canvasWidth - canvasWidth * newZoom.factor; 36 | const initialAndScaledHeightDiff = 37 | canvasHeight - canvasHeight * newZoom.factor; 38 | newPos.x += initialAndScaledWidthDiff / 2; 39 | newPos.y += initialAndScaledHeightDiff / 2; 40 | } 41 | 42 | return { 43 | ...newPos, 44 | factor: newZoom.factor, 45 | }; 46 | }; 47 | 48 | export default calculateZoomData; 49 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/utils/compareRatios.js: -------------------------------------------------------------------------------- 1 | import toPrecisedFloat from './toPrecisedFloat'; 2 | 3 | const compareRatios = (ratio1, ratio2) => 4 | toPrecisedFloat(ratio1) === toPrecisedFloat(ratio2); 5 | 6 | export default compareRatios; 7 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/utils/cropImage.js: -------------------------------------------------------------------------------- 1 | import { ELLIPSE_CROP } from './constants'; 2 | 3 | const cropImage = (context, cropBox, noEllipticalCrop = false) => { 4 | if (cropBox.ratio === ELLIPSE_CROP && !noEllipticalCrop) { 5 | context.ellipse( 6 | cropBox.x + cropBox.width / 2, 7 | cropBox.y + cropBox.height / 2, 8 | cropBox.width / 2, 9 | cropBox.height / 2, 10 | 0, 11 | 0, 12 | 2 * Math.PI, 13 | ); 14 | } else { 15 | context.rect(cropBox.x, cropBox.y, cropBox.width, cropBox.height); 16 | } 17 | }; 18 | 19 | export default cropImage; 20 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/utils/debounce.js: -------------------------------------------------------------------------------- 1 | const debounce = (func, timeout = 300) => { 2 | let timer; 3 | 4 | return (...args) => { 5 | clearTimeout(timer); 6 | const [firstArg, ...otherArgs] = args; 7 | timer = setTimeout( 8 | func.bind(null, firstArg?.target?.value ?? firstArg, ...otherArgs), 9 | timeout, 10 | ); 11 | }; 12 | }; 13 | 14 | export default debounce; 15 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/utils/deepMerge.js: -------------------------------------------------------------------------------- 1 | const deepMerge = (source, object = {}, considerArray = false) => { 2 | const mergedObject = { ...source }; 3 | const keys = Object.keys(object); 4 | keys.forEach((k) => { 5 | // considers null in the values. 6 | const val = object[k]; 7 | if (val !== undefined) { 8 | const valType = typeof val; 9 | if ( 10 | valType !== 'object' || 11 | val instanceof HTMLElement || 12 | val === null || 13 | Array.isArray(val) || 14 | !source[k] || 15 | typeof source[k] !== 'object' 16 | ) { 17 | mergedObject[k] = 18 | considerArray && Array.isArray(mergedObject[k]) && Array.isArray(val) 19 | ? [...mergedObject[k], ...val] 20 | : val; 21 | return; 22 | } 23 | 24 | // After the above condition we now have both of them in type objects. 25 | mergedObject[k] = deepMerge(source[k], val); 26 | } 27 | }); 28 | 29 | return mergedObject; 30 | }; 31 | 32 | export default deepMerge; 33 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/utils/extractCurrentDesignState.js: -------------------------------------------------------------------------------- 1 | /** Internal Dependencies */ 2 | import defaultConfig from 'context/defaultConfig'; 3 | import getInitialAppState from 'context/getInitialAppState'; 4 | 5 | /** 6 | * Extracts the needed properties/state that are used in canvas's design from the global state. 7 | * 8 | * @param {Object} state The global state. 9 | * @param {Object} defaultValue Value assigned to any undefined/null property in returned object. 10 | * @returns {Object} The extracted design state. 11 | */ 12 | const extractCurrentDesignState = (state, useStateAsConfig) => { 13 | const initialAppState = getInitialAppState( 14 | useStateAsConfig ? state : defaultConfig, 15 | ); 16 | 17 | return { 18 | imgSrc: state.imgSrc || initialAppState.imgSrc, 19 | finetunes: state.finetunes || initialAppState.finetunes, 20 | finetunesProps: state.finetunesProps || initialAppState.finetunesProps, 21 | filter: state.filter || initialAppState.filter, 22 | adjustments: state.adjustments || initialAppState.adjustments, 23 | annotations: state.annotations || initialAppState.annotations, 24 | resize: state.resize || initialAppState.resize, 25 | }; 26 | }; 27 | 28 | export default extractCurrentDesignState; 29 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/utils/extractNameFromUrl.js: -------------------------------------------------------------------------------- 1 | const extractNameFromUrl = (url) => { 2 | const urlParts = url.split('/'); 3 | return urlParts[urlParts.length - 1].split('?')[0]; 4 | }; 5 | 6 | export default extractNameFromUrl; 7 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/utils/filterStrToClass.js: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import Konva from 'konva'; 3 | 4 | /** Internal Dependencies */ 5 | import * as CustomFilters from 'custom/filters'; 6 | 7 | const filterStrToClass = (filterString) => { 8 | if (filterString) { 9 | return CustomFilters[filterString] || Konva.Filters[filterString]; 10 | } 11 | 12 | return null; 13 | }; 14 | 15 | export default filterStrToClass; 16 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/utils/finetunesStrsToClasses.js: -------------------------------------------------------------------------------- 1 | /** External Dependencies */ 2 | import Konva from 'konva'; 3 | 4 | /** Internal Dependencies */ 5 | import * as CustomFinetunes from 'custom/finetunes'; 6 | 7 | const finetunesStrsToClasses = (finetunesStrings) => { 8 | if (Array.isArray(finetunesStrings) && finetunesStrings.length > 0) { 9 | return finetunesStrings.map( 10 | (finetuneClassName) => 11 | Konva.Filters[finetuneClassName] || CustomFinetunes[finetuneClassName], 12 | ); 13 | } 14 | 15 | return []; 16 | }; 17 | 18 | export default finetunesStrsToClasses; 19 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/utils/getCenterRotatedPoint.js: -------------------------------------------------------------------------------- 1 | const getRotatedPoint = ({ x, y }, angleDegree) => { 2 | const radianAngle = (angleDegree * Math.PI) / 180; 3 | const rcos = Math.cos(radianAngle); 4 | const rsin = Math.sin(radianAngle); 5 | return { x: x * rcos - y * rsin, y: y * rcos + x * rsin }; 6 | }; 7 | 8 | const getCenterRotatedPoint = (width, height, newRotationAngleDegree) => { 9 | if ( 10 | !width || 11 | !height || 12 | (!newRotationAngleDegree && newRotationAngleDegree !== 0) 13 | ) { 14 | return { 15 | x: 0, 16 | y: 0, 17 | rotation: newRotationAngleDegree, 18 | }; 19 | } 20 | const topLeft = { x: -width / 2, y: -height / 2 }; 21 | const current = getRotatedPoint(topLeft, 0); 22 | const rotated = getRotatedPoint(topLeft, newRotationAngleDegree); 23 | const dx = rotated.x - current.x; 24 | const dy = rotated.y - current.y; 25 | 26 | return { x: dx, y: dy, rotation: newRotationAngleDegree }; 27 | }; 28 | 29 | export default getCenterRotatedPoint; 30 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/utils/getDefaultSaveQuality.js: -------------------------------------------------------------------------------- 1 | import { DEFAULT_SAVE_QUALITY } from './constants'; 2 | 3 | const getDefaultSaveQuality = (providedDefaultQuality) => 4 | providedDefaultQuality <= 0 || providedDefaultQuality > 1 5 | ? DEFAULT_SAVE_QUALITY 6 | : providedDefaultQuality; 7 | 8 | export default getDefaultSaveQuality; 9 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/utils/getDimensionsMinimalRatio.js: -------------------------------------------------------------------------------- 1 | const getDimensionsMinimalRatio = ( 2 | firstWidth, 3 | firstHeight, 4 | secondWidth, 5 | secondHeight, 6 | ) => { 7 | const widthScale = firstWidth / secondWidth; 8 | const heightScale = firstHeight / secondHeight; 9 | 10 | return Math.min(widthScale, heightScale) || 1; 11 | }; 12 | 13 | export default getDimensionsMinimalRatio; 14 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/utils/getElemDocumentCoords.js: -------------------------------------------------------------------------------- 1 | /** Internal Dependencies */ 2 | import getScrollOffset from './getScrollOffset'; 3 | 4 | const getElemDocumentCoords = (elem) => { 5 | if (!elem) { 6 | return null; 7 | } 8 | const box = elem.getBoundingClientRect(); 9 | 10 | const { body } = document; 11 | const { topOffset, leftOffset } = getScrollOffset(); 12 | 13 | const docEl = document.documentElement; 14 | const clientTop = docEl.clientTop || body.clientTop || 0; 15 | const clientLeft = docEl.clientLeft || body.clientLeft || 0; 16 | 17 | const top = box.top + topOffset - clientTop; 18 | const left = box.left + leftOffset - clientLeft; 19 | 20 | return { 21 | top: Math.round(top), 22 | left: Math.round(left), 23 | width: box.width, 24 | height: box.height, 25 | }; 26 | }; 27 | 28 | export default getElemDocumentCoords; 29 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/utils/getFileFullName.js: -------------------------------------------------------------------------------- 1 | import { 2 | DEFAULT_IMAGE_TYPE, 3 | POSSIBLE_IMAGE_TYPES, 4 | SUPPORTED_IMAGE_TYPES, 5 | } from './constants'; 6 | 7 | const getFileFullName = (fileName = '', appendedExtension) => { 8 | let finalExtension = appendedExtension; 9 | let finalFileName = fileName; 10 | if ( 11 | !finalExtension && 12 | POSSIBLE_IMAGE_TYPES.some( 13 | (extension) => 14 | fileName.lastIndexOf(`.${extension}`) === 15 | fileName.length - `.${extension}`.length, 16 | ) 17 | ) { 18 | const currentExtension = fileName 19 | .slice(fileName.lastIndexOf('.') + 1) 20 | ?.toLowerCase(); 21 | finalExtension = 22 | currentExtension && SUPPORTED_IMAGE_TYPES.includes(currentExtension) 23 | ? currentExtension 24 | : DEFAULT_IMAGE_TYPE; 25 | finalFileName = fileName.slice(0, fileName.lastIndexOf('.')); 26 | } 27 | 28 | finalExtension = finalExtension || DEFAULT_IMAGE_TYPE; 29 | 30 | return { 31 | fullName: `${finalFileName}.${finalExtension}`, 32 | name: finalFileName, 33 | extension: finalExtension, 34 | }; 35 | }; 36 | 37 | export default getFileFullName; 38 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/utils/getImageSealingParams.js: -------------------------------------------------------------------------------- 1 | /** Internal Dependencies */ 2 | import sha1 from './sha1'; 3 | 4 | const encodeBase64 = (str) => { 5 | return btoa(str).replace(/=*$/g, ''); 6 | }; 7 | 8 | const getSha1 = (str, length) => { 9 | return sha1(str).slice(0, length); 10 | }; 11 | 12 | const getSealingParams = (paramsStr, originalUrl, salt, charCount) => { 13 | const base64String = encodeBase64(paramsStr); 14 | const calcHash = getSha1(originalUrl + base64String + salt, charCount); 15 | 16 | return [ 17 | calcHash ? `ci_seal=${calcHash}` : '', 18 | base64String ? `ci_eqs=${base64String}` : '', 19 | ] 20 | .filter((i) => i) 21 | .join('&'); 22 | }; 23 | 24 | const getImageSealingParams = (paramsStr, imageSealing, originalUrl) => { 25 | const { salt, charCount, includeParams = [] } = imageSealing || {}; 26 | const isIncludeParamsEmpty = !includeParams || includeParams?.length === 0; 27 | 28 | let sealingParamsStr = ''; 29 | let restParamsStr = ''; 30 | 31 | const sealingParams = []; 32 | const restParams = []; 33 | 34 | paramsStr.split('&').forEach((item) => { 35 | const [paramName] = item.split('='); 36 | 37 | if (includeParams?.indexOf(paramName) > -1 || isIncludeParamsEmpty) { 38 | sealingParams.push(item); 39 | } else { 40 | restParams.push(item); 41 | } 42 | }); 43 | 44 | if (restParams.length > 0) { 45 | restParamsStr = restParams.join('&'); 46 | } 47 | 48 | // We need to add sealing always, even if sealingParams is empty. 49 | // In case with empty params sealing will be like: ci_seal=10613a92e5 50 | sealingParamsStr = getSealingParams( 51 | sealingParams.join('&'), 52 | originalUrl, 53 | salt, 54 | charCount, 55 | ); 56 | 57 | return [sealingParamsStr, restParamsStr].filter((p) => p).join('&'); 58 | }; 59 | 60 | export default getImageSealingParams; 61 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/utils/getPointerOffsetPositionBoundedToObject.js: -------------------------------------------------------------------------------- 1 | /** Internal Dependencies */ 2 | import restrictNumber from './restrictNumber'; 3 | 4 | /** 5 | * Gets the touch/mouse position relative to the passed object to be considred as offset X/Y. 6 | * 7 | * @param {Object} previewGroup - The preview group that is a direct child of the design layer 8 | * @param {Object} relativeToObject - The object to be considered as parent element 9 | * contains left, top, width & height relative to the document. 10 | * @returns {Object} both X & Y offset values. 11 | */ 12 | const getPointerOffsetPositionBoundedToObject = ( 13 | previewGroup = {}, 14 | relativeToObject = {}, 15 | ) => { 16 | const designLayer = previewGroup.parent; 17 | const canvas = designLayer.getStage(); 18 | const canvasZoomFactor = canvas.attrs.zoomFactor; 19 | const pos = designLayer.getRelativePointerPosition(); 20 | 21 | return { 22 | offsetX: 23 | restrictNumber( 24 | pos.x, 25 | 0, 26 | relativeToObject.width / (canvas.scaleX() / canvasZoomFactor), 27 | ) + designLayer.attrs.xPadding, 28 | offsetY: 29 | restrictNumber( 30 | pos.y, 31 | 0, 32 | relativeToObject.height / (canvas.scaleY() / canvasZoomFactor), 33 | ) + designLayer.attrs.yPadding, 34 | }; 35 | }; 36 | 37 | export default getPointerOffsetPositionBoundedToObject; 38 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/utils/getProperDimensions.js: -------------------------------------------------------------------------------- 1 | /** Internal Dependencies */ 2 | import getSizeAfterRotation from './getSizeAfterRotation'; 3 | import mapCropBox from './mapCropBox'; 4 | 5 | const getProperDimensions = ( 6 | resizeDimensions, 7 | cropDimensions, 8 | shownImageDimensions, 9 | originalDimensions, 10 | rotationAngle = 0, 11 | ) => { 12 | if (resizeDimensions.width && resizeDimensions.height) { 13 | return resizeDimensions; 14 | } 15 | 16 | const mappedCropArea = mapCropBox( 17 | cropDimensions, 18 | shownImageDimensions, 19 | originalDimensions, 20 | ); 21 | const croppedRotatedArea = getSizeAfterRotation( 22 | mappedCropArea.width, 23 | mappedCropArea.height, 24 | rotationAngle, 25 | ); 26 | if (resizeDimensions.width || resizeDimensions.height) { 27 | return { 28 | width: resizeDimensions.width || croppedRotatedArea.width, 29 | height: resizeDimensions.height || croppedRotatedArea.height, 30 | }; 31 | } 32 | 33 | return ( 34 | (croppedRotatedArea.width && 35 | croppedRotatedArea.height && 36 | croppedRotatedArea) || { 37 | ...originalDimensions, 38 | ...getSizeAfterRotation( 39 | originalDimensions.width, 40 | originalDimensions.height, 41 | rotationAngle, 42 | ), 43 | } 44 | ); 45 | }; 46 | 47 | export default getProperDimensions; 48 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/utils/getProperImageToCanvasSpacing.js: -------------------------------------------------------------------------------- 1 | const SPACING_PERCENTAGE = 0.05; 2 | const DEFAULT_SPACING = 12; 3 | 4 | const getProperImageToCanvasSpacing = () => 5 | (window 6 | ? Math.min(window.innerHeight, window.innerWidth) * SPACING_PERCENTAGE 7 | : DEFAULT_SPACING) * 2; 8 | 9 | export default getProperImageToCanvasSpacing; 10 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/utils/getScrollOffset.js: -------------------------------------------------------------------------------- 1 | const getScrollOffset = () => { 2 | const { body } = document; 3 | const docEl = document.documentElement; 4 | 5 | const scrollTop = window?.pageYOffset || docEl.scrollTop || body.scrollTop; 6 | const scrollLeft = window?.pageXOffset || docEl.scrollLeft || body.scrollLeft; 7 | 8 | return { 9 | topOffset: scrollTop, 10 | leftOffset: scrollLeft, 11 | }; 12 | }; 13 | 14 | export default getScrollOffset; 15 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/utils/getSizeAfterRotation.js: -------------------------------------------------------------------------------- 1 | const getSizeAfterRotation = (width, height, rotationAngleInDegree = 0) => { 2 | const absRotationAngleInDegree = Math.abs(rotationAngleInDegree); 3 | const roundedDegree = Math.round(rotationAngleInDegree); 4 | const isGreaterThan90Degree = absRotationAngleInDegree > 90; 5 | const currentAbsRotationAngleInDegree = isGreaterThan90Degree 6 | ? absRotationAngleInDegree - 90 7 | : absRotationAngleInDegree; 8 | const currentWidth = isGreaterThan90Degree ? height : width; 9 | const currentHeight = isGreaterThan90Degree ? width : height; 10 | const radianAngle = (currentAbsRotationAngleInDegree * Math.PI) / 180; 11 | const sin = Math.abs(Math.sin(radianAngle)); 12 | const cos = Math.abs(Math.cos(radianAngle)); 13 | const getLeftOffset = () => 14 | roundedDegree > 90 15 | ? currentWidth * cos + currentHeight * sin 16 | : currentHeight * sin; 17 | const getTopOffset = () => { 18 | if (roundedDegree < 0 && roundedDegree > -90) { 19 | return currentWidth * sin; 20 | } 21 | if (roundedDegree > 90) { 22 | return currentWidth * sin; 23 | } 24 | return currentHeight * cos + currentWidth * sin; 25 | }; 26 | 27 | return { 28 | width: Math.round(currentWidth * cos) + Math.round(currentHeight * sin), 29 | height: Math.round(currentWidth * sin) + Math.round(currentHeight * cos), 30 | offsetTop: roundedDegree >= 0 && roundedDegree <= 90 ? 0 : getTopOffset(), 31 | offsetLeft: 32 | roundedDegree <= 0 && roundedDegree >= -90 ? 0 : getLeftOffset(), 33 | }; 34 | }; 35 | 36 | export default getSizeAfterRotation; 37 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/utils/getZoomFitFactor.js: -------------------------------------------------------------------------------- 1 | import { DEFAULT_ZOOM_FACTOR } from './constants'; 2 | 3 | const getZoomFitFactor = (previewDimens, originalDimens) => 4 | Math.min( 5 | previewDimens.width / originalDimens.width, 6 | previewDimens.height / originalDimens.height, 7 | ) || DEFAULT_ZOOM_FACTOR; 8 | 9 | export default getZoomFitFactor; 10 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/utils/imageToBase64.js: -------------------------------------------------------------------------------- 1 | const imageToBase64 = (image) => { 2 | if (image instanceof HTMLImageElement) { 3 | const canvas = document.createElement('canvas'); 4 | const ctx = canvas.getContext('2d'); 5 | canvas.width = image.width; 6 | canvas.height = image.height; 7 | ctx.drawImage(image, 0, 0); 8 | return canvas.toDataURL(); 9 | } 10 | 11 | return ''; 12 | }; 13 | 14 | export default imageToBase64; 15 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/utils/isDefaultZeroValuesOnly.js: -------------------------------------------------------------------------------- 1 | const isDefaultZeroValuesOnly = (initialProps, newProps) => ( 2 | initialProps && 3 | Object.keys(initialProps || {}).every((key) => initialProps[key] === 0) && 4 | newProps && 5 | Object.keys(newProps || {}).length === 0 6 | ) 7 | 8 | export default isDefaultZeroValuesOnly; 9 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/utils/isSameImage.js: -------------------------------------------------------------------------------- 1 | const isSameImage = (img1, img2HtmlElement) => 2 | img1 && 3 | img2HtmlElement && 4 | ((img1 instanceof HTMLImageElement && 5 | img1.src === img2HtmlElement.src && 6 | img1.width === img2HtmlElement.width && 7 | img1.height === img2HtmlElement.height) || 8 | (img1?.src || img1) === img2HtmlElement.src); 9 | 10 | export default isSameImage; 11 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/utils/loadImage.js: -------------------------------------------------------------------------------- 1 | import extractNameFromUrl from './extractNameFromUrl'; 2 | 3 | const loadImage = (imageSrc, imageFileName, noCrossOrigin = false) => 4 | new Promise((resolve, reject) => { 5 | const imageElement = new Image(); 6 | if (!noCrossOrigin) { 7 | imageElement.crossOrigin = 'Anonymous'; 8 | } 9 | imageElement.src = imageSrc; 10 | imageElement.name = imageFileName ?? extractNameFromUrl(imageSrc); 11 | imageElement.onload = () => { 12 | resolve(imageElement); 13 | }; 14 | imageElement.onerror = () => { 15 | reject( 16 | new Error( 17 | `Error in loading the image with the provided url: ${imageSrc}`, 18 | ), 19 | ); 20 | }; 21 | }); 22 | 23 | export default loadImage; 24 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/utils/mapCropBox.js: -------------------------------------------------------------------------------- 1 | import mapNumber from './mapNumber'; 2 | 3 | const mapCropBox = (crop, shownImageDimensions, toDimensions) => ({ 4 | ...(crop.x || crop.x === 0 5 | ? { 6 | x: Math.round( 7 | mapNumber( 8 | crop.x, 9 | 0, 10 | shownImageDimensions.width, // could replace with image node's dimensions from designLayer as they're same 11 | 0, 12 | toDimensions.width, 13 | ), 14 | ), 15 | } 16 | : {}), 17 | ...(crop.y || crop.y === 0 18 | ? { 19 | y: Math.round( 20 | mapNumber( 21 | crop.y, 22 | 0, 23 | shownImageDimensions.height, 24 | 0, 25 | toDimensions.height, 26 | ), 27 | ), 28 | } 29 | : {}), 30 | width: Math.round( 31 | mapNumber( 32 | crop.width ?? shownImageDimensions.width, 33 | 0, 34 | shownImageDimensions.width, 35 | 0, 36 | toDimensions.width, 37 | ), 38 | ), 39 | height: Math.round( 40 | mapNumber( 41 | crop.height ?? shownImageDimensions.height, 42 | 0, 43 | shownImageDimensions.height, 44 | 0, 45 | toDimensions.height, 46 | ), 47 | ), 48 | }); 49 | 50 | export default mapCropBox; 51 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/utils/mapNumber.js: -------------------------------------------------------------------------------- 1 | const mapNumber = (number, oldMin, oldMax, newMin, newMax) => 2 | ((number - oldMin) * (newMax - newMin)) / (oldMax - oldMin) + newMin; 3 | 4 | export default mapNumber; 5 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/utils/randomId.js: -------------------------------------------------------------------------------- 1 | const randomId = (prefixString = '') => 2 | `${prefixString}${prefixString ? '-' : ''}${parseInt( 3 | Date.now() * Math.random(), 4 | 10, 5 | )}`; 6 | 7 | export default randomId; 8 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/utils/restrictNumber.js: -------------------------------------------------------------------------------- 1 | const restrictNumber = (number, min = 0, max) => { 2 | // we are not assigning default value for it as if max was null it will override the default value. 3 | const currentMax = max || 1000000; 4 | const convertedNumber = +number; 5 | 6 | return Math.min(Math.max(min, convertedNumber), currentMax); 7 | }; 8 | 9 | export default restrictNumber; 10 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/utils/rgbaToHexa.js: -------------------------------------------------------------------------------- 1 | const rgbaToHexWithOpacity = (rgba = '') => { 2 | const defaultHexColor = { hex: '000000', opacity: 1 }; 3 | if (!rgba) { 4 | return defaultHexColor; 5 | } 6 | if (rgba.startsWith('#')) { 7 | return { hex: rgba.replace('#', ''), opacity: 1 }; 8 | } 9 | 10 | let [r, g, b, opacity] = rgba.split(','); 11 | if (!r || !g || !b) { 12 | return defaultHexColor; 13 | } 14 | r = parseFloat(r.replace(/rgba?\(/, '').trim()).toString(16); 15 | g = parseFloat(g.trim()).toString(16); 16 | b = parseFloat(b.trim()).toString(16); 17 | opacity = opacity ? parseFloat(opacity.trim() ?? 1) : undefined; 18 | 19 | if (r.length === 1) r = `0${r}`; 20 | if (g.length === 1) g = `0${g}`; 21 | if (b.length === 1) b = `0${b}`; 22 | 23 | return { 24 | hex: `${r}${g}${b}`, 25 | opacity, 26 | }; 27 | }; 28 | 29 | export default rgbaToHexWithOpacity; 30 | -------------------------------------------------------------------------------- /packages/react-filerobot-image-editor/src/utils/toPrecisedFloat.js: -------------------------------------------------------------------------------- 1 | const toPrecisedFloat = (number, precision = 5) => 2 | number && +parseFloat(number).toFixed(precision); 3 | 4 | export default toPrecisedFloat; 5 | -------------------------------------------------------------------------------- /public/assets/Ellipse 3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaleflex/filerobot-image-editor/3d198dd662b4f0ec004bb97d52c0a45048539334/public/assets/Ellipse 3.png -------------------------------------------------------------------------------- /public/assets/Hollow-Ellipse 3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaleflex/filerobot-image-editor/3d198dd662b4f0ec004bb97d52c0a45048539334/public/assets/Hollow-Ellipse 3.png -------------------------------------------------------------------------------- /public/assets/adding-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaleflex/filerobot-image-editor/3d198dd662b4f0ec004bb97d52c0a45048539334/public/assets/adding-icon.png -------------------------------------------------------------------------------- /public/assets/arrow-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaleflex/filerobot-image-editor/3d198dd662b4f0ec004bb97d52c0a45048539334/public/assets/arrow-icon.png -------------------------------------------------------------------------------- /public/assets/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaleflex/filerobot-image-editor/3d198dd662b4f0ec004bb97d52c0a45048539334/public/assets/arrow.png -------------------------------------------------------------------------------- /public/assets/check-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaleflex/filerobot-image-editor/3d198dd662b4f0ec004bb97d52c0a45048539334/public/assets/check-icon.png -------------------------------------------------------------------------------- /public/assets/copy-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaleflex/filerobot-image-editor/3d198dd662b4f0ec004bb97d52c0a45048539334/public/assets/copy-icon.png -------------------------------------------------------------------------------- /public/assets/down-arrow-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaleflex/filerobot-image-editor/3d198dd662b4f0ec004bb97d52c0a45048539334/public/assets/down-arrow-icon.png -------------------------------------------------------------------------------- /public/assets/git-stars.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/assets/github-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/assets/half-circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaleflex/filerobot-image-editor/3d198dd662b4f0ec004bb97d52c0a45048539334/public/assets/half-circle.png --------------------------------------------------------------------------------