From 17598f823f819e51beff5bcc17f8206abced073f Mon Sep 17 00:00:00 2001 From: lja1018 Date: Wed, 2 Dec 2020 17:30:21 +0900 Subject: [PATCH 1/2] env: add prettier --- .eslintrc.js | 52 +++++++++++++++++++++++++---------------------- .prettierrc | 13 ++++++++++++ package-lock.json | 49 +++++++++++++++++++++++++++++++++++++++++++- package.json | 3 +++ 4 files changed, 92 insertions(+), 25 deletions(-) create mode 100644 .prettierrc diff --git a/.eslintrc.js b/.eslintrc.js index ada7ba59b..06858d632 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,26 +1,30 @@ module.exports = { - "extends": "tui/es6", - "env": { - "browser": true, - "amd": true, - "node": true, - "jasmine": true, - "jquery": true, - "es6": true - }, - "globals": { - "fabric": true, - "tui": true, - "loadFixtures": true - }, - "parserOptions": { - "sourceType": "module" - }, - 'rules': { - indent: [2, 4, {SwitchCase: 1, ignoreComments: false, ImportDeclaration: 1, flatTernaryExpressions: false}], - 'prefer-destructuring': ['error', { - VariableDeclarator: {array: true, object: true}, - AssignmentExpression: {array: false, object: false} - }] - } + extends: ['tui/es6', 'plugin:prettier/recommended'], + plugins: ['prettier'], + env: { + browser: true, + amd: true, + node: true, + jasmine: true, + jquery: true, + es6: true, + }, + globals: { + fabric: true, + tui: true, + loadFixtures: true, + }, + parserOptions: { + sourceType: 'module', + }, + rules: { + 'prefer-destructuring': [ + 'error', + { + VariableDeclarator: { array: true, object: true }, + AssignmentExpression: { array: false, object: false }, + }, + ], + 'prettier/prettier': 'error', + }, }; diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000..8180f6934 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,13 @@ +{ + "singleQuote": true, + "printWidth": 100, + "tabWidth": 2, + "useTabs": false, + "semi": true, + "quoteProps": "as-needed", + "trailingComma": "es5", + "arrowParens": "always", + "endOfLine": "lf", + "bracketSpacing": true, + "proseWrap": "preserve" +} diff --git a/package-lock.json b/package-lock.json index 39eb4ea0b..e95c5e8c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "tui-image-editor", - "version": "3.10.0", + "version": "3.10.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -3785,6 +3785,23 @@ } } }, + "eslint-config-prettier": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.15.0.tgz", + "integrity": "sha512-a1+kOYLR8wMGustcgAjdydMsQ2A/2ipRPwRKUmfYaSxc9ZPcrku080Ctl6zrZzZNs/U82MjSv+qKREkoq3bJaw==", + "dev": true, + "requires": { + "get-stdin": "^6.0.0" + }, + "dependencies": { + "get-stdin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", + "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", + "dev": true + } + } + }, "eslint-config-tui": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/eslint-config-tui/-/eslint-config-tui-1.0.3.tgz", @@ -3804,6 +3821,15 @@ "rimraf": "^2.6.1" } }, + "eslint-plugin-prettier": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.4.tgz", + "integrity": "sha512-jZDa8z76klRqo+TdGDTFJSavwbnWK2ZpqGKNZ+VvweMW516pDUMmQ2koXvxEE4JhzNvTv+radye/bWGBmA6jmg==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, "eslint-scope": { "version": "3.7.3", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz", @@ -4176,6 +4202,12 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==" }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -8400,6 +8432,21 @@ "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", "dev": true }, + "prettier": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz", + "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==", + "dev": true + }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, "private": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", diff --git a/package.json b/package.json index 9f7782021..9e1fb61f2 100644 --- a/package.json +++ b/package.json @@ -33,8 +33,10 @@ "dtslint": "^0.4.2", "es5-shim": "^4.5.9", "eslint": "^4.5.0", + "eslint-config-prettier": "^6.15.0", "eslint-config-tui": "^1.0.1", "eslint-loader": "^2.0.0", + "eslint-plugin-prettier": "^3.1.4", "file-saver": "^1.3.3", "istanbul-instrumenter-loader": "^1.0.0", "jasmine-core": "^2.4.1", @@ -58,6 +60,7 @@ "mini-css-extract-plugin": "^0.9.0", "mkdirp": "^0.5.1", "optimize-css-assets-webpack-plugin": "^5.0.3", + "prettier": "^2.2.1", "safe-umd-webpack-plugin": "^4.0.0", "selenium-webdriver": "^4.0.0-alpha.7", "simulant": "^0.2.2", From b5f4a06b5ce7b511aa429146b727669ad579c8c9 Mon Sep 17 00:00:00 2001 From: lja1018 Date: Tue, 8 Dec 2020 16:08:50 +0900 Subject: [PATCH 2/2] chore: apply prettier --- .babelrc | 12 +- .github/ISSUE_TEMPLATE/bug_report.md | 18 +- .github/ISSUE_TEMPLATE/feature_request.md | 5 +- .github/ISSUE_TEMPLATE/question.md | 1 - CODE_OF_CONDUCT.md | 20 +- CONTRIBUTING.md | 50 +- ISSUE_TEMPLATE.md | 4 + README.md | 275 +- bower.json | 8 +- createConfigVariable.js | 37 +- docs/Apply-Mobile-Version-Image.md | 73 +- docs/Apply-Mobile-Version.md | 56 +- docs/Basic-Tutorial.md | 358 +-- docs/COMMIT_MESSAGE_CONVENTION.md | 44 +- docs/ISSUE_TEMPLATE.md | 6 +- docs/ImageEditor-2.0.0-Migration-guide.md | 143 +- docs/PULL_REQUEST_TEMPLATE.md | 6 +- docs/README.md | 6 +- docs/Reference.md | 41 +- docs/Structure.md | 156 +- examples/css/service-basic.css | 178 +- examples/css/service-mobile.css | 293 ++- examples/css/tui-example-style.css | 20 +- examples/example01-includeUi.html | 104 +- examples/example02-useApiDirect.html | 615 +++-- examples/example03-mobile.html | 421 +-- examples/js/service-basic.js | 1132 ++++---- examples/js/service-mobile.js | 724 ++--- examples/js/theme/black-theme.js | 124 +- examples/js/theme/white-theme.js | 124 +- index.d.ts | 578 ++-- jsdoc.conf.json | 9 +- karma.conf.js | 326 ++- makesvg.js | 40 +- src/js/action.js | 983 +++---- src/js/command/addIcon.js | 66 +- src/js/command/addImageObject.js | 46 +- src/js/command/addObject.js | 68 +- src/js/command/addShape.js | 80 +- src/js/command/addText.js | 94 +- src/js/command/applyFilter.js | 130 +- src/js/command/changeIconColor.js | 78 +- src/js/command/changeSelection.js | 40 +- src/js/command/changeShape.js | 102 +- src/js/command/changeText.js | 68 +- src/js/command/changeTextStyle.js | 98 +- src/js/command/clearObjects.js | 46 +- src/js/command/flip.js | 52 +- src/js/command/loadImage.js | 98 +- src/js/command/removeFilter.js | 56 +- src/js/command/removeObject.js | 54 +- src/js/command/resizeCanvasDimension.js | 58 +- src/js/command/rotate.js | 68 +- src/js/command/setObjectPosition.js | 82 +- src/js/command/setObjectProperties.js | 90 +- src/js/component/cropper.js | 637 ++--- src/js/component/filter.js | 388 +-- src/js/component/flip.js | 250 +- src/js/component/freeDrawing.js | 84 +- src/js/component/icon.js | 396 +-- src/js/component/imageLoader.js | 113 +- src/js/component/line.js | 354 +-- src/js/component/rotation.js | 136 +- src/js/component/shape.js | 1023 ++++---- src/js/component/text.js | 974 +++---- src/js/consts.js | 350 +-- src/js/drawingMode/cropper.js | 44 +- src/js/drawingMode/freeDrawing.js | 46 +- src/js/drawingMode/icon.js | 44 +- src/js/drawingMode/lineDrawing.js | 46 +- src/js/drawingMode/shape.js | 44 +- src/js/drawingMode/text.js | 44 +- src/js/extension/arrowLine.js | 126 +- src/js/extension/blur.js | 15 +- src/js/extension/colorFilter.js | 86 +- src/js/extension/cropzone.js | 654 ++--- src/js/extension/emboss.js | 17 +- src/js/extension/mask.js | 73 +- src/js/extension/sharpen.js | 17 +- src/js/factory/command.js | 16 +- src/js/factory/errorMessage.js | 35 +- src/js/graphics.js | 2571 +++++++++--------- src/js/helper/imagetracer.js | 2301 ++++++++-------- src/js/helper/selectionModifyHelper.js | 56 +- src/js/helper/shapeFilterFillHelper.js | 606 +++-- src/js/helper/shapeResizeHelper.js | 311 ++- src/js/imageEditor.js | 2922 ++++++++++----------- src/js/interface/command.js | 186 +- src/js/interface/component.js | 214 +- src/js/interface/drawingMode.js | 56 +- src/js/invoker.js | 481 ++-- src/js/polyfill.js | 863 +++--- src/js/ui.js | 1237 ++++----- src/js/ui/crop.js | 246 +- src/js/ui/draw.js | 288 +- src/js/ui/filter.js | 857 +++--- src/js/ui/flip.js | 130 +- src/js/ui/icon.js | 299 +-- src/js/ui/locale/locale.js | 22 +- src/js/ui/mask.js | 142 +- src/js/ui/rotate.js | 205 +- src/js/ui/shape.js | 422 +-- src/js/ui/submenuBase.js | 181 +- src/js/ui/template/controls.js | 4 +- src/js/ui/template/mainContainer.js | 12 +- src/js/ui/template/style.js | 44 +- src/js/ui/template/submenu/crop.js | 4 +- src/js/ui/template/submenu/draw.js | 4 +- src/js/ui/template/submenu/filter.js | 4 +- src/js/ui/template/submenu/flip.js | 4 +- src/js/ui/template/submenu/icon.js | 4 +- src/js/ui/template/submenu/mask.js | 4 +- src/js/ui/template/submenu/rotate.js | 4 +- src/js/ui/template/submenu/shape.js | 4 +- src/js/ui/template/submenu/text.js | 4 +- src/js/ui/text.js | 471 ++-- src/js/ui/theme/standard.js | 152 +- src/js/ui/theme/theme.js | 445 ++-- src/js/ui/tools/colorpicker.js | 449 ++-- src/js/ui/tools/range.js | 636 ++--- src/js/util.js | 234 +- test/.eslintrc.js | 6 +- test/action.spec.js | 820 +++--- test/arrowLine.spec.js | 110 +- test/command.spec.js | 902 ++++--- test/cropper.spec.js | 647 ++--- test/cropzone.spec.js | 299 ++- test/drawingMode.spec.js | 68 +- test/filter.spec.js | 107 +- test/flip.spec.js | 212 +- test/graphics.spec.js | 388 +-- test/icon.spec.js | 186 +- test/imageEditor.spec.js | 92 +- test/invoker.spec.js | 258 +- test/line.spec.js | 102 +- test/promiseApi.spec.js | 796 +++--- test/rotation.spec.js | 87 +- test/selectionModifyHelper.spec.js | 98 +- test/shape.spec.js | 895 ++++--- test/text.spec.js | 214 +- test/theme.spec.js | 161 +- test/types/tsconfig.json | 13 +- test/types/type-tests.ts | 366 +-- test/ui.spec.js | 339 +-- test/uiRange.spec.js | 77 +- tsBannerGenerator.js | 30 +- tslint.json | 17 +- tuidoc.config.json | 66 +- webpack.config.js | 205 +- 149 files changed, 20124 insertions(+), 18992 deletions(-) diff --git a/.babelrc b/.babelrc index f3ccf4a26..fdc3c986f 100644 --- a/.babelrc +++ b/.babelrc @@ -1,8 +1,8 @@ { - "presets": ["es2015"], - "plugins": [ - ["transform-es2015-destructuring", {"loose": true}], - ["transform-es2015-for-of", {"loose": true}], - ["transform-es2015-spread", {"loose": true}] - ] + "presets": ["es2015"], + "plugins": [ + ["transform-es2015-destructuring", { "loose": true }], + ["transform-es2015-for-of", { "loose": true }], + ["transform-es2015-spread", { "loose": true }] + ] } diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 85f1302e6..d8b2446cc 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -4,7 +4,6 @@ about: Create a report to help us improve title: '' labels: Bug assignees: '' - --- **Describe the bug** @@ -12,6 +11,7 @@ A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: + 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' @@ -24,15 +24,17 @@ A clear and concise description of what you expected to happen. If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - - OS: [e.g. iOS] - - Browser [e.g. chrome, safari] - - Version [e.g. 22] + +- OS: [e.g. iOS] +- Browser [e.g. chrome, safari] +- Version [e.g. 22] **Smartphone (please complete the following information):** - - Device: [e.g. iPhone6] - - OS: [e.g. iOS8.1] - - Browser [e.g. stock browser, safari] - - Version [e.g. 22] + +- Device: [e.g. iPhone6] +- OS: [e.g. iOS8.1] +- Browser [e.g. stock browser, safari] +- Version [e.g. 22] **Additional context** Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index caec98a0b..6201c2d88 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -4,7 +4,6 @@ about: Suggest an idea for this project title: '' labels: Enhancement, Need Discussion assignees: '' - --- ## Version + ## Development Environment + ## Current Behavior + ```js @@ -32,4 +34,5 @@ And you can write template's contents in Korean also. ``` ## Expected Behavior + diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index 62ff686c4..25a932239 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -4,7 +4,6 @@ about: Create a question about imageEditor title: '' labels: Question assignees: '' - --- ## Version + ## Development Environment + ## Current Behavior + ```js @@ -23,4 +26,5 @@ And you can write template's contents in Korean also. ``` ## Expected Behavior + diff --git a/README.md b/README.md index e78e63a40..8bb668b7e 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,47 @@ # ![Toast UI ImageEditor](https://user-images.githubusercontent.com/35218826/40895380-0b9f4cd6-67ea-11e8-982f-18121daa3a04.png) -> Full featured image editor using HTML5 Canvas. It's easy to use and provides powerful filters. +> Full featured image editor using HTML5 Canvas. It's easy to use and provides powerful filters. [![github version](https://img.shields.io/github/release/nhn/tui.image-editor.svg)](https://github.com/nhn/tui.image-editor/releases/latest) [![npm version](https://img.shields.io/npm/v/tui-image-editor.svg)](https://www.npmjs.com/package/tui-image-editor) [![bower version](https://img.shields.io/bower/v/tui.image-editor.svg)](https://github.com/nhn/tui.image-editor/releases/latest) [![license](https://img.shields.io/github/license/nhn/tui.image-editor.svg)](https://github.com/nhn/tui.image-editor/blob/master/LICENSE) [![PRs welcome](https://img.shields.io/badge/PRs-welcome-ff69b4.svg)](https://github.com/nhn/tui.image-editor/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22)[![code with hearth by NHN](https://img.shields.io/badge/%3C%2F%3E%20with%20%E2%99%A5%20by-NHN%20Entertainment-ff1414.svg)](https://github.com/nhn) ## Wrappers + - [toast-ui.vue-image-editor](https://github.com/nhn/toast-ui.vue-image-editor): Vue wrapper component is powered by [NHN](https://github.com/nhn). - [toast-ui.react-image-editor](https://github.com/nhn/toast-ui.react-image-editor): React wrapper component is powered by [NHN](https://github.com/nhn). ![6 -20-2018 17-45-54](https://user-images.githubusercontent.com/35218826/41647896-7b218ae0-74b2-11e8-90db-d7805cc23e8c.gif) ## 🚩 Table of Contents -* [Collect statistics on the use of open source](#Collect-statistics-on-the-use-of-open-source) -* [Browser Support](#-browser-support) -* [Has full features that stick to the basic.](#-has-full-features-that-stick-to-the-basic) - * [Photo manipulation](#photo-manipulation) - * [Integration function](#integration-function) - * [Powerful filter function](#powerful-filter-function) - * [Select only the desired function](#select-only-the-desired-function) -* [Easy to apply the size and design you want](#-easy-to-apply-the-size-and-design-you-want) - * [Can be used everywhere](#can-be-used-everywhere) - * [Nice default & Fully customizable Themes](#nice-default--fully-customizable-themes) -* [Features](#-features) -* [Install](#-install) - * [Via Package Manager](#via-package-manager) - * [Via Contents Delivery Network (CDN)](#via-contents-delivery-network-cdn) - * [Download Source Files](#download-source-files) -* [Usage](#-usage) - * [HTML](#html) - * [JavaScript](#javascript) - * [Menu svg icon setting](#menu-svg-icon-setting) - * [TypeScript](#-typescript) -* [Development](#-development) - * [Setup](#setup) - * [Run webpack-dev-server](#run-webpack-dev-server) -* [Documents](#-documents) -* [Contributing](#-contributing) -* [Dependency](#-dependency) -* [TOAST UI Family](#-toast-ui-family) -* [Used By](#-used-by) -* [License](#-license) +- [Collect statistics on the use of open source](#Collect-statistics-on-the-use-of-open-source) +- [Browser Support](#-browser-support) +- [Has full features that stick to the basic.](#-has-full-features-that-stick-to-the-basic) + - [Photo manipulation](#photo-manipulation) + - [Integration function](#integration-function) + - [Powerful filter function](#powerful-filter-function) + - [Select only the desired function](#select-only-the-desired-function) +- [Easy to apply the size and design you want](#-easy-to-apply-the-size-and-design-you-want) + - [Can be used everywhere](#can-be-used-everywhere) + - [Nice default & Fully customizable Themes](#nice-default--fully-customizable-themes) +- [Features](#-features) +- [Install](#-install) + - [Via Package Manager](#via-package-manager) + - [Via Contents Delivery Network (CDN)](#via-contents-delivery-network-cdn) + - [Download Source Files](#download-source-files) +- [Usage](#-usage) + - [HTML](#html) + - [JavaScript](#javascript) + - [Menu svg icon setting](#menu-svg-icon-setting) + - [TypeScript](#typescript) +- [Development](#-development) + - [Setup](#setup) + - [Run webpack-dev-server](#run-webpack-dev-server) +- [Documents](#-documents) +- [Contributing](#-contributing) +- [Dependency](#-dependency) +- [TOAST UI Family](#-toast-ui-family) +- [Used By](#-used-by) +- [License](#-license) ## Collect statistics on the use of open source @@ -48,9 +49,9 @@ TOAST UI ImageEditor applies Google Analytics (GA) to collect statistics on the ```js var options = { - //... - usageStatistics: false -} + //... + usageStatistics: false, +}; var imageEditor = new tui.ImageEditor('#tui-image-editor-container', options); ``` @@ -61,21 +62,24 @@ Or, include [`tui-code-snippet`](https://github.com/nhn/tui.code-snippet)(**v1.4 tui.usageStatistics = false; ``` - ## 🌏 Browser Support -| Chrome Chrome | IE Internet Explorer | Edge Edge | Safari Safari | Firefox Firefox | -| :---------: | :---------: | :---------: | :---------: | :---------: | -| Yes | 10+ | Yes | Yes | Yes | +| Chrome Chrome | IE Internet Explorer | Edge Edge | Safari Safari | Firefox Firefox | +| :--------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| Yes | 10+ | Yes | Yes | Yes | ## 💪 Has full features that stick to the basic. + ### Photo manipulation + - Crop, Flip, Rotation, Drawing, Shape, Icon, Text, Mask Filter, Image Filter - -### Integration function + +### Integration function + - Download, Image Load, Undo, Redo, Reset, Delete Object(Shape, Line, Mask Image...) + @@ -108,24 +112,21 @@ tui.usageStatistics = false;
Crop Flip
### Powerful filter function + - Grayscale, Invert, Sepia, Blur Sharpen, Emboss, RemoveWhite, Brightness, Noise, Pixelate, ColorFilter, Tint, Multiply, Blend -| Grayscale | Noise | Emboss | Pixelate | -| --- | --- | --- | --- | +| Grayscale | Noise | Emboss | Pixelate | +| ------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | ![grayscale](https://user-images.githubusercontent.com/35218826/41753470-930fb7b0-7608-11e8-9966-1c890e73d131.png) | ![noise](https://user-images.githubusercontent.com/35218826/41753458-9013bc82-7608-11e8-91d9-74dcc3ffce31.png) | ![emboss](https://user-images.githubusercontent.com/35218826/41753460-906c018a-7608-11e8-8861-c135c0117cea.png) | ![pixelate](https://user-images.githubusercontent.com/35218826/41753461-90a614a6-7608-11e8-97a7-0d3b7bb4aec4.png) | - -| Sepia | Sepia2 | Blend-righten | Blend-diff | Invert | -| --- | --- | --- | --- | --- | +| Sepia | Sepia2 | Blend-righten | Blend-diff | Invert | +| -------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | | ![sepia](https://user-images.githubusercontent.com/35218826/41753464-91acc41c-7608-11e8-8652-572f935ea704.png) | ![sepia2](https://user-images.githubusercontent.com/35218826/41753640-91e57248-7609-11e8-8960-145e0de57e39.png) | ![blend-righten](https://user-images.githubusercontent.com/35218826/41753462-9114bc3a-7608-11e8-9ab4-16ce20a34321.png) | ![blend-diff](https://user-images.githubusercontent.com/35218826/41753465-91e4baf2-7608-11e8-9b8f-79e1b956d387.png) | ![invert](https://user-images.githubusercontent.com/35218826/41753466-9260b224-7608-11e8-848a-73231a02ae3a.png) | -| Multifly | Tint | Brightness | Remove-white | Sharpen | -| --- | --- | --- | --- | --- | +| Multifly | Tint | Brightness | Remove-white | Sharpen | +| ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | | ![multifly](https://user-images.githubusercontent.com/35218826/41753467-92baae28-7608-11e8-80d2-187a310213f5.png) | ![tint](https://user-images.githubusercontent.com/35218826/41753468-92e6391c-7608-11e8-8977-651366ebe693.png) | ![brightness](https://user-images.githubusercontent.com/35218826/41753457-8fb3d3c6-7608-11e8-9e1d-10c6e4aeba68.png) | ![remove-white](https://user-images.githubusercontent.com/35218826/41753463-917feeb0-7608-11e8-862d-eb3af84e120a.png) | ![sharpen](https://user-images.githubusercontent.com/35218826/41753639-91b8470a-7609-11e8-8d13-48ac3232365b.png) | - - - ### Select only the desired function ```javascripot @@ -137,41 +138,39 @@ var imageEditor = new tui.ImageEditor('#tui-image-editor-container', { ... ``` - ## 🙆 Easy to apply the size and design you want ### Can be used everywhere. - - Widely supported in browsers including IE10. - - Option to support various display sizes. - (allows you to use the editor features on your web pages at least over **550 * 450 sizes**) - - ![2018-06-04 5 35 25](https://user-images.githubusercontent.com/35218826/40907369-9221f482-681e-11e8-801c-78d6f2e246a8.png) - -### Nice default & Fully customizable Themes - - Has a white and black theme, and you can modify the theme file to customize it. - - Has an API so that you can create your own instead of the built-in. +- Widely supported in browsers including IE10. +- Option to support various display sizes. + (allows you to use the editor features on your web pages at least over **550 \* 450 sizes**) -| black - top | black - bottom | white - left | white - right | -| --- | --- | --- | --- | -| ![2018-06-05 1 41 13](https://user-images.githubusercontent.com/35218826/40930753-8b72502e-6863-11e8-9cff-1719aee9aef0.png) | ![2018-06-05 1 40 24](https://user-images.githubusercontent.com/35218826/40930755-8bcee136-6863-11e8-8e28-0a6722d38c59.png) | ![2018-06-05 1 41 48](https://user-images.githubusercontent.com/35218826/40930756-8bfe0b50-6863-11e8-8682-bab11a0a2289.png) | ![2018-06-05 1 42 27](https://user-images.githubusercontent.com/35218826/40930754-8ba1dba0-6863-11e8-9439-cc059241b675.png) | + ![2018-06-04 5 35 25](https://user-images.githubusercontent.com/35218826/40907369-9221f482-681e-11e8-801c-78d6f2e246a8.png) +### Nice default & Fully customizable Themes +- Has a white and black theme, and you can modify the theme file to customize it. +- Has an API so that you can create your own instead of the built-in. +| black - top | black - bottom | white - left | white - right | +| --------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | +| ![2018-06-05 1 41 13](https://user-images.githubusercontent.com/35218826/40930753-8b72502e-6863-11e8-9cff-1719aee9aef0.png) | ![2018-06-05 1 40 24](https://user-images.githubusercontent.com/35218826/40930755-8bcee136-6863-11e8-8e28-0a6722d38c59.png) | ![2018-06-05 1 41 48](https://user-images.githubusercontent.com/35218826/40930756-8bfe0b50-6863-11e8-8682-bab11a0a2289.png) | ![2018-06-05 1 42 27](https://user-images.githubusercontent.com/35218826/40930754-8ba1dba0-6863-11e8-9439-cc059241b675.png) | ## 🎨 Features -* Load image to canvas -* Undo/Redo (With shortcut) -* Crop -* Flip -* Rotation -* Free drawing -* Line drawing -* Shape -* Icon -* Text -* Mask Filter -* Image Filter + +- Load image to canvas +- Undo/Redo (With shortcut) +- Crop +- Flip +- Rotation +- Free drawing +- Line drawing +- Shape +- Icon +- Text +- Mask Filter +- Image Filter ## 💾 Install @@ -184,10 +183,10 @@ You can find TOAST UI producs via [npm](https://www.npmjs.com/) and [bower](http Install by using the commands provided by each package manager. When using npm, be sure [Node.js](https://nodejs.org) is installed in the environment. - #### npm #### 1. ImageEditor installation + ```sh $ npm install --save tui-image-editor # Latest version $ npm install --save tui-image-editor@ # Specific version @@ -205,12 +204,16 @@ $ bower install tui-image-editor# # Specific version ``` ### Via Contents Delivery Network (CDN) + TOAST UI products are available over the CDN powered by [TOAST Cloud](https://www.toast.com). You can use the CDN as below. ```html - + ``` @@ -229,10 +232,9 @@ tui-image-editor/ ``` ### Download Source Files -* [Download bundle files from `dist` folder](https://github.com/nhn/tui.image-editor/tree/production/dist) -* [Download all sources for each version](https://github.com/nhn/tui.image-editor/releases) - +- [Download bundle files from `dist` folder](https://github.com/nhn/tui.image-editor/tree/production/dist) +- [Download all sources for each version](https://github.com/nhn/tui.image-editor/releases) ## 🔨 Usage @@ -240,11 +242,11 @@ tui-image-editor/ Add the container element where TOAST UI ImageEditor will be created. -``` html +```html -... -
-... + ... +
+ ... ``` @@ -256,28 +258,29 @@ Add dependencies & initialize ImageEditor class with given element to make an im var ImageEditor = require('tui-image-editor'); var FileSaver = require('file-saver'); //to download edited image to local. Use after npm install file-saver var blackTheme = require('./js/theme/black-theme.js'); -var locale_ru_RU = { // override default English locale to your custom - 'Crop': 'Обзрезать', - 'Delete-all': 'Удалить всё' - // etc... +var locale_ru_RU = { + // override default English locale to your custom + Crop: 'Обзрезать', + 'Delete-all': 'Удалить всё', + // etc... }; var instance = new ImageEditor(document.querySelector('#tui-image-editor'), { - includeUI: { - loadImage: { - path: 'img/sampleImage.jpg', - name: 'SampleImage' - }, - locale: locale_ru_RU, - theme: blackTheme, // or whiteTheme - initMenu: 'filter', - menuBarPosition: 'bottom' - }, - cssMaxWidth: 700, - cssMaxHeight: 500, - selectionStyle: { - cornerSize: 20, - rotatingPointOffset: 70 - } + includeUI: { + loadImage: { + path: 'img/sampleImage.jpg', + name: 'SampleImage', + }, + locale: locale_ru_RU, + theme: blackTheme, // or whiteTheme + initMenu: 'filter', + menuBarPosition: 'bottom', + }, + cssMaxWidth: 700, + cssMaxHeight: 500, + selectionStyle: { + cornerSize: 20, + rotatingPointOffset: 70, + }, }); ``` @@ -286,12 +289,12 @@ Or ~ UI ```javascript var ImageEditor = require('tui-image-editor'); var instance = new ImageEditor(document.querySelector('#tui-image-editor'), { - cssMaxWidth: 700, - cssMaxHeight: 500, - selectionStyle: { - cornerSize: 20, - rotatingPointOffset: 70 - } + cssMaxWidth: 700, + cssMaxHeight: 500, + selectionStyle: { + cornerSize: 20, + rotatingPointOffset: 70, + }, }); ``` @@ -305,6 +308,7 @@ var instance = new ImageEditor(document.querySelector('#tui-image-editor'), { Can find more details in [this document](https://github.com/nhn/tui.image-editor/blob/master/docs/Basic-Tutorial.md#4-menu-submenu-svg-icon-setting). ### TypeScript + If you using TypeScript, You must `import module = require('module')` on importing. [`export =` and `import = require()`](https://www.typescriptlang.org/docs/handbook/modules.html#export--and-import--require) @@ -313,12 +317,12 @@ import ImageEditor = require('tui-image-editor'); var FileSaver = require('file-saver'); //to download edited image to local. Use after npm install file-saver const instance = new ImageEditor(document.querySelector('#tui-image-editor'), { - cssMaxWidth: 700, - cssMaxHeight: 500, - selectionStyle: { - cornerSize: 20, - rotatingPointOffset: 70 - } + cssMaxWidth: 700, + cssMaxHeight: 500, + selectionStyle: { + cornerSize: 20, + rotatingPointOffset: 70, + }, }); ``` @@ -333,7 +337,7 @@ Run npm scripts and develop with the following process. ### Setup Fork `master` branch into your personal repository. -Clone to local computer. +Clone to local computer. Install node modules. Before starting development, check for any errors. @@ -351,32 +355,37 @@ $ npm run serve ``` ## 📙 Documents -* **Tutorial** : [https://github.com/nhn/tui.image-editor/tree/master/docs](https://github.com/nhn/tui.image-editor/tree/master/docs) -* **Example** : [http://nhn.github.io/tui.image-editor/latest/tutorial-example01-includeUi](http://nhn.github.io/tui.image-editor/latest/tutorial-example01-includeUi) -* **API** : [http://nhn.github.io/tui.image-editor/latest](http://nhn.github.io/tui.image-editor/latest/index) + +- **Tutorial** : [https://github.com/nhn/tui.image-editor/tree/master/docs](https://github.com/nhn/tui.image-editor/tree/master/docs) +- **Example** : [http://nhn.github.io/tui.image-editor/latest/tutorial-example01-includeUi](http://nhn.github.io/tui.image-editor/latest/tutorial-example01-includeUi) +- **API** : [http://nhn.github.io/tui.image-editor/latest](http://nhn.github.io/tui.image-editor/latest/index) ## 💬 Contributing -* [Code of Conduct](https://github.com/nhn/tui.image-editor/blob/master/CODE_OF_CONDUCT.md) -* [Contributing guideline](https://github.com/nhn/tui.image-editor/blob/master/CONTRIBUTING.md) -* [Issue guideline](https://github.com/nhn/tui.image-editor/blob/master/ISSUE_TEMPLATE.md) -* [Commit convention](https://github.com/nhn/tui.image-editor/blob/production/docs/COMMIT_MESSAGE_CONVENTION.md) + +- [Code of Conduct](https://github.com/nhn/tui.image-editor/blob/master/CODE_OF_CONDUCT.md) +- [Contributing guideline](https://github.com/nhn/tui.image-editor/blob/master/CONTRIBUTING.md) +- [Issue guideline](https://github.com/nhn/tui.image-editor/blob/master/ISSUE_TEMPLATE.md) +- [Commit convention](https://github.com/nhn/tui.image-editor/blob/production/docs/COMMIT_MESSAGE_CONVENTION.md) ## 🔩 Dependency -* [fabric.js](https://github.com/fabricjs/fabric.js/releases) = 4.2.0 -* [tui.code-snippet](https://github.com/nhn/tui.code-snippet/releases/tag/v1.5.0) >= 1.5.0 -* [tui.color-picker](https://github.com/nhn/tui.color-picker/releases/tag/v2.2.6) >= 2.2.6 +- [fabric.js](https://github.com/fabricjs/fabric.js/releases) = 4.2.0 +- [tui.code-snippet](https://github.com/nhn/tui.code-snippet/releases/tag/v1.5.0) >= 1.5.0 +- [tui.color-picker](https://github.com/nhn/tui.color-picker/releases/tag/v2.2.6) >= 2.2.6 ## 🍞 TOAST UI Family -* [TOAST UI Editor](https://github.com/nhn/tui.editor) -* [TOAST UI Grid](https://github.com/nhn/tui.grid) -* [TOAST UI Chart](https://github.com/nhn/tui.chart) -* [TOAST UI Calendar](https://github.com/nhn/tui.calendar) -* [TOAST UI Components](https://github.com/nhn) + +- [TOAST UI Editor](https://github.com/nhn/tui.editor) +- [TOAST UI Grid](https://github.com/nhn/tui.grid) +- [TOAST UI Chart](https://github.com/nhn/tui.chart) +- [TOAST UI Calendar](https://github.com/nhn/tui.calendar) +- [TOAST UI Components](https://github.com/nhn) ## 🚀 Used By -* [TOAST Dooray! - Collaboration Service (Project, Messenger, Mail, Calendar, Drive, Wiki, Contacts)](https://dooray.com/home/) -* [Catalyst](https://catalystapp.co/) + +- [TOAST Dooray! - Collaboration Service (Project, Messenger, Mail, Calendar, Drive, Wiki, Contacts)](https://dooray.com/home/) +- [Catalyst](https://catalystapp.co/) ## 📜 License + [MIT LICENSE](https://github.com/nhn/tui.image-editor/blob/master/LICENSE) diff --git a/bower.json b/bower.json index 77277f45e..a6932b0a9 100644 --- a/bower.json +++ b/bower.json @@ -1,12 +1,8 @@ { "name": "tui-image-editor", - "authors": [ - "NHN FE Dev Lab " - ], + "authors": ["NHN FE Dev Lab "], "license": "MIT", - "main": [ - "dist/tui-image-editor.js" - ], + "main": ["dist/tui-image-editor.js"], "ignore": [ "**/.*", "node_modules", diff --git a/createConfigVariable.js b/createConfigVariable.js index cfae9ada9..60a41df4a 100644 --- a/createConfigVariable.js +++ b/createConfigVariable.js @@ -1,6 +1,6 @@ -const fs = require("fs"); -const path = require("path"); -const config = require(path.resolve(process.cwd(), "tuidoc.config.json")); +const fs = require('fs'); +const path = require('path'); +const config = require(path.resolve(process.cwd(), 'tuidoc.config.json')); const examples = config.examples || {}; const { filePath, globalErrorLogVariable } = examples; @@ -8,28 +8,29 @@ const { filePath, globalErrorLogVariable } = examples; * Get Examples Url */ function getTestUrls() { - if (!filePath) { - throw Error("not exist examples path at tuidoc.config.json"); - } + if (!filePath) { + throw Error('not exist examples path at tuidoc.config.json'); + } + + const urlPrefix = 'http://nhn.github.io/tui.image-editor/latest'; - const urlPrefix = "http://nhn.github.io/tui.image-editor/latest"; + const testUrls = fs.readdirSync(filePath).reduce((urls, fileName) => { + if (/html$/.test(fileName)) { + urls.push(`${urlPrefix}/${filePath}/${fileName}`); + } - const testUrls = fs.readdirSync(filePath).reduce((urls, fileName) => { - if (/html$/.test(fileName)) { - urls.push(`${urlPrefix}/${filePath}/${fileName}`); - } - return urls; - }, []); + return urls; + }, []); - fs.writeFileSync("url.txt", testUrls.join(", ")); + fs.writeFileSync('url.txt', testUrls.join(', ')); } function getGlobalVariable() { - if (!globalErrorLogVariable) { - throw Error("not exist examples path at tuidoc.config.json"); - } + if (!globalErrorLogVariable) { + throw Error('not exist examples path at tuidoc.config.json'); + } - fs.writeFileSync("errorVariable.txt", globalErrorLogVariable); + fs.writeFileSync('errorVariable.txt', globalErrorLogVariable); } getTestUrls(); diff --git a/docs/Apply-Mobile-Version-Image.md b/docs/Apply-Mobile-Version-Image.md index 9e6bc46c3..7b6d9e635 100644 --- a/docs/Apply-Mobile-Version-Image.md +++ b/docs/Apply-Mobile-Version-Image.md @@ -3,77 +3,86 @@ ## Load Image #### Issue + - You can load photos directly from your mobile device into the image editor, but images with too high a resolution are not suitable for use. - For an action that includes a mouse gesture, such as cropping and drawing in the image editor, the action is determined by the aspect ratio relative to the original image size, so the higher the resolution, the less usable. - Maximum resolution per device - * iPhone : `3264 * 2448` - * Galaxy4 : `4128 * 3096` (High resolution) / `3264 * 2448` (Normal) / `2048 * 1152` (Low resolution) + +* iPhone : `3264 * 2448` +* Galaxy4 : `4128 * 3096` (High resolution) / `3264 * 2448` (Normal) / `2048 * 1152` (Low resolution) + - The appropriate image size for usability is `3264 * 2448`. If you receive a file upload event when loading an image taken at high resolution on your Android device, do the following. #### How to handle high-resolution image uploads + ```html - + ``` + ```js var MAX_RESOLUTION = 3264 * 2448; -$('input-image-file').on('change', function(event) { - var file; - var img; - var resolution; +$('input-image-file').on('change', function (event) { + var file; + var img; + var resolution; - if (!supportingFileAPI) { - alert('This browser does not support file-api'); - } + if (!supportingFileAPI) { + alert('This browser does not support file-api'); + } - file = event.target.files[0]; + file = event.target.files[0]; - if (file) { - img = new Image(); + if (file) { + img = new Image(); - img.onload = function() { - resolution = this.width * this.height; + img.onload = function () { + resolution = this.width * this.height; - if (resolution <= MAX_RESOLUTION) { - imageEditor.loadImageFromFile(file); - } else { - alert('Loaded image\'s resolution is too large!\nRecommended resolution is 3264 * 2448!'); - } + if (resolution <= MAX_RESOLUTION) { + imageEditor.loadImageFromFile(file); + } else { + alert("Loaded image's resolution is too large!\nRecommended resolution is 3264 * 2448!"); + } - URL.revokeObjectURL(file); - }; + URL.revokeObjectURL(file); + }; - img.src = URL.createObjectURL(file); - } + img.src = URL.createObjectURL(file); + } }); ``` ## Save Image #### Issue + - Saving an edited image does not appear in the current sample page, but the actual service must send the file to the server to save the image. - Uses Ajax communication. #### How to Save a Server Image Step 1. Import image data to be saved in the image editor. + ```js var dataURL = imageEditor.toDataURL(); ``` Step 2. `base64` encoded image data is Ajax communicated and sent to the server. + ```js $.ajax({ - type: 'POST', - url: serverUrl, - data: { - imgBase64: dataURL // Data from Step 1. - } -}).done(function() { - console.log('saved!'); + type: 'POST', + url: serverUrl, + data: { + imgBase64: dataURL, // Data from Step 1. + }, +}).done(function () { + console.log('saved!'); }); ``` -Step 3. The server processes the received data and stores it. +Step 3. The server processes the received data and stores it. + - [Using Java](https://sangupta.com/tech/saving-html5-canvas-to-java-server.html) - [Using Php](http://permadi.com/2010/10/html5-saving-canvas-image-data-using-php-and-ajax/) diff --git a/docs/Apply-Mobile-Version.md b/docs/Apply-Mobile-Version.md index 4140da133..e20ecf2aa 100644 --- a/docs/Apply-Mobile-Version.md +++ b/docs/Apply-Mobile-Version.md @@ -4,7 +4,7 @@ - Some settings are required to use Image Editor components on mobile devices. - Please refer to the [sample page](http://nhn.github.io/tui.image-editor/latest/tutorial-example03-mobile.html) first to check the UI configuration and operation. - + #### Step 1. Include the dependency file on the page. (PC version same) ```html @@ -18,14 +18,14 @@ ```html
- +
``` #### Step 3. `head` Add a meta tag for setting the viewport to the tag. ```html - + ``` #### Step 4. Create an image editor by setting option values for mobile device optimization. @@ -33,47 +33,46 @@ ```js // Create image editor var imageEditor = new tui.component.ImageEditor('.tui-image-editor canvas', { - cssMaxWidth: document.documentElement.clientWidth, - cssMaxHeight: document.documentElement.clientHeight, - selectionStyle: { - cornerSize: 50, - rotatingPointOffset: 100 - } + cssMaxWidth: document.documentElement.clientWidth, + cssMaxHeight: document.documentElement.clientHeight, + selectionStyle: { + cornerSize: 50, + rotatingPointOffset: 100, + }, }); ``` - `cssMaxWidth`, `cssMaxHeight` : - * Sets maximum `width` and` height` values in the canvas area. - * Do not set it to a fixed value like the PC version because the value changes depending on the mobile device to be connected. + - Sets maximum `width` and` height` values in the canvas area. + - Do not set it to a fixed value like the PC version because the value changes depending on the mobile device to be connected. - `selectionStyle` : - * Selection style setting options that are displayed when an object such as an icon, text, etc. is selected. - * If the corner size is small, it is difficult to resize and rotate, so set the selection style. - * The selection style options are the same as those provided by `fabric.js` and can be set with the following option values: ([Reference](http://fabricjs.com/customization)) - + - Selection style setting options that are displayed when an object such as an icon, text, etc. is selected. + - If the corner size is small, it is difficult to resize and rotate, so set the selection style. + - The selection style options are the same as those provided by `fabric.js` and can be set with the following option values: ([Reference](http://fabricjs.com/customization)) + ```js var options = { - //... - selectionStyle: { - borderColor: 'red', // Selection line color - cornerColor: 'green', // Selection corner color - cornerSize: 6, // Selection corner size - rotatingPointOffset: 100, // Distance from selection area to rotation corner - transparentCorners: false // Selection corner Transparency - } + //... + selectionStyle: { + borderColor: 'red', // Selection line color + cornerColor: 'green', // Selection corner color + cornerSize: 6, // Selection corner size + rotatingPointOffset: 100, // Distance from selection area to rotation corner + transparentCorners: false, // Selection corner Transparency + }, }; ``` -![2016-08-18 4 52 29](https://cloud.githubusercontent.com/assets/18183560/17766120/86f7c3fc-6564-11e6-86d7-554e8e946843.png) +![2016-08-18 4 52 29](https://cloud.githubusercontent.com/assets/18183560/17766120/86f7c3fc-6564-11e6-86d7-554e8e946843.png) #### Step 5. Add a CSS file and markup for UI configuration. (PC version same) ```html - + ``` -> -The CSS file is used on the sample page and should only refer to the UI configuration, -It is recommended to customize image, CSS, and markup files when applying the service. +> The CSS file is used on the sample page and should only refer to the UI configuration, +> It is recommended to customize image, CSS, and markup files when applying the service. #### Step 6. Apply the image editor API to the UI @@ -82,4 +81,3 @@ It is recommended to customize image, CSS, and markup files when applying the se ![all_feature_small](https://cloud.githubusercontent.com/assets/18183560/17803706/034ea17c-6633-11e6-914d-6602d12888f9.gif) ![text_feature_small](https://cloud.githubusercontent.com/assets/18183560/17803707/03530636-6633-11e6-8c03-cd5523716b9b.gif) - diff --git a/docs/Basic-Tutorial.md b/docs/Basic-Tutorial.md index 2704315d3..3a52a914b 100644 --- a/docs/Basic-Tutorial.md +++ b/docs/Basic-Tutorial.md @@ -1,8 +1,11 @@ ## Basic + Follow these 3steps to create image-editor. ### 1. Load required files + Load first the dependencies, and then load `image-editor.js` or `image-editor.min.js`. + ```html @@ -11,30 +14,35 @@ Load first the dependencies, and then load `image-editor.js` or `image-editor.mi ``` ### 2. HTML Markup + ImageEditor needs a division element having a canvas element.
And **the division element must have own (css)height.** + ```html
- +
``` ### 3. Javascript + ImageEditor constructor needs two parameters. -* The canvas element selector -* Css max width & Css max height - * Set the max width according to the size of your page. - * The max height should be same the height of the division element (in this example, `#my-image-editor`). + +- The canvas element selector +- Css max width & Css max height + - Set the max width according to the size of your page. + - The max height should be same the height of the division element (in this example, `#my-image-editor`). + ```js // Create image editor var imageEditor = new tui.component.ImageEditor('#my-image-editor canvas', { - cssMaxWidth: 1000, // Component default value: 1000 - cssMaxHeight: 800 // Component default value: 800 + cssMaxWidth: 1000, // Component default value: 1000 + cssMaxHeight: 800, // Component default value: 800 }); // Load image -imageEditor.loadImageFromURL('img/sampleImage.jpg', 'My sample image') +imageEditor.loadImageFromURL('img/sampleImage.jpg', 'My sample image'); ```
@@ -48,184 +56,196 @@ In the image below, the red and blue areas are set using the SVG icon. #### Two ways to set the icon 1. **Use default SVG built** into imageEditor without setting SVG file path (Features added since version v3.9.0). - * This is the default setting for Image Editor. - * It's easy to change the color to match the icon state as shown below, but it uses the built-in default shape so you can't change the icon's appearance. - ```js - const instance = new ImageEditor(document.querySelector('#tui-image-editor'), { - includeUI: { - ... - theme: { - 'menu.normalIcon.color': '#8a8a8a', - 'menu.activeIcon.color': '#555555', - 'menu.disabledIcon.color': '#434343', - 'menu.hoverIcon.color': '#e9e9e9', - 'submenu.normalIcon.color': '#8a8a8a', - 'submenu.activeIcon.color': '#e9e9e9', - } - ... - }); - ``` + + - This is the default setting for Image Editor. + - It's easy to change the color to match the icon state as shown below, but it uses the built-in default shape so you can't change the icon's appearance. + ```js + const instance = new ImageEditor(document.querySelector('#tui-image-editor'), { + includeUI: { + ..., + theme: { + 'menu.normalIcon.color': '#8a8a8a', + 'menu.activeIcon.color': '#555555', + 'menu.disabledIcon.color': '#434343', + 'menu.hoverIcon.color': '#e9e9e9', + 'submenu.normalIcon.color': '#8a8a8a', + 'submenu.activeIcon.color': '#e9e9e9', + }, + ... + } + }); + ``` 2. There is a way to use the **your SVG file** and **set the file location manually**. - * This is used when you want to completely reconfigure the SVG icon itself rather than the built-in icon. - * The disadvantage is that the color must be set by modifying the SVG file directly. - * Need to set the path and name for each icon state as shown below. - ```js - const instance = new ImageEditor(document.querySelector('#tui-image-editor'), { - includeUI: { - ... - theme: { - 'menu.normalIcon.path': '../dist/svg/icon-d.svg', - 'menu.normalIcon.name': 'icon-d', - 'menu.activeIcon.path': '../dist/svg/icon-b.svg', - 'menu.activeIcon.name': 'icon-b', - 'menu.disabledIcon.path': '../dist/svg/icon-a.svg', - 'menu.disabledIcon.name': 'icon-a', - 'menu.hoverIcon.path': '../dist/svg/icon-c.svg', - 'menu.hoverIcon.name': 'icon-c', - 'submenu.normalIcon.path': '../dist/svg/icon-a.svg', - 'submenu.normalIcon.name': 'icon-a', - 'submenu.activeIcon.path': '../dist/svg/icon-c.svg', - 'submenu.activeIcon.name': 'icon-c' - } - ... - }); - ``` - * How to get SVG file sample - * In the project folder where `tui-image-editor` is installed, the file is in the path described below - ```bash - // or use cdn (https://uicdn.toast.com/tui-image-editor/latest/svg/icon-a.svg) - $ cd node_modules/tui-image-editor/dist/svg - ``` - * Or just get the file via cdn. - * https://uicdn.toast.com/tui-image-editor/latest/svg/icon-a.svg - * https://uicdn.toast.com/tui-image-editor/latest/svg/icon-b.svg - * https://uicdn.toast.com/tui-image-editor/latest/svg/icon-c.svg - * https://uicdn.toast.com/tui-image-editor/latest/svg/icon-d.svg - - - * Don't forget to use the icon name setting of the `includeUI.theme` option to match the $ {iconName} part of the file. - ```svg - icon-a.svg file - submenu.activeIcon.name <-> iconName - ... - - - - - - - ... - ``` + - This is used when you want to completely reconfigure the SVG icon itself rather than the built-in icon. + - The disadvantage is that the color must be set by modifying the SVG file directly. + - Need to set the path and name for each icon state as shown below. + ```js + const instance = new ImageEditor(document.querySelector('#tui-image-editor'), { + includeUI: { + ..., + theme: { + 'menu.normalIcon.path': '../dist/svg/icon-d.svg', + 'menu.normalIcon.name': 'icon-d', + 'menu.activeIcon.path': '../dist/svg/icon-b.svg', + 'menu.activeIcon.name': 'icon-b', + 'menu.disabledIcon.path': '../dist/svg/icon-a.svg', + 'menu.disabledIcon.name': 'icon-a', + 'menu.hoverIcon.path': '../dist/svg/icon-c.svg', + 'menu.hoverIcon.name': 'icon-c', + 'submenu.normalIcon.path': '../dist/svg/icon-a.svg', + 'submenu.normalIcon.name': 'icon-a', + 'submenu.activeIcon.path': '../dist/svg/icon-c.svg', + 'submenu.activeIcon.name': 'icon-c' + }, + ..., + } + }); + ``` + - How to get SVG file sample + + - In the project folder where `tui-image-editor` is installed, the file is in the path described below + + ```bash + // or use cdn (https://uicdn.toast.com/tui-image-editor/latest/svg/icon-a.svg) + $ cd node_modules/tui-image-editor/dist/svg + ``` + + - Or just get the file via cdn. + + - https://uicdn.toast.com/tui-image-editor/latest/svg/icon-a.svg + - https://uicdn.toast.com/tui-image-editor/latest/svg/icon-b.svg + - https://uicdn.toast.com/tui-image-editor/latest/svg/icon-c.svg + - https://uicdn.toast.com/tui-image-editor/latest/svg/icon-d.svg + + - Don't forget to use the icon name setting of the `includeUI.theme` option to match the $ {iconName} part of the file. + + ```svg + icon-a.svg file + submenu.activeIcon.name <-> iconName + ... + + + + + + + ... + ``` ### 5. Localization + ImageEditor provide feature to customize all of inscriptions. Look at example. ```js -var locale_ru_RU = { // override default English locale to your custom - 'Crop': 'Обзрезать', // as result default English inscription will be translated into Russian - 'Delete-all': 'Удалить всё' - // etc... +var locale_ru_RU = { + // override default English locale to your custom + Crop: 'Обзрезать', // as result default English inscription will be translated into Russian + 'Delete-all': 'Удалить всё', + // etc... }; // Image editor const instance = new ImageEditor(document.querySelector('#tui-image-editor'), { - includeUI: { - loadImage: { - path: 'img/sampleImage.jpg', - name: 'SampleImage' - }, - locale: locale_ru_RU, // key-value object with localization - theme: blackTheme, // or whiteTheme - initMenu: 'filter', - menuBarPosition: 'bottom' - }, - cssMaxWidth: 700, - cssMaxHeight: 500, - selectionStyle: { - cornerSize: 20, - rotatingPointOffset: 70 - } + includeUI: { + loadImage: { + path: 'img/sampleImage.jpg', + name: 'SampleImage', + }, + locale: locale_ru_RU, // key-value object with localization + theme: blackTheme, // or whiteTheme + initMenu: 'filter', + menuBarPosition: 'bottom', + }, + cssMaxWidth: 700, + cssMaxHeight: 500, + selectionStyle: { + cornerSize: 20, + rotatingPointOffset: 70, + }, }); ``` +
Full inscriptions list who can be replaced to custom ones: -* 3:2 -* 4:3 -* 5:4 -* 7:5 -* 16:9 -* Apply -* Arrow -* Arrow-2 -* Arrow-3 -* Blend -* Blur -* Bold -* Brightness -* Bubble -* Cancel -* Center -* Circle -* Color -* Color Filter -* Crop -* Custom -* Custom icon -* Delete -* Delete-all -* Distance -* Download -* Draw -* Emboss -* Fill -* Filter -* Flip -* Flip X -* Flip Y -* Free -* Grayscale -* Heart -* Icon -* Invert -* Italic -* Left -* Load -* Load Mask Image -* Location -* Mask -* Multiply -* Noise -* Pixelate -* Polygon -* Range -* Rectangle -* Redo -* Remove White -* Reset -* Right -* Rotate -* Sepia -* Sepia2 -* Shape -* Sharpen -* Square -* Star-1 -* Star-2 -* Straight -* Stroke -* Text -* Text size -* Threshold -* Tint -* Triangle -* Underline -* Undo -* Value + +- 3:2 +- 4:3 +- 5:4 +- 7:5 +- 16:9 +- Apply +- Arrow +- Arrow-2 +- Arrow-3 +- Blend +- Blur +- Bold +- Brightness +- Bubble +- Cancel +- Center +- Circle +- Color +- Color Filter +- Crop +- Custom +- Custom icon +- Delete +- Delete-all +- Distance +- Download +- Draw +- Emboss +- Fill +- Filter +- Flip +- Flip X +- Flip Y +- Free +- Grayscale +- Heart +- Icon +- Invert +- Italic +- Left +- Load +- Load Mask Image +- Location +- Mask +- Multiply +- Noise +- Pixelate +- Polygon +- Range +- Rectangle +- Redo +- Remove White +- Reset +- Right +- Rotate +- Sepia +- Sepia2 +- Shape +- Sharpen +- Square +- Star-1 +- Star-2 +- Straight +- Stroke +- Text +- Text size +- Threshold +- Tint +- Triangle +- Underline +- Undo +- Value ## More.. See the API page and the sample page -* API: http://nhn.github.io/tui.image-editor/latest/ -* Sample: http://nhn.github.io/tui.image-editor/latest/tutorial-example01-includeUi.html + +- API: http://nhn.github.io/tui.image-editor/latest/ +- Sample: http://nhn.github.io/tui.image-editor/latest/tutorial-example01-includeUi.html diff --git a/docs/COMMIT_MESSAGE_CONVENTION.md b/docs/COMMIT_MESSAGE_CONVENTION.md index 7e68f99f6..62568bd8e 100644 --- a/docs/COMMIT_MESSAGE_CONVENTION.md +++ b/docs/COMMIT_MESSAGE_CONVENTION.md @@ -1,17 +1,21 @@ # Commit Message Convention +The commit messages of the main branch should follow the convention. + ## Commit Message Format ``` -: Short description (fix #1234) +: short description (fix #1234) Longer description here if necessary BREAKING CHANGE: only contain breaking change ``` -* Any line of the commit message cannot be longer 100 characters! + +- Any line of the commit message cannot be longer 100 characters! ## Revert + ``` revert: commit @@ -20,30 +24,34 @@ More description if needed ``` ## Type + +The type is determined by the intention. Must be one of the following: -* **feat**: A new feature -* **fix**: A bug fix -* **docs**: Documentation only changes -* **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) -* **refactor**: A code change that neither fixes a bug nor adds a feature -* **perf**: A code change that improves performance -* **test**: Adding missing or correcting existing tests -* **chore**: Changes to the build process or auxiliary tools and libraries such as documentation generation +- **feat**: A new feature +- **fix**: A bug fix +- **docs**: Documentation only changes +- **refactor**: A code change that neither fixes a bug nor adds a feature +- **perf**: A code change that improves performance +- **test**: Adding missing or correcting existing tests +- **chore**: Changes to the build process or auxiliary tools and libraries such as documentation generation +- **env**: Update dependencies, Changes to environment configuration files(package.json, elintrc, babelrc, webpack-config. browserlist, etc) ## Subject -* use the imperative, __present__ tense: "change" not "changed" nor "changes" -* don't capitalize the first letter -* no dot (.) at the end -* reference GitHub issues at the end. If the commit doesn’t completely fix the issue, then use `(refs #1234)` instead of `(fixes #1234)`. + +- use the imperative, **present** tense: "change" not "changed" nor "changes" +- don't capitalize the first letter +- no dot (.) at the end +- reference GitHub issues at the end. If the commit doesn’t completely fix the issue, then use `(refs #1234)` instead of `(fixes #1234)`. ## Body -* use the imperative, __present__ tense: "change" not "changed" nor "changes". -* the motivation for the change and contrast this with previous behavior. +- use the imperative, **present** tense: "change" not "changed" nor "changes". +- the motivation for the change and contrast this with previous behavior. ## BREAKING CHANGE -* This commit contains breaking change(s). -* start with the word BREAKING CHANGE: with a space or two newlines. The rest of the commit message is then used for this. + +- This commit contains breaking change(s). +- start with the word BREAKING CHANGE: with a space or two newlines. The rest of the commit message is then used for this. This convention is based on [AngularJS](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#commits) and [ESLint](https://eslint.org/docs/developer-guide/contributing/pull-requests#step2) diff --git a/docs/ISSUE_TEMPLATE.md b/docs/ISSUE_TEMPLATE.md index 2f6fd0eea..36bc68c19 100644 --- a/docs/ISSUE_TEMPLATE.md +++ b/docs/ISSUE_TEMPLATE.md @@ -8,12 +8,15 @@ To use the template is mandatory for submit new issue and we won't reply the iss ## Version + ## Development Environment + ## Current Behavior + ```js @@ -21,4 +24,5 @@ To use the template is mandatory for submit new issue and we won't reply the iss ``` ## Expected Behavior - \ No newline at end of file + + diff --git a/docs/ImageEditor-2.0.0-Migration-guide.md b/docs/ImageEditor-2.0.0-Migration-guide.md index ba9bb318e..a139e026e 100644 --- a/docs/ImageEditor-2.0.0-Migration-guide.md +++ b/docs/ImageEditor-2.0.0-Migration-guide.md @@ -1,35 +1,40 @@ There are a lot of changes for ImageEditor 2.0.0 including API changes and new features. This migration document will be nicely moving to v2.0.0. ## New drawing mode change APIs - * New APIs - * `startDrawingMode(modeName)` starts a drawing mode - * `stopDrawingMode()` stops current drawing mode and back to 'NORMAL' mode - * `getDrawingMode()` returns current drawing mode name. - * `getCropzoneRect()` returns cropping rect in 'CROPPER' drawing mode. - * `crop(rect)` crops image given area - - * Removed APIs - * `startCropping`, `endCropping` - * `startDrawingShapeMode`, `endDrawingShapeMode` - * `startFreeDrawing`, `endFreeDrawing` - * `startLineDrawing`, `endLineDrawing` - * `startTextMode`, `endTextMode` - * `endAll` - * `endCropping` is divided into three APIs + +- New APIs + + - `startDrawingMode(modeName)` starts a drawing mode + - `stopDrawingMode()` stops current drawing mode and back to 'NORMAL' mode + - `getDrawingMode()` returns current drawing mode name. + - `getCropzoneRect()` returns cropping rect in 'CROPPER' drawing mode. + - `crop(rect)` crops image given area + +- Removed APIs + - `startCropping`, `endCropping` + - `startDrawingShapeMode`, `endDrawingShapeMode` + - `startFreeDrawing`, `endFreeDrawing` + - `startLineDrawing`, `endLineDrawing` + - `startTextMode`, `endTextMode` + - `endAll` + - `endCropping` is divided into three APIs + ```js var rect = imageEditor.getCropzoneRect(); imageEditor.crop(rect).then(() => { - imageEditor.stopDrawingMode(); + imageEditor.stopDrawingMode(); }); ``` ## Changed APIs -* `removeActiveObject()` ==> `removeObject(id)` -* `getCurrentState()` ==> `getDrawingMode()` + +- `removeActiveObject()` ==> `removeObject(id)` +- `getCurrentState()` ==> `getDrawingMode()` ## Use object is with all drawing APIs - * In versions prior to 1.4.1, the users should select an object and manipulate it which is called 'active object'. There was no way to manipulate non-selected object. After 2.0.0 version, you can manipulate not only selected object, but also non-selected objects by receiving the Object Id. - * To get the Object Id, use the parameter.id in Promise.then() and the event callback. + +- In versions prior to 1.4.1, the users should select an object and manipulate it which is called 'active object'. There was no way to manipulate non-selected object. After 2.0.0 version, you can manipulate not only selected object, but also non-selected objects by receiving the Object Id. +- To get the Object Id, use the parameter.id in Promise.then() and the event callback. ```js /* @@ -54,65 +59,71 @@ imageEditor.crop(rect).then(() => { textDecoration: string } */ -imageEditor.on('objectActivated', function(props) { - console.log(props); - console.log(props.type); - console.log(props.id); +imageEditor.on('objectActivated', function (props) { + console.log(props); + console.log(props.type); + console.log(props.id); }); ``` + ```js -imageEditor.addShape('circle', { +imageEditor + .addShape('circle', { fill: 'red', stroke: 'blue', strokeWidth: 3, rx: 10, ry: 100, - isRegular: false -}).then(function(props) { + isRegular: false, + }) + .then(function (props) { console.log(props.id); - imageEditor.changeShape(props.id, { // change circle - fill: '#FFFF00', - strokeWidth: 10 + imageEditor.changeShape(props.id, { + // change circle + fill: '#FFFF00', + strokeWidth: 10, }); -}); + }); ``` ## Support Promise API -* All drawing APIs returns Promise and supports Undo/Redo. -* List of related APIs - * `addIcon`, `addImageObject`, `addShape`, `changeIconColor` - * `changeShape`, `addText`, `changeText`, `changeTextStyle`, - * `resizeCanvasDimension`, `applyFilter`, `removeFilter`, - * `clearObjects`, `flipX`, `flipY`, `loadImageFromFile`, - * `loadImageFromURL`, `redo`, `undo`, `removeObject`, - * `resetFlip`, `rotate`, `setAngle`, `crop`, - * `setObjectPosition`, `setObjectProperties` + +- All drawing APIs returns Promise and supports Undo/Redo. +- List of related APIs + - `addIcon`, `addImageObject`, `addShape`, `changeIconColor` + - `changeShape`, `addText`, `changeText`, `changeTextStyle`, + - `resizeCanvasDimension`, `applyFilter`, `removeFilter`, + - `clearObjects`, `flipX`, `flipY`, `loadImageFromFile`, + - `loadImageFromURL`, `redo`, `undo`, `removeObject`, + - `resetFlip`, `rotate`, `setAngle`, `crop`, + - `setObjectPosition`, `setObjectProperties` ## Changed event type -| As-Is | To-Be | Change | Why & Purpose | -| ----- | ----- | --- | --- | -| **~~_activateText_~~** | **addText** | renamed | when mousedown event occurs in 'TEXT' drawing mode | -| **_~~addObject~~_** | - | removed | unnecessary | -| **_~~adjustObject~~_** | **objectMoved** | renamed
changed | when user drags an object | -| **_~~adjustObject~~_** | **objectScaled** | renamed
changed | when object is being scaled | -| ~~applyFilter~~ | - | removed | Replace it to `applyFilter()` Promise API | -| ~~clearImage~~ | - | removed | Replace it to `loadImageFromFile()`, `loadImageFromURL()` Promise API | -| ~~clearObjects~~ | - | removed | Replace it to `clearObjects()` Promise API | -| **_~~editText~~_** | **textEditing** | renamed | when textbox is being edited | -| **~~_emptyRedoStack_~~** | **redoStackChanged** | renamed
changed | Replace it to `redoStackChanged` event with length `0` | -| **~~_emptyUndoStack_~~** | **undoStackChanged** | renamed
changed | Replace it to `undoStackChanged` event with length `0` | -| ~~endCropping~~ | - | removed | unnecessary | -| ~~endFreeDrawing~~ | - | removed | unnecessary | -| ~~endLineDrawing~~ | - | removed | unnecessary | -| ~~flipImage~~ | - | removed | Replace it to `flipX()`, `flipY()` Promise API | -| ~~loadImage~~ | - | removed | Replace it to `loadImageFromFile()`, `loadImageFromURL()` Promise API | -| **mousedown** | **mousedown** | remained | just mousedown | -| **_~~pushRedoStack~~_** | **redoStackChanged** | renamed
changed |redo change event | -| **_~~pushUndoStack~~_** | **undoStackChanged** | renamed
changed | undo change event | -| ~~removeObject~~ | - | removed | Replace it to `removeObject()` Promise API | -| ~~rotateImage~~ | - | removed | Replace it to `rotate()`, `setAngle()` Promise API | -| **_~~selectObject~~_** | **objectActivated** | renamed
changed | when user selects an object | -| ~~startCropping~~ | - | removed | unnecessary | -| ~~startFreeDrawing~~ | - | removed | unnecessary | -| ~~startLineDrawing~~ | - | removed | unnecessary | \ No newline at end of file + +| As-Is | To-Be | Change | Why & Purpose | +| ------------------------ | -------------------- | ------------------ | --------------------------------------------------------------------- | +| **~~_activateText_~~** | **addText** | renamed | when mousedown event occurs in 'TEXT' drawing mode | +| **_~~addObject~~_** | - | removed | unnecessary | +| **_~~adjustObject~~_** | **objectMoved** | renamed
changed | when user drags an object | +| **_~~adjustObject~~_** | **objectScaled** | renamed
changed | when object is being scaled | +| ~~applyFilter~~ | - | removed | Replace it to `applyFilter()` Promise API | +| ~~clearImage~~ | - | removed | Replace it to `loadImageFromFile()`, `loadImageFromURL()` Promise API | +| ~~clearObjects~~ | - | removed | Replace it to `clearObjects()` Promise API | +| **_~~editText~~_** | **textEditing** | renamed | when textbox is being edited | +| **~~_emptyRedoStack_~~** | **redoStackChanged** | renamed
changed | Replace it to `redoStackChanged` event with length `0` | +| **~~_emptyUndoStack_~~** | **undoStackChanged** | renamed
changed | Replace it to `undoStackChanged` event with length `0` | +| ~~endCropping~~ | - | removed | unnecessary | +| ~~endFreeDrawing~~ | - | removed | unnecessary | +| ~~endLineDrawing~~ | - | removed | unnecessary | +| ~~flipImage~~ | - | removed | Replace it to `flipX()`, `flipY()` Promise API | +| ~~loadImage~~ | - | removed | Replace it to `loadImageFromFile()`, `loadImageFromURL()` Promise API | +| **mousedown** | **mousedown** | remained | just mousedown | +| **_~~pushRedoStack~~_** | **redoStackChanged** | renamed
changed | redo change event | +| **_~~pushUndoStack~~_** | **undoStackChanged** | renamed
changed | undo change event | +| ~~removeObject~~ | - | removed | Replace it to `removeObject()` Promise API | +| ~~rotateImage~~ | - | removed | Replace it to `rotate()`, `setAngle()` Promise API | +| **_~~selectObject~~_** | **objectActivated** | renamed
changed | when user selects an object | +| ~~startCropping~~ | - | removed | unnecessary | +| ~~startFreeDrawing~~ | - | removed | unnecessary | +| ~~startLineDrawing~~ | - | removed | unnecessary | diff --git a/docs/PULL_REQUEST_TEMPLATE.md b/docs/PULL_REQUEST_TEMPLATE.md index efd0e978b..c72ae983a 100644 --- a/docs/PULL_REQUEST_TEMPLATE.md +++ b/docs/PULL_REQUEST_TEMPLATE.md @@ -26,6 +26,7 @@ --> ### Please check if the PR fulfills these requirements + - [ ] It's submitted to right branch according to our branching model - [ ] It's right issue type on title - [ ] When resolving a specific issue, it's referenced in the PR's title (e.g. `fix #xxx[,#xxx]`, where "xxx" is the issue number) @@ -36,7 +37,6 @@ ### Description - - --- -Thank you for your contribution to TOAST UI product. 🎉 😘 ✨ \ No newline at end of file + +Thank you for your contribution to TOAST UI product. 🎉 😘 ✨ diff --git a/docs/README.md b/docs/README.md index 15a041296..9ed18574a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,17 +1,15 @@ ## Tutorials + - [Getting Started](Basic-Tutorial.md) - [Understand the structure](Structure.md) - [Simple Reference](Reference.md) - [Mobile Version](Apply-Mobile-Version.md) - [Mobile Version - Image Load and Save](Apply-Mobile-Version-Image.md) - ## Documents + - [Code of Conducting](../CODE_OF_CONDUCT.md) - [Contributing Guide](../CONTRIBUTING.md) - [Commit Message Convention](COMMIT_MESSAGE_CONVENTION.md) - [API & Examples](http://nhn.github.io/tui.image-editor/latest/) - [v2.0.0 Migration Guide](ImageEditor-2.0.0-Migration-guide.md) - - - diff --git a/docs/Reference.md b/docs/Reference.md index 7dacba531..86923919a 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -1,16 +1,18 @@ - ### Text + - Insert text on the canvas and modify text. - Change text color, weight, align so on. ->**Implementing to insert text on the canvas using the text palette** +> **Implementing to insert text on the canvas using the text palette** + - The user can make the specific text palette and edit some text using this palette. - Call custom event API, it can insert text object and control the text palette. - * `ImageEditor#activateText` : It occurs when the canvas is clicked. - * `ImageEditor#adjustObject` : It occurs when any inserted text object is moved or resized. + +* `ImageEditor#activateText` : It occurs when the canvas is clicked. +* `ImageEditor#adjustObject` : It occurs when any inserted text object is moved or resized. ```js -imageEditor.on('activateText', function(obj) { +imageEditor.on('activateText', function (obj) { console.log(obj.type); // Whether the current text object is new or aleady created console.log(obj.text); // Contents of the current text object console.log(obj.styles); // Styles of the current text object @@ -18,53 +20,60 @@ imageEditor.on('activateText', function(obj) { console.log(obj.clientPosition); // Mouse position on browser - set the text palette's position }); ``` + ```js -imageEditor.on('adjustObject', function(obj) { +imageEditor.on('adjustObject', function (obj) { console.log(obj.type); // Whether the selected object's type is "text" or others - control the the text palette's view state }); ``` -![image](https://cloud.githubusercontent.com/assets/18183560/16838164/cd200920-4a02-11e6-9c5a-304d1a07d82a.png) +![image](https://cloud.githubusercontent.com/assets/18183560/16838164/cd200920-4a02-11e6-9c5a-304d1a07d82a.png) ### Icon + - Insert the basic icon on the canvas. (type: _arrow_, _cancel_ icon) - Register the custom icon. - Change color of the icon. ->**How to draw SVG path** +> **How to draw SVG path** + - [Link](https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths) ->**How to get SVG path value when registering the custom icon** +> **How to get SVG path value when registering the custom icon** + - [Link](https://css-tricks.com/using-svg/) ![image](https://cloud.githubusercontent.com/assets/18183560/16838300/726f8a68-4a03-11e6-8703-6d0e36a7f3e3.png) - ### Mask Filter + - Load the image for using mask filter. (This image is called the "mask image") - When applying mask filter on the canvas image, the canvas image's areas matching the mask image's black areas should be transparent. ![image](https://cloud.githubusercontent.com/assets/18183560/16837578/07444c46-49ff-11e6-99fc-2355a6777dc0.gif) - ### Line Drawing + - Draw the straight line on the canvas. - Change the color and width value of brush to draw line. ![image](https://cloud.githubusercontent.com/assets/18183560/16837621/4beed348-49ff-11e6-8276-8e0f7e9e85e6.gif) ### Shourtcut + - On the canvas - * `ctrl + z` : undo - * `ctrl + y` : redo + +* `ctrl + z` : undo +* `ctrl + y` : redo + - Crop - * `shift` : making the cropzone of 1:1 ratio -![image](https://cloud.githubusercontent.com/assets/18183560/16837645/73e7614e-49ff-11e6-9460-e596dd683724.gif) +* `shift` : making the cropzone of 1:1 ratio +![image](https://cloud.githubusercontent.com/assets/18183560/16837645/73e7614e-49ff-11e6-9460-e596dd683724.gif) ## More + - Get started (Tutorial) : [https://github.com/nhn/tui.component.image-editor/wiki/Tutorial](https://github.com/nhn/tui.component.image-editor/wiki/Tutorial) - API : [http://nhn.github.io/tui.image-editor/latest/](http://nhn.github.io/tui.image-editor/latest/) - Sample : [http://nhn.github.io/tui.image-editor/latest/tutorial-example01-basic.html](http://nhn.github.io/tui.image-editor/latest/tutorial-example01-basic.html) - diff --git a/docs/Structure.md b/docs/Structure.md index 7179aec8a..8cf666fec 100644 --- a/docs/Structure.md +++ b/docs/Structure.md @@ -1,60 +1,62 @@ - # Introducation + - The image editor includes an implementation that includes the UI via the includeUI option. -However, you can express the implementation more freely without using the basic UI. + However, you can express the implementation more freely without using the basic UI. # Modules + Internally, it is separated into two major layers. -* `ImageEditor` - The object responsible for the API has the having two layers, and communicates directly with the UI. - * `Middle Layer` - It is composed of `Invoker`, `Command`, `CommandFactory` which provides the function of the application independent of the drawing operation. - * `Graphics Layer` - Generally speaking, a canvas is composed of `Canvas` which consists of functions provided by ImageEditor. Drawing operation is abstracted and the actual implementation uses _fabric.js_. - * `Component` is a modularized drawing operation of a specific function and belongs to the Graphics Layer. - * Drawing mode is a feature that is essential in the Graphics Layer. + +- `ImageEditor` - The object responsible for the API has the having two layers, and communicates directly with the UI. + - `Middle Layer` - It is composed of `Invoker`, `Command`, `CommandFactory` which provides the function of the application independent of the drawing operation. + - `Graphics Layer` - Generally speaking, a canvas is composed of `Canvas` which consists of functions provided by ImageEditor. Drawing operation is abstracted and the actual implementation uses _fabric.js_. + - `Component` is a modularized drawing operation of a specific function and belongs to the Graphics Layer. + - Drawing mode is a feature that is essential in the Graphics Layer. ## ImageEditor + The object responsible for the API, which has the Invoker and Graphics properties. ## CommandFactory + It is a class to register and create `Command`. Provide an interface to register a command and create an instance of the registered command where you need to draw with ImageEditor or Command. -* register - Command registration -* create - Create Instance of Registered Command +- register - Command registration +- create - Create Instance of Registered Command ## Command + Command is a unit of execution for performing specific functions and is independent of other modules. In the image editor, it is used as an execution unit for Undo / Redo, and the Command instance is managed as a stack in the Invoker.
Command registration receives objects actions and args that define `name`,` execute`, and `undo`. ### Command Class + ```js class Command { - constructor(actions, args) { - this.name = actions.name; - this.args = args; - this.execute = actions.execute; - this.undo = actions.undo; - this.executeCallback = actions.executeCallback || null; - this.undoCallback = actions.undoCallback || null; - this.undoData = {}; - } + constructor(actions, args) { + this.name = actions.name; + this.args = args; + this.execute = actions.execute; + this.undo = actions.undo; + this.executeCallback = actions.executeCallback || null; + this.undoCallback = actions.undoCallback || null; + this.undoData = {}; + } } ``` #### Command registration process. -CommandFactory.register is executed when import. The Command class gets the Component to use and passes the Graphics instance to perform the function. +CommandFactory.register is executed when import. The Command class gets the Component to use and passes the Graphics instance to perform the function. - ``` js +```js // commandName.js import commandFactory from '../factory/command'; const command = { - name: 'commandName', - execute(graphics, ...args) { - - }, - undo(graphics, ...args) { - - } + name: 'commandName', + execute(graphics, ...args) {}, + undo(graphics, ...args) {}, }; CommandFactory.register(command); @@ -62,40 +64,44 @@ module.export = command; ``` #### Command creation and execution. + ```js const command = commandFactory.create('commandName', param1, param2); -command.execute(...command.args).then(value => { +command + .execute(...command.args) + .then((value) => { // push undo stack return value; -}).catch(message => { + }) + .catch((message) => { // do something return Promise.reject(message); -}) + }); ``` ## Invoker -Execute `Command` and manage Undo / Redo. -* `Component` list management is removed here. Escalate to the Canvas to be specified below. +Execute `Command` and manage Undo / Redo. +- `Component` list management is removed here. Escalate to the Canvas to be specified below. ## Canvas + - As the drawing core module of ImageEditor, we have all of the drawing functions we provide. - The underlying graphics module uses fabric.js. - Below is a list of functions provided by the image editor. -| **Icon** | **Image** | **Shape** | **Text** | **Flip** | **FreeDrawing** | **LineDrawing** | **Rotate** | Crop | **기타** | -| ---- | ----- | ----- | ---- | ---- | ----------- | ----------- | ------ | ---- | --- | -| addIcon | addImageObject | addShape | addText | flipX | setBrush | setBrush | rotate | crop | resizeCanvasDimension | -| registerIcons | loadImageFromFile | setDrawingShape | changeText | flipY | | | setAngle | getCropzoneRect | toDataURL | -| changeIconColor | loadImageFromURL | changeShape | changeTextStyle | resetFlip | | | | | getDrawingMode | -| | ApplyFilter | | | | | | | | setDrawingMode | -| | RemoveFilter | | | | | | | | getImageName | -| | hasFilter | | | | | | | | clearObjects | -| | | | | | | | | | removeActiveObject | -| | | | | | | | | | destroy | -| | | | | | | | | | setDefaultPathStyle | - +| **Icon** | **Image** | **Shape** | **Text** | **Flip** | **FreeDrawing** | **LineDrawing** | **Rotate** | Crop | **기타** | +| --------------- | ----------------- | --------------- | --------------- | --------- | --------------- | --------------- | ---------- | --------------- | --------------------- | +| addIcon | addImageObject | addShape | addText | flipX | setBrush | setBrush | rotate | crop | resizeCanvasDimension | +| registerIcons | loadImageFromFile | setDrawingShape | changeText | flipY | | | setAngle | getCropzoneRect | toDataURL | +| changeIconColor | loadImageFromURL | changeShape | changeTextStyle | resetFlip | | | | | getDrawingMode | +| | ApplyFilter | | | | | | | | setDrawingMode | +| | RemoveFilter | | | | | | | | getImageName | +| | hasFilter | | | | | | | | clearObjects | +| | | | | | | | | | removeActiveObject | +| | | | | | | | | | destroy | +| | | | | | | | | | setDefaultPathStyle | ## Component @@ -105,23 +111,24 @@ Execute `Command` and manage Undo / Redo. - The event that should be externally transmitted from the Component is passed through the Canvas. The Canvas passes the event back if it is registered outside Canvas. - The component list is shown below, and the components that need to change modes such as start / end are displayed. -| Name | Need mode | Usage | -| --- | ----- | --- | -| Cropper | O | Crop module, event handling for Crop. | -| filter | X | Image filter module | -| flip | X | Image flip Module. | -| freeDrawing | O | free drawing module | -| icon | X | Add Icon Module | -| imageLoader | X | Main image loading module | -| line | O | Straight line drawing module | -| rotation | X | Main image and objects rotation module | -| shape | O | Shape drawing module | -| text | O | Text object input module. | +| Name | Need mode | Usage | +| ----------- | --------- | -------------------------------------- | +| Cropper | O | Crop module, event handling for Crop. | +| filter | X | Image filter module | +| flip | X | Image flip Module. | +| freeDrawing | O | free drawing module | +| icon | X | Add Icon Module | +| imageLoader | X | Main image loading module | +| line | O | Straight line drawing module | +| rotation | X | Main image and objects rotation module | +| shape | O | Shape drawing module | +| text | O | Text object input module. | # The drawing mode is mutually exclusive, and Command operation is the user's part. + - Only one drawing mode should be activated at a time, because the events and UI used for each mode are different. Therefore, the drawing mode is mutually exclusive. - On the other hand, `Command` is a command that defines the drawing operation, so it can be considered to be able to operate regardless of the drawing mode. -- Command` is is no dependency on the drawing mode, and the operation is delegated to the user. +- Command` is is no dependency on the drawing mode, and the operation is delegated to the user. - The image editor expects you to make the following general API calls: @@ -141,26 +148,29 @@ editor.setDrawingMode("normal"); ``` # Event handling + - Canvas Layer For modularity, all events that occur inside the canvas are passed through the Canvas. - Events that occur on a Component managed by a Canvas are passed to the Canvas, and Canvas to the outside. - All callbacks that need to be passed to the UI are passed through the ImageEditor. For example, events that need to be imported from Canvas and Component are registered with ImageEditor and registered with Canvas. - If an event from the Canvas needs to be managed by an undo stack, the ImageEditor receives an event from the Canvas and calls the Invoker function. # UI delivery events. + - Most events that are passed to the UI are replaced by Promise, which conveys the completion of execution. The undo / redo related events pass events to the UI as they are for state value management. -Name | Purpose ------- | -------- -addText | when mousedown event occurs in 'TEXT' drawing mode -objectActivated | when user selects an object -objectMoved | when user drags an object -objectScaled | when object is being scaled -textEditing | when textbox is being edited -mousedown | just mousedown -undoStackChanged | undo change event -redoStackChanged | redo change event +| Name | Purpose | +| ---------------- | -------------------------------------------------- | +| addText | when mousedown event occurs in 'TEXT' drawing mode | +| objectActivated | when user selects an object | +| objectMoved | when user drags an object | +| objectScaled | when object is being scaled | +| textEditing | when textbox is being edited | +| mousedown | just mousedown | +| undoStackChanged | undo change event | +| redoStackChanged | redo change event | ### Example + ```js /* { @@ -184,17 +194,16 @@ redoStackChanged | redo change event textDecoration: string } */ -imageEditor.on('objectActivated', function(props) { - console.log(props); - console.log(props.type); - console.log(props.id); +imageEditor.on('objectActivated', function (props) { + console.log(props); + console.log(props.type); + console.log(props.id); }); ``` - # Class Diagram -``` uml +```uml class ServiceUI { -ImageEditor _imageEditor } @@ -347,4 +356,3 @@ LineDrawingMode <-- Line ShapeDrawingMode <-- Shape TextDrawingMode <-- Text ``` - diff --git a/examples/css/service-basic.css b/examples/css/service-basic.css index 3bb5b6a45..28e8627ea 100644 --- a/examples/css/service-basic.css +++ b/examples/css/service-basic.css @@ -1,128 +1,128 @@ .border { - border: 1px solid black; + border: 1px solid black; } .body-container { - width: 1000px; + width: 1000px; } .tui-image-editor-controls { - min-height: 250px; + min-height: 250px; } .menu { - padding: 0; - margin-bottom: 5px; - text-align: center; - color:#544B61; - font-weight: 400; - list-style-type: none; - user-select: none; - -moz-user-select: none; - -ms-user-select: none; - -webkit-user-select: none; + padding: 0; + margin-bottom: 5px; + text-align: center; + color: #544b61; + font-weight: 400; + list-style-type: none; + user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -webkit-user-select: none; } .logo { - margin: 0 auto; - width: 300px; - vertical-align: middle; + margin: 0 auto; + width: 300px; + vertical-align: middle; } .header .name { - padding: 10px; - line-height:50px; - font-size: 30px; - font-weight: 100; - vertical-align: middle; + padding: 10px; + line-height: 50px; + font-size: 30px; + font-weight: 100; + vertical-align: middle; } .header .menu { - display: inline-block; + display: inline-block; } .menu-item { - padding: 10px; - display: inline-block; - cursor: pointer; - vertical-align: middle; + padding: 10px; + display: inline-block; + cursor: pointer; + vertical-align: middle; } .menu-item a { - text-decoration: none; + text-decoration: none; } .menu-item.no-pointer { - cursor: default; + cursor: default; } .menu-item.active, .menu-item:hover { - background-color:#f3f3f3; + background-color: #f3f3f3; } .menu-item.disabled { - cursor: default; - color: #BFBEBE; + cursor: default; + color: #bfbebe; } .align-left-top { - text-align: left; - vertical-align: top; + text-align: left; + vertical-align: top; } .range-narrow { - width: 80px; + width: 80px; } .sub-menu-container { - font-size: 14px; - margin-bottom: 1em; - display: none; + font-size: 14px; + margin-bottom: 1em; + display: none; } .tui-image-editor { - height: 500px; + height: 500px; } .tui-image-editor-canvas-container { - margin: 0 auto; - top: 50%; - transform: translateY(-50%); - -ms-transform: translateY(-50%); - -moz-transform: translateY(-50%); - -webkit-transform: translateY(-50%); - border: 1px dashed black; - overflow: hidden; + margin: 0 auto; + top: 50%; + transform: translateY(-50%); + -ms-transform: translateY(-50%); + -moz-transform: translateY(-50%); + -webkit-transform: translateY(-50%); + border: 1px dashed black; + overflow: hidden; } .tui-colorpicker-container { - margin: 5px auto 0; + margin: 5px auto 0; } .tui-colorpicker-palette-toggle-slider { - display: none; -} -.input-wrapper{ - position: relative; -} -.input-wrapper input{ - cursor: pointer; - position: absolute; - font-size: 999px; - left: 0; - top: 0; - opacity: 0; - width: 100%; - height: 100%; - overflow: hidden; -} -.btn-text-style{ - padding: 5px; - margin: 3px 1px; - border: 1px dashed #bfbebe; - outline: 0; - background-color: #eee; - cursor: pointer; -} -.icon-text{ - font-size: 20px; -} -.select-line-type{ - outline: 0; - vertical-align: middle; -} -#tui-color-picker{ - display: inline-block; - vertical-align: middle; -} -#tui-text-palette{ - display: none; - position: absolute; - padding: 10px; - border: 1px solid #bfbebe; - background-color: #fff; - z-index: 9999; + display: none; +} +.input-wrapper { + position: relative; +} +.input-wrapper input { + cursor: pointer; + position: absolute; + font-size: 999px; + left: 0; + top: 0; + opacity: 0; + width: 100%; + height: 100%; + overflow: hidden; +} +.btn-text-style { + padding: 5px; + margin: 3px 1px; + border: 1px dashed #bfbebe; + outline: 0; + background-color: #eee; + cursor: pointer; +} +.icon-text { + font-size: 20px; +} +.select-line-type { + outline: 0; + vertical-align: middle; +} +#tui-color-picker { + display: inline-block; + vertical-align: middle; +} +#tui-text-palette { + display: none; + position: absolute; + padding: 10px; + border: 1px solid #bfbebe; + background-color: #fff; + z-index: 9999; } diff --git a/examples/css/service-mobile.css b/examples/css/service-mobile.css index be39d67f6..074aeb3cb 100644 --- a/examples/css/service-mobile.css +++ b/examples/css/service-mobile.css @@ -1,200 +1,203 @@ -html, body { - margin: 0; - padding: 0; - height: 100%; - overflow: hidden; - background-color: #383838; - font-family: Sans-Serif; -} -ul, li { - list-style: none; - margin: 0; - padding: 0; -} -input[type=button], button { - -webkit-appearance: none; - -moz-appearance: none; - background-color: #fff; -} -input[type=file] { - position: absolute; - margin: 0; - padding: 0; - top: 0; - left: 0; - width: 100%; - height: 100%; - cursor: pointer; - opacity: 0; - filter: alpha(opacity=0); +html, +body { + margin: 0; + padding: 0; + height: 100%; + overflow: hidden; + background-color: #383838; + font-family: Sans-Serif; +} +ul, +li { + list-style: none; + margin: 0; + padding: 0; +} +input[type='button'], +button { + -webkit-appearance: none; + -moz-appearance: none; + background-color: #fff; +} +input[type='file'] { + position: absolute; + margin: 0; + padding: 0; + top: 0; + left: 0; + width: 100%; + height: 100%; + cursor: pointer; + opacity: 0; + filter: alpha(opacity=0); } .header { - position: fixed; - left: 0; - top: 0; - width: 100%; - background-color: #fff; - text-align: center; - z-index: 9999; + position: fixed; + left: 0; + top: 0; + width: 100%; + background-color: #fff; + text-align: center; + z-index: 9999; } .header .logo { - margin: 10px 5px; - width: 180px; - vertical-align: middle; + margin: 10px 5px; + width: 180px; + vertical-align: middle; } .header .name { - font-size: 16px; - font-weight: bold; + font-size: 16px; + font-weight: bold; } .header .menu { - padding: 10px; - background-color: #000; + padding: 10px; + background-color: #000; } .header .menu input { - opacity: 0; + opacity: 0; } .header .menu img { - width: 20px; - height: 20px; - vertical-align: middle; + width: 20px; + height: 20px; + vertical-align: middle; } .header .button { - position: relative; - display: inline-block; - margin: 0 5px; - padding: 0; - border-radius: 5px 5px; - width: 30px; - height: 30px; - border: 0; - background-color: #fff; - vertical-align: middle; + position: relative; + display: inline-block; + margin: 0 5px; + padding: 0; + border-radius: 5px 5px; + width: 30px; + height: 30px; + border: 0; + background-color: #fff; + vertical-align: middle; } .header .button.disabled img { - opacity: 0.5; + opacity: 0.5; } .tui-image-editor { - height: 100%; + height: 100%; } .tui-image-editor-canvas-container { - margin: 0 auto; - top: 50%; - transform: translateY(-50%); - -ms-transform: translateY(-50%); - -moz-transform: translateY(-50%); - -webkit-transform: translateY(-50%); - overflow: hidden; + margin: 0 auto; + top: 50%; + transform: translateY(-50%); + -ms-transform: translateY(-50%); + -moz-transform: translateY(-50%); + -webkit-transform: translateY(-50%); + overflow: hidden; } .tui-image-editor-controls { - position: fixed; - width: 100%; - left: 0; - bottom: 0; - background-color: #fff; + position: fixed; + width: 100%; + left: 0; + bottom: 0; + background-color: #fff; } .tui-image-editor-controls .scrollable { - display: inline-block; - overflow-x: auto; - width: 100%; - height: 100%; - white-space: nowrap; - font-size:0; - background-color: #000; - vertical-align: middle; + display: inline-block; + overflow-x: auto; + width: 100%; + height: 100%; + white-space: nowrap; + font-size: 0; + background-color: #000; + vertical-align: middle; } .tui-image-editor-controls .no-scrollable { - overflow-x: hidden; + overflow-x: hidden; } .tui-image-editor-controls .menu-item { - display: inline-block; - height: 80px; - border-right: 1px solid #383838; - background-color: #ddd; - vertical-align: middle; + display: inline-block; + height: 80px; + border-right: 1px solid #383838; + background-color: #ddd; + vertical-align: middle; } .tui-image-editor-controls .menu-button { - width: 80px; - height: 80px; - border: none; - vertical-align: middle; - background-color: #000; - color: #fff; - font-size: 12px; - font-weight: bold; - outline: 0; + width: 80px; + height: 80px; + border: none; + vertical-align: middle; + background-color: #000; + color: #fff; + font-size: 12px; + font-weight: bold; + outline: 0; } .tui-image-editor-controls .submenu-button { - width: 80px; - height: 80px; - border: none; - background-color: #ddd; - vertical-align: middle; + width: 80px; + height: 80px; + border: none; + background-color: #ddd; + vertical-align: middle; } .tui-image-editor-controls .hiddenmenu-button { - margin: 0 10px; - padding: 5px; - border: none; - color: #fff; - background-color: rgba(255, 255, 255, 0); + margin: 0 10px; + padding: 5px; + border: none; + color: #fff; + background-color: rgba(255, 255, 255, 0); } .tui-image-editor-controls .submenu { - display: none; - position: absolute; - top: 0; - left: 0; - width: 100%; - font-size: 0; + display: none; + position: absolute; + top: 0; + left: 0; + width: 100%; + font-size: 0; } .tui-image-editor-controls .submenu.show { - display: block; + display: block; } .tui-image-editor-controls .submenu .menu-item:last-child { - margin-right: 50px; + margin-right: 50px; } .tui-image-editor-controls .hiddenmenu { - position: absolute; - display: none; - padding: 40px; - width: 100%; - left: 0; - bottom: 80px; - background-color: rgba(0, 0, 0, 0.7); - text-align: center; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - z-index: 9999; + position: absolute; + display: none; + padding: 40px; + width: 100%; + left: 0; + bottom: 80px; + background-color: rgba(0, 0, 0, 0.7); + text-align: center; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + z-index: 9999; } .tui-image-editor-controls .hiddenmenu.show { - display: block; + display: block; } .tui-image-editor-controls .hiddenmenu .top { - font-size: 12px; - color: #fff; - margin-bottom: 20px; + font-size: 12px; + color: #fff; + margin-bottom: 20px; } .tui-image-editor-controls .btn-prev { - display: inline-block; - width: 30px; - height: 80px; - background-color: #000; - color: #fff; - border: none; - vertical-align: middle; + display: inline-block; + width: 30px; + height: 80px; + background-color: #000; + color: #fff; + border: none; + vertical-align: middle; } .tui-image-editor-controls .tui-colorpicker-container { - display: inline-block; + display: inline-block; } .tui-image-editor-controls .msg { - position: absolute; - margin-left: 50%; - padding: 5px 10px; - left: -86px; - top: -50px; - border-radius: 5px 5px; - background-color: rgba(255, 255, 255, 0.5); - font-size: 12px; + position: absolute; + margin-left: 50%; + padding: 5px 10px; + left: -86px; + top: -50px; + border-radius: 5px 5px; + background-color: rgba(255, 255, 255, 0.5); + font-size: 12px; } .tui-image-editor-controls .msg.hide { - display: none; + display: none; } diff --git a/examples/css/tui-example-style.css b/examples/css/tui-example-style.css index f16006584..598b891e1 100644 --- a/examples/css/tui-example-style.css +++ b/examples/css/tui-example-style.css @@ -1,21 +1,21 @@ body { - margin: 0; - padding: 0; + margin: 0; + padding: 0; } .code-description { - padding: 22px 52px; - background-color: rgba(81, 92, 230, 0.1); - line-height: 1.4em; + padding: 22px 52px; + background-color: rgba(81, 92, 230, 0.1); + line-height: 1.4em; } .code-description, .code-description a { - font-family: Arial; - font-size: 14px; - color: #515ce6; + font-family: Arial; + font-size: 14px; + color: #515ce6; } .code-html { - padding: 20px 52px; -} \ No newline at end of file + padding: 20px 52px; +} diff --git a/examples/example01-includeUi.html b/examples/example01-includeUi.html index 69c4952d5..0a602ff6e 100644 --- a/examples/example01-includeUi.html +++ b/examples/example01-includeUi.html @@ -1,47 +1,63 @@ - - - 0. Design - - - - - - -
- - - - - - - - - + + + 0. Design + + + + + +
+ + + + + + + + + diff --git a/examples/example02-useApiDirect.html b/examples/example02-useApiDirect.html index d1fb7ab50..74b9b07b6 100644 --- a/examples/example02-useApiDirect.html +++ b/examples/example02-useApiDirect.html @@ -1,231 +1,392 @@ - - - 1. Basic - - - - -
-
-
- - Image Editor - -
- - - - - - - - - - -
-
+ + + 1. Basic + + + + +
+
+
+ + Image Editor +
- - - - - - - - + + + + + + + + + + +
+
+
+ + + + + + + + diff --git a/examples/example03-mobile.html b/examples/example03-mobile.html index a2d10a489..0bdfb1464 100644 --- a/examples/example03-mobile.html +++ b/examples/example03-mobile.html @@ -1,182 +1,249 @@ - - - - 2. Mobile - - - - - -
-
- Image Editor -
- -
- -
- -
+ + + + 2. Mobile + + + + + +
+
+ Image Editor +
+ +
+ +
+ +
+
    +
+ + + + + + + +

Menu Scrolling Left ⇔ Right

+
+ + + + + + + + diff --git a/examples/js/service-basic.js b/examples/js/service-basic.js index 8fec306a8..10ff5a4b1 100644 --- a/examples/js/service-basic.js +++ b/examples/js/service-basic.js @@ -100,822 +100,830 @@ var $selectBlendType = $('[name="select-blend-type"]'); // Image editor var imageEditor = new tui.ImageEditor('.tui-image-editor', { - cssMaxWidth: 700, - cssMaxHeight: 500, - selectionStyle: { - cornerSize: 20, - rotatingPointOffset: 70 - } + cssMaxWidth: 700, + cssMaxHeight: 500, + selectionStyle: { + cornerSize: 20, + rotatingPointOffset: 70, + }, }); // Color picker for free drawing var brushColorpicker = tui.colorPicker.create({ - container: $('#tui-brush-color-picker')[0], - color: '#000000' + container: $('#tui-brush-color-picker')[0], + color: '#000000', }); // Color picker for text palette var textColorpicker = tui.colorPicker.create({ - container: $('#tui-text-color-picker')[0], - color: '#000000' + container: $('#tui-text-color-picker')[0], + color: '#000000', }); // Color picker for shape var shapeColorpicker = tui.colorPicker.create({ - container: $('#tui-shape-color-picker')[0], - color: '#000000' + container: $('#tui-shape-color-picker')[0], + color: '#000000', }); // Color picker for icon var iconColorpicker = tui.colorPicker.create({ - container: $('#tui-icon-color-picker')[0], - color: '#000000' + container: $('#tui-icon-color-picker')[0], + color: '#000000', }); var tintColorpicker = tui.colorPicker.create({ - container: $('#tui-tint-color-picker')[0], - color: '#000000' + container: $('#tui-tint-color-picker')[0], + color: '#000000', }); var multiplyColorpicker = tui.colorPicker.create({ - container: $('#tui-multiply-color-picker')[0], - color: '#000000' + container: $('#tui-multiply-color-picker')[0], + color: '#000000', }); var blendColorpicker = tui.colorPicker.create({ - container: $('#tui-blend-color-picker')[0], - color: '#00FF00' + container: $('#tui-blend-color-picker')[0], + color: '#00FF00', }); // Common global functions // HEX to RGBA function hexToRGBa(hex, alpha) { - var r = parseInt(hex.slice(1, 3), 16); - var g = parseInt(hex.slice(3, 5), 16); - var b = parseInt(hex.slice(5, 7), 16); - var a = alpha || 1; + var r = parseInt(hex.slice(1, 3), 16); + var g = parseInt(hex.slice(3, 5), 16); + var b = parseInt(hex.slice(5, 7), 16); + var a = alpha || 1; - return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')'; + return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')'; } function base64ToBlob(data) { - var mimeString = ''; - var raw, uInt8Array, i, rawLength; + var mimeString = ''; + var raw, uInt8Array, i, rawLength; - raw = data.replace(rImageType, function(header, imageType) { - mimeString = imageType; + raw = data.replace(rImageType, function (header, imageType) { + mimeString = imageType; - return ''; - }); + return ''; + }); - raw = atob(raw); - rawLength = raw.length; - uInt8Array = new Uint8Array(rawLength); // eslint-disable-line + raw = atob(raw); + rawLength = raw.length; + uInt8Array = new Uint8Array(rawLength); // eslint-disable-line - for (i = 0; i < rawLength; i += 1) { - uInt8Array[i] = raw.charCodeAt(i); - } + for (i = 0; i < rawLength; i += 1) { + uInt8Array[i] = raw.charCodeAt(i); + } - return new Blob([uInt8Array], {type: mimeString}); + return new Blob([uInt8Array], { type: mimeString }); } function resizeEditor() { - var $editor = $('.tui-image-editor'); - var $container = $('.tui-image-editor-canvas-container'); - var height = parseFloat($container.css('max-height')); + var $editor = $('.tui-image-editor'); + var $container = $('.tui-image-editor-canvas-container'); + var height = parseFloat($container.css('max-height')); - $editor.height(height); + $editor.height(height); } function getBrushSettings() { - var brushWidth = parseInt($inputBrushWidthRange.val(), 10); - var brushColor = brushColorpicker.getColor(); + var brushWidth = parseInt($inputBrushWidthRange.val(), 10); + var brushColor = brushColorpicker.getColor(); - return { - width: brushWidth, - color: hexToRGBa(brushColor, 0.5) - }; + return { + width: brushWidth, + color: hexToRGBa(brushColor, 0.5), + }; } function activateShapeMode() { - if (imageEditor.getDrawingMode() !== 'SHAPE') { - imageEditor.stopDrawingMode(); - imageEditor.startDrawingMode('SHAPE'); - } + if (imageEditor.getDrawingMode() !== 'SHAPE') { + imageEditor.stopDrawingMode(); + imageEditor.startDrawingMode('SHAPE'); + } } function activateIconMode() { - imageEditor.stopDrawingMode(); + imageEditor.stopDrawingMode(); } function activateTextMode() { - if (imageEditor.getDrawingMode() !== 'TEXT') { - imageEditor.stopDrawingMode(); - imageEditor.startDrawingMode('TEXT'); - } + if (imageEditor.getDrawingMode() !== 'TEXT') { + imageEditor.stopDrawingMode(); + imageEditor.startDrawingMode('TEXT'); + } } function setTextToolbar(obj) { - var fontSize = obj.fontSize; - var fontColor = obj.fill; + var fontSize = obj.fontSize; + var fontColor = obj.fill; - $inputFontSizeRange.val(fontSize); - textColorpicker.setColor(fontColor); + $inputFontSizeRange.val(fontSize); + textColorpicker.setColor(fontColor); } function setIconToolbar(obj) { - var iconColor = obj.fill; + var iconColor = obj.fill; - iconColorpicker.setColor(iconColor); + iconColorpicker.setColor(iconColor); } function setShapeToolbar(obj) { - var fillColor, isTransparent, isFilter; - var colorType = $selectColorType.val(); - var changeValue = colorType === 'stroke' ? obj.stroke : obj.fill.type; - isTransparent = (changeValue === 'transparent'); - isFilter = (changeValue === 'filter'); - - if (colorType === 'stroke') { - if (!isTransparent && !isFilter) { - shapeColorpicker.setColor(changeValue); - } - } else if (colorType === 'fill') { - if (!isTransparent && !isFilter) { - fillColor = obj.fill.color; - shapeColorpicker.setColor(fillColor); - } + var fillColor, isTransparent, isFilter; + var colorType = $selectColorType.val(); + var changeValue = colorType === 'stroke' ? obj.stroke : obj.fill.type; + isTransparent = changeValue === 'transparent'; + isFilter = changeValue === 'filter'; + + if (colorType === 'stroke') { + if (!isTransparent && !isFilter) { + shapeColorpicker.setColor(changeValue); + } + } else if (colorType === 'fill') { + if (!isTransparent && !isFilter) { + fillColor = obj.fill.color; + shapeColorpicker.setColor(fillColor); } + } - $inputCheckTransparent.prop('checked', isTransparent); - $inputCheckFilter.prop('checked', isFilter); - $inputStrokeWidthRange.val(obj.strokeWidth); + $inputCheckTransparent.prop('checked', isTransparent); + $inputCheckFilter.prop('checked', isFilter); + $inputStrokeWidthRange.val(obj.strokeWidth); } function showSubMenu(type) { - var $submenu; - - switch (type) { - case 'shape': - $submenu = $drawShapeSubMenu; - break; - case 'icon': - $submenu = $iconSubMenu; - break; - case 'text': - $submenu = $textSubMenu; - break; - default: - $submenu = 0; - } - - $displayingSubMenu.hide(); - $displayingSubMenu = $submenu.show(); + var $submenu; + + switch (type) { + case 'shape': + $submenu = $drawShapeSubMenu; + break; + case 'icon': + $submenu = $iconSubMenu; + break; + case 'text': + $submenu = $textSubMenu; + break; + default: + $submenu = 0; + } + + $displayingSubMenu.hide(); + $displayingSubMenu = $submenu.show(); } function applyOrRemoveFilter(applying, type, options) { - if (applying) { - imageEditor.applyFilter(type, options).then(function(result) { - console.log(result); - }); - } else { - imageEditor.removeFilter(type); - } + if (applying) { + imageEditor.applyFilter(type, options).then(function (result) { + console.log(result); + }); + } else { + imageEditor.removeFilter(type); + } } // Attach image editor custom events imageEditor.on({ - objectAdded: function(objectProps) { - console.info(objectProps); - }, - undoStackChanged: function(length) { - if (length) { - $btnUndo.removeClass('disabled'); - } else { - $btnUndo.addClass('disabled'); - } - resizeEditor(); - }, - redoStackChanged: function(length) { - if (length) { - $btnRedo.removeClass('disabled'); - } else { - $btnRedo.addClass('disabled'); - } - resizeEditor(); - }, - objectScaled: function(obj) { - if (obj.type === 'text') { - $inputFontSizeRange.val(obj.fontSize); - } - }, - addText: function(pos) { - imageEditor.addText('Double Click', { - position: pos.originPosition - }).then(function(objectProps) { - console.log(objectProps); - }); - }, - objectActivated: function(obj) { - activeObjectId = obj.id; - if (obj.type === 'rect' || obj.type === 'circle' || obj.type === 'triangle') { - showSubMenu('shape'); - setShapeToolbar(obj); - activateShapeMode(); - } else if (obj.type === 'icon') { - showSubMenu('icon'); - setIconToolbar(obj); - activateIconMode(); - } else if (obj.type === 'text') { - showSubMenu('text'); - setTextToolbar(obj); - activateTextMode(); - } - }, - mousedown: function(event, originPointer) { - if ($imageFilterSubMenu.is(':visible') && imageEditor.hasFilter('colorFilter')) { - imageEditor.applyFilter('colorFilter', { - x: parseInt(originPointer.x, 10), - y: parseInt(originPointer.y, 10) - }); - } + objectAdded: function (objectProps) { + console.info(objectProps); + }, + undoStackChanged: function (length) { + if (length) { + $btnUndo.removeClass('disabled'); + } else { + $btnUndo.addClass('disabled'); + } + resizeEditor(); + }, + redoStackChanged: function (length) { + if (length) { + $btnRedo.removeClass('disabled'); + } else { + $btnRedo.addClass('disabled'); + } + resizeEditor(); + }, + objectScaled: function (obj) { + if (obj.type === 'text') { + $inputFontSizeRange.val(obj.fontSize); } + }, + addText: function (pos) { + imageEditor + .addText('Double Click', { + position: pos.originPosition, + }) + .then(function (objectProps) { + console.log(objectProps); + }); + }, + objectActivated: function (obj) { + activeObjectId = obj.id; + if (obj.type === 'rect' || obj.type === 'circle' || obj.type === 'triangle') { + showSubMenu('shape'); + setShapeToolbar(obj); + activateShapeMode(); + } else if (obj.type === 'icon') { + showSubMenu('icon'); + setIconToolbar(obj); + activateIconMode(); + } else if (obj.type === 'text') { + showSubMenu('text'); + setTextToolbar(obj); + activateTextMode(); + } + }, + mousedown: function (event, originPointer) { + if ($imageFilterSubMenu.is(':visible') && imageEditor.hasFilter('colorFilter')) { + imageEditor.applyFilter('colorFilter', { + x: parseInt(originPointer.x, 10), + y: parseInt(originPointer.y, 10), + }); + } + }, }); // Attach button click event listeners -$btns.on('click', function() { - $btnsActivatable.removeClass('active'); +$btns.on('click', function () { + $btnsActivatable.removeClass('active'); }); -$btnsActivatable.on('click', function() { - $(this).addClass('active'); +$btnsActivatable.on('click', function () { + $(this).addClass('active'); }); -$btnUndo.on('click', function() { - $displayingSubMenu.hide(); +$btnUndo.on('click', function () { + $displayingSubMenu.hide(); - if (!$(this).hasClass('disabled')) { - imageEditor.undo(); - } + if (!$(this).hasClass('disabled')) { + imageEditor.undo(); + } }); -$btnRedo.on('click', function() { - $displayingSubMenu.hide(); +$btnRedo.on('click', function () { + $displayingSubMenu.hide(); - if (!$(this).hasClass('disabled')) { - imageEditor.redo(); - } + if (!$(this).hasClass('disabled')) { + imageEditor.redo(); + } }); -$btnClearObjects.on('click', function() { - $displayingSubMenu.hide(); - imageEditor.clearObjects(); +$btnClearObjects.on('click', function () { + $displayingSubMenu.hide(); + imageEditor.clearObjects(); }); -$btnRemoveActiveObject.on('click', function() { - $displayingSubMenu.hide(); - imageEditor.removeObject(activeObjectId); +$btnRemoveActiveObject.on('click', function () { + $displayingSubMenu.hide(); + imageEditor.removeObject(activeObjectId); }); -$btnCrop.on('click', function() { - imageEditor.startDrawingMode('CROPPER'); - $displayingSubMenu.hide(); - $displayingSubMenu = $cropSubMenu.show(); +$btnCrop.on('click', function () { + imageEditor.startDrawingMode('CROPPER'); + $displayingSubMenu.hide(); + $displayingSubMenu = $cropSubMenu.show(); }); -$btnFlip.on('click', function() { - imageEditor.stopDrawingMode(); - $displayingSubMenu.hide(); - $displayingSubMenu = $flipSubMenu.show(); +$btnFlip.on('click', function () { + imageEditor.stopDrawingMode(); + $displayingSubMenu.hide(); + $displayingSubMenu = $flipSubMenu.show(); }); -$btnRotation.on('click', function() { - imageEditor.stopDrawingMode(); - $displayingSubMenu.hide(); - $displayingSubMenu = $rotationSubMenu.show(); +$btnRotation.on('click', function () { + imageEditor.stopDrawingMode(); + $displayingSubMenu.hide(); + $displayingSubMenu = $rotationSubMenu.show(); }); -$btnClose.on('click', function() { - imageEditor.stopDrawingMode(); - $displayingSubMenu.hide(); +$btnClose.on('click', function () { + imageEditor.stopDrawingMode(); + $displayingSubMenu.hide(); }); -$btnApplyCrop.on('click', function() { - imageEditor.crop(imageEditor.getCropzoneRect()).then(function() { - imageEditor.stopDrawingMode(); - resizeEditor(); - }); +$btnApplyCrop.on('click', function () { + imageEditor.crop(imageEditor.getCropzoneRect()).then(function () { + imageEditor.stopDrawingMode(); + resizeEditor(); + }); }); -$btnCancelCrop.on('click', function() { - imageEditor.stopDrawingMode(); +$btnCancelCrop.on('click', function () { + imageEditor.stopDrawingMode(); }); -$btnFlipX.on('click', function() { - imageEditor.flipX().then(function(status) { - console.log('flipX: ', status.flipX); - console.log('flipY: ', status.flipY); - console.log('angle: ', status.angle); - }); +$btnFlipX.on('click', function () { + imageEditor.flipX().then(function (status) { + console.log('flipX: ', status.flipX); + console.log('flipY: ', status.flipY); + console.log('angle: ', status.angle); + }); }); -$btnFlipY.on('click', function() { - imageEditor.flipY().then(function(status) { - console.log('flipX: ', status.flipX); - console.log('flipY: ', status.flipY); - console.log('angle: ', status.angle); - }); +$btnFlipY.on('click', function () { + imageEditor.flipY().then(function (status) { + console.log('flipX: ', status.flipX); + console.log('flipY: ', status.flipY); + console.log('angle: ', status.angle); + }); }); -$btnResetFlip.on('click', function() { - imageEditor.resetFlip().then(function(status) { - console.log('flipX: ', status.flipX); - console.log('flipY: ', status.flipY); - console.log('angle: ', status.angle); - }); +$btnResetFlip.on('click', function () { + imageEditor.resetFlip().then(function (status) { + console.log('flipX: ', status.flipX); + console.log('flipY: ', status.flipY); + console.log('angle: ', status.angle); + }); }); -$btnRotateClockwise.on('click', function() { - imageEditor.rotate(30); +$btnRotateClockwise.on('click', function () { + imageEditor.rotate(30); }); -$btnRotateCounterClockWise.on('click', function() { - imageEditor.rotate(-30); +$btnRotateCounterClockWise.on('click', function () { + imageEditor.rotate(-30); }); -$inputRotationRange.on('mousedown', function() { - var changeAngle = function() { - imageEditor.setAngle(parseInt($inputRotationRange.val(), 10))['catch'](function() {}); - }; - $(document).on('mousemove', changeAngle); - $(document).on('mouseup', function stopChangingAngle() { - $(document).off('mousemove', changeAngle); - $(document).off('mouseup', stopChangingAngle); - }); +$inputRotationRange.on('mousedown', function () { + var changeAngle = function () { + imageEditor.setAngle(parseInt($inputRotationRange.val(), 10))['catch'](function () {}); + }; + $(document).on('mousemove', changeAngle); + $(document).on('mouseup', function stopChangingAngle() { + $(document).off('mousemove', changeAngle); + $(document).off('mouseup', stopChangingAngle); + }); }); -$inputRotationRange.on('change', function() { - imageEditor.setAngle(parseInt($inputRotationRange.val(), 10))['catch'](function() {}); +$inputRotationRange.on('change', function () { + imageEditor.setAngle(parseInt($inputRotationRange.val(), 10))['catch'](function () {}); }); -$inputBrushWidthRange.on('change', function() { - imageEditor.setBrush({width: parseInt(this.value, 10)}); +$inputBrushWidthRange.on('change', function () { + imageEditor.setBrush({ width: parseInt(this.value, 10) }); }); -$inputImage.on('change', function(event) { - var file; +$inputImage.on('change', function (event) { + var file; - if (!supportingFileAPI) { - alert('This browser does not support file-api'); - } + if (!supportingFileAPI) { + alert('This browser does not support file-api'); + } - file = event.target.files[0]; - imageEditor.loadImageFromFile(file).then(function(result) { - console.log(result); - imageEditor.clearUndoStack(); - }); + file = event.target.files[0]; + imageEditor.loadImageFromFile(file).then(function (result) { + console.log(result); + imageEditor.clearUndoStack(); + }); }); -$btnDownload.on('click', function() { - var imageName = imageEditor.getImageName(); - var dataURL = imageEditor.toDataURL(); - var blob, type, w; - - if (supportingFileAPI) { - blob = base64ToBlob(dataURL); - type = blob.type.split('/')[1]; - if (imageName.split('.').pop() !== type) { - imageName += '.' + type; - } +$btnDownload.on('click', function () { + var imageName = imageEditor.getImageName(); + var dataURL = imageEditor.toDataURL(); + var blob, type, w; - // Library: FileSaver - saveAs - saveAs(blob, imageName); // eslint-disable-line - } else { - alert('This browser needs a file-server'); - w = window.open(); - w.document.body.innerHTML = ''; + if (supportingFileAPI) { + blob = base64ToBlob(dataURL); + type = blob.type.split('/')[1]; + if (imageName.split('.').pop() !== type) { + imageName += '.' + type; } + + // Library: FileSaver - saveAs + saveAs(blob, imageName); // eslint-disable-line + } else { + alert('This browser needs a file-server'); + w = window.open(); + w.document.body.innerHTML = ''; + } }); // control draw line mode -$btnDrawLine.on('click', function() { - imageEditor.stopDrawingMode(); - $displayingSubMenu.hide(); - $displayingSubMenu = $drawLineSubMenu.show(); - $selectLine.eq(0).change(); +$btnDrawLine.on('click', function () { + imageEditor.stopDrawingMode(); + $displayingSubMenu.hide(); + $displayingSubMenu = $drawLineSubMenu.show(); + $selectLine.eq(0).change(); }); -$selectLine.on('change', function() { - var mode = $(this).val(); - var settings = getBrushSettings(); +$selectLine.on('change', function () { + var mode = $(this).val(); + var settings = getBrushSettings(); - imageEditor.stopDrawingMode(); - if (mode === 'freeDrawing') { - imageEditor.startDrawingMode('FREE_DRAWING', settings); - } else { - imageEditor.startDrawingMode('LINE_DRAWING', settings); - } + imageEditor.stopDrawingMode(); + if (mode === 'freeDrawing') { + imageEditor.startDrawingMode('FREE_DRAWING', settings); + } else { + imageEditor.startDrawingMode('LINE_DRAWING', settings); + } }); -brushColorpicker.on('selectColor', function(event) { - imageEditor.setBrush({ - color: hexToRGBa(event.color, 0.5) - }); +brushColorpicker.on('selectColor', function (event) { + imageEditor.setBrush({ + color: hexToRGBa(event.color, 0.5), + }); }); // control draw shape mode -$btnDrawShape.on('click', function() { - showSubMenu('shape'); +$btnDrawShape.on('click', function () { + showSubMenu('shape'); - // step 1. get options to draw shape from toolbar - shapeType = $('[name="select-shape-type"]:checked').val(); + // step 1. get options to draw shape from toolbar + shapeType = $('[name="select-shape-type"]:checked').val(); - shapeOptions.stroke = '#000000'; - shapeOptions.fill = '#ffffff'; + shapeOptions.stroke = '#000000'; + shapeOptions.fill = '#ffffff'; - shapeOptions.strokeWidth = Number($inputStrokeWidthRange.val()); + shapeOptions.strokeWidth = Number($inputStrokeWidthRange.val()); - // step 2. set options to draw shape - imageEditor.setDrawingShape(shapeType, shapeOptions); + // step 2. set options to draw shape + imageEditor.setDrawingShape(shapeType, shapeOptions); - // step 3. start drawing shape mode - activateShapeMode(); + // step 3. start drawing shape mode + activateShapeMode(); }); -$selectShapeType.on('change', function() { - shapeType = $(this).val(); +$selectShapeType.on('change', function () { + shapeType = $(this).val(); - imageEditor.setDrawingShape(shapeType); + imageEditor.setDrawingShape(shapeType); }); -$selectColorType.on('change', function() { - var colorType = $(this).val(); - if (colorType === 'stroke') { - $inputCheckFilter.prop('disabled', true); - $inputCheckFilter.prop('checked', false); - } else { - $inputCheckTransparent.prop('disabled', false); - $inputCheckFilter.prop('disabled', false); - } +$selectColorType.on('change', function () { + var colorType = $(this).val(); + if (colorType === 'stroke') { + $inputCheckFilter.prop('disabled', true); + $inputCheckFilter.prop('checked', false); + } else { + $inputCheckTransparent.prop('disabled', false); + $inputCheckFilter.prop('disabled', false); + } }); $inputCheckTransparent.on('change', onChangeShapeFill); $inputCheckFilter.on('change', onChangeShapeFill); -shapeColorpicker.on('selectColor', function(event) { - $inputCheckTransparent.prop('checked', false); - $inputCheckFilter.prop('checked', false); - onChangeShapeFill(event); +shapeColorpicker.on('selectColor', function (event) { + $inputCheckTransparent.prop('checked', false); + $inputCheckFilter.prop('checked', false); + onChangeShapeFill(event); }); function onChangeShapeFill(event) { - var colorType = $selectColorType.val(); - var isTransparent = $inputCheckTransparent.prop('checked'); - var isFilter = $inputCheckFilter.prop('checked'); - var shapeOption; - - if (event.color) { - shapeOption = event.color; - } else if (isTransparent) { - shapeOption = 'transparent'; - } else if (isFilter) { - shapeOption = { - type: 'filter', - filter: [{pixelate: PIXELATE_FILTER_DEFAULT_VALUE}] - }; - } - - if (colorType === 'stroke') { - imageEditor.changeShape(activeObjectId, { - stroke: shapeOption - }); - } else if (colorType === 'fill') { - imageEditor.changeShape(activeObjectId, { - fill: shapeOption - }); - } - - imageEditor.setDrawingShape(shapeType, shapeOptions); -} - -$inputStrokeWidthRange.on('change', function() { - var strokeWidth = Number($(this).val()); + var colorType = $selectColorType.val(); + var isTransparent = $inputCheckTransparent.prop('checked'); + var isFilter = $inputCheckFilter.prop('checked'); + var shapeOption; + + if (event.color) { + shapeOption = event.color; + } else if (isTransparent) { + shapeOption = 'transparent'; + } else if (isFilter) { + shapeOption = { + type: 'filter', + filter: [{ pixelate: PIXELATE_FILTER_DEFAULT_VALUE }], + }; + } + if (colorType === 'stroke') { imageEditor.changeShape(activeObjectId, { - strokeWidth: strokeWidth + stroke: shapeOption, }); + } else if (colorType === 'fill') { + imageEditor.changeShape(activeObjectId, { + fill: shapeOption, + }); + } - imageEditor.setDrawingShape(shapeType, shapeOptions); -}); + imageEditor.setDrawingShape(shapeType, shapeOptions); +} -// control text mode -$btnText.on('click', function() { - showSubMenu('text'); - activateTextMode(); -}); +$inputStrokeWidthRange.on('change', function () { + var strokeWidth = Number($(this).val()); -$inputFontSizeRange.on('change', function() { - imageEditor.changeTextStyle(activeObjectId, { - fontSize: parseInt(this.value, 10) - }); -}); - -$btnTextStyle.on('click', function(e) { // eslint-disable-line - var styleType = $(this).attr('data-style-type'); - var styleObj; - - e.stopPropagation(); - - switch (styleType) { - case 'b': - styleObj = {fontWeight: 'bold'}; - break; - case 'i': - styleObj = {fontStyle: 'italic'}; - break; - case 'u': - styleObj = {underline: true}; - break; - case 'l': - styleObj = {textAlign: 'left'}; - break; - case 'c': - styleObj = {textAlign: 'center'}; - break; - case 'r': - styleObj = {textAlign: 'right'}; - break; - default: - styleObj = {}; - } + imageEditor.changeShape(activeObjectId, { + strokeWidth: strokeWidth, + }); - imageEditor.changeTextStyle(activeObjectId, styleObj); + imageEditor.setDrawingShape(shapeType, shapeOptions); }); -textColorpicker.on('selectColor', function(event) { - imageEditor.changeTextStyle(activeObjectId, { - 'fill': event.color - }); +// control text mode +$btnText.on('click', function () { + showSubMenu('text'); + activateTextMode(); +}); + +$inputFontSizeRange.on('change', function () { + imageEditor.changeTextStyle(activeObjectId, { + fontSize: parseInt(this.value, 10), + }); +}); + +$btnTextStyle.on('click', function (e) { + // eslint-disable-line + var styleType = $(this).attr('data-style-type'); + var styleObj; + + e.stopPropagation(); + + switch (styleType) { + case 'b': + styleObj = { fontWeight: 'bold' }; + break; + case 'i': + styleObj = { fontStyle: 'italic' }; + break; + case 'u': + styleObj = { underline: true }; + break; + case 'l': + styleObj = { textAlign: 'left' }; + break; + case 'c': + styleObj = { textAlign: 'center' }; + break; + case 'r': + styleObj = { textAlign: 'right' }; + break; + default: + styleObj = {}; + } + + imageEditor.changeTextStyle(activeObjectId, styleObj); +}); + +textColorpicker.on('selectColor', function (event) { + imageEditor.changeTextStyle(activeObjectId, { + fill: event.color, + }); }); // control icon -$btnAddIcon.on('click', function() { - showSubMenu('icon'); - activateIconMode(); +$btnAddIcon.on('click', function () { + showSubMenu('icon'); + activateIconMode(); }); function onClickIconSubMenu(event) { - var element = event.target || event.srcElement; - var iconType = $(element).attr('data-icon-type'); - - imageEditor.once('mousedown', function(e, originPointer) { - imageEditor.addIcon(iconType, { - left: originPointer.x, - top: originPointer.y - }).then(function(objectProps) { - // console.log(objectProps); - }); - }); + var element = event.target || event.srcElement; + var iconType = $(element).attr('data-icon-type'); + + imageEditor.once('mousedown', function (e, originPointer) { + imageEditor + .addIcon(iconType, { + left: originPointer.x, + top: originPointer.y, + }) + .then(function (objectProps) { + // console.log(objectProps); + }); + }); } -$btnRegisterIcon.on('click', function() { - $iconSubMenu.find('.menu-item').eq(3).after( - '' - ); +$btnRegisterIcon.on('click', function () { + $iconSubMenu + .find('.menu-item') + .eq(3) + .after(''); - imageEditor.registerIcons({ - customArrow: 'M 60 0 L 120 60 H 90 L 75 45 V 180 H 45 V 45 L 30 60 H 0 Z' - }); + imageEditor.registerIcons({ + customArrow: 'M 60 0 L 120 60 H 90 L 75 45 V 180 H 45 V 45 L 30 60 H 0 Z', + }); - $btnRegisterIcon.off('click'); + $btnRegisterIcon.off('click'); - $iconSubMenu.on('click', '#customArrow', onClickIconSubMenu); + $iconSubMenu.on('click', '#customArrow', onClickIconSubMenu); }); $iconSubMenu.on('click', '.icon-text', onClickIconSubMenu); -iconColorpicker.on('selectColor', function(event) { - imageEditor.changeIconColor(activeObjectId, event.color); +iconColorpicker.on('selectColor', function (event) { + imageEditor.changeIconColor(activeObjectId, event.color); }); // control mask filter -$btnMaskFilter.on('click', function() { - imageEditor.stopDrawingMode(); - $displayingSubMenu.hide(); - - $displayingSubMenu = $filterSubMenu.show(); -}); - -$btnImageFilter.on('click', function() { - var filters = { - 'grayscale': $inputCheckGrayscale, - 'invert': $inputCheckInvert, - 'sepia': $inputCheckSepia, - 'sepia2': $inputCheckSepia2, - 'blur': $inputCheckBlur, - 'shapren': $inputCheckSharpen, - 'emboss': $inputCheckEmboss, - 'removeWhite': $inputCheckRemoveWhite, - 'brightness': $inputCheckBrightness, - 'noise': $inputCheckNoise, - 'pixelate': $inputCheckPixelate, - 'tint': $inputCheckTint, - 'multiply': $inputCheckMultiply, - 'blend': $inputCheckBlend, - 'colorFilter': $inputCheckColorFilter - }; +$btnMaskFilter.on('click', function () { + imageEditor.stopDrawingMode(); + $displayingSubMenu.hide(); - tui.util.forEach(filters, function($value, key) { - $value.prop('checked', imageEditor.hasFilter(key)); - }); - $displayingSubMenu.hide(); + $displayingSubMenu = $filterSubMenu.show(); +}); + +$btnImageFilter.on('click', function () { + var filters = { + grayscale: $inputCheckGrayscale, + invert: $inputCheckInvert, + sepia: $inputCheckSepia, + sepia2: $inputCheckSepia2, + blur: $inputCheckBlur, + shapren: $inputCheckSharpen, + emboss: $inputCheckEmboss, + removeWhite: $inputCheckRemoveWhite, + brightness: $inputCheckBrightness, + noise: $inputCheckNoise, + pixelate: $inputCheckPixelate, + tint: $inputCheckTint, + multiply: $inputCheckMultiply, + blend: $inputCheckBlend, + colorFilter: $inputCheckColorFilter, + }; - $displayingSubMenu = $imageFilterSubMenu.show(); + tui.util.forEach(filters, function ($value, key) { + $value.prop('checked', imageEditor.hasFilter(key)); + }); + $displayingSubMenu.hide(); + + $displayingSubMenu = $imageFilterSubMenu.show(); }); -$btnLoadMaskImage.on('change', function() { - var file; - var imgUrl; +$btnLoadMaskImage.on('change', function () { + var file; + var imgUrl; - if (!supportingFileAPI) { - alert('This browser does not support file-api'); - } + if (!supportingFileAPI) { + alert('This browser does not support file-api'); + } - file = event.target.files[0]; + file = event.target.files[0]; - if (file) { - imgUrl = URL.createObjectURL(file); + if (file) { + imgUrl = URL.createObjectURL(file); - imageEditor.loadImageFromURL(imageEditor.toDataURL(), 'FilterImage').then(function() { - imageEditor.addImageObject(imgUrl).then(function(objectProps) { - URL.revokeObjectURL(file); - console.log(objectProps); - }); - }); - } + imageEditor.loadImageFromURL(imageEditor.toDataURL(), 'FilterImage').then(function () { + imageEditor.addImageObject(imgUrl).then(function (objectProps) { + URL.revokeObjectURL(file); + console.log(objectProps); + }); + }); + } }); -$btnApplyMask.on('click', function() { - imageEditor.applyFilter('mask', { - maskObjId: activeObjectId - }).then(function(result) { - console.log(result); +$btnApplyMask.on('click', function () { + imageEditor + .applyFilter('mask', { + maskObjId: activeObjectId, + }) + .then(function (result) { + console.log(result); }); }); -$inputCheckGrayscale.on('change', function() { - applyOrRemoveFilter(this.checked, 'Grayscale', null); +$inputCheckGrayscale.on('change', function () { + applyOrRemoveFilter(this.checked, 'Grayscale', null); }); -$inputCheckInvert.on('change', function() { - applyOrRemoveFilter(this.checked, 'Invert', null); +$inputCheckInvert.on('change', function () { + applyOrRemoveFilter(this.checked, 'Invert', null); }); -$inputCheckSepia.on('change', function() { - applyOrRemoveFilter(this.checked, 'Sepia', null); +$inputCheckSepia.on('change', function () { + applyOrRemoveFilter(this.checked, 'Sepia', null); }); -$inputCheckSepia2.on('change', function() { - applyOrRemoveFilter(this.checked, 'Sepia2', null); +$inputCheckSepia2.on('change', function () { + applyOrRemoveFilter(this.checked, 'Sepia2', null); }); -$inputCheckBlur.on('change', function() { - applyOrRemoveFilter(this.checked, 'Blur', null); +$inputCheckBlur.on('change', function () { + applyOrRemoveFilter(this.checked, 'Blur', null); }); -$inputCheckSharpen.on('change', function() { - applyOrRemoveFilter(this.checked, 'Sharpen', null); +$inputCheckSharpen.on('change', function () { + applyOrRemoveFilter(this.checked, 'Sharpen', null); }); -$inputCheckEmboss.on('change', function() { - applyOrRemoveFilter(this.checked, 'Emboss', null); +$inputCheckEmboss.on('change', function () { + applyOrRemoveFilter(this.checked, 'Emboss', null); }); -$inputCheckRemoveWhite.on('change', function() { - applyOrRemoveFilter(this.checked, 'removeWhite', { - threshold: parseInt($inputRangeRemoveWhiteThreshold.val(), 10), - distance: parseInt($inputRangeRemoveWhiteDistance.val(), 10) - }); +$inputCheckRemoveWhite.on('change', function () { + applyOrRemoveFilter(this.checked, 'removeWhite', { + threshold: parseInt($inputRangeRemoveWhiteThreshold.val(), 10), + distance: parseInt($inputRangeRemoveWhiteDistance.val(), 10), + }); }); -$inputRangeRemoveWhiteThreshold.on('change', function() { - applyOrRemoveFilter($inputCheckRemoveWhite.is(':checked'), 'removeWhite', { - threshold: parseInt(this.value, 10) - }); +$inputRangeRemoveWhiteThreshold.on('change', function () { + applyOrRemoveFilter($inputCheckRemoveWhite.is(':checked'), 'removeWhite', { + threshold: parseInt(this.value, 10), + }); }); -$inputRangeRemoveWhiteDistance.on('change', function() { - applyOrRemoveFilter($inputCheckRemoveWhite.is(':checked'), 'removeWhite', { - distance: parseInt(this.value, 10) - }); +$inputRangeRemoveWhiteDistance.on('change', function () { + applyOrRemoveFilter($inputCheckRemoveWhite.is(':checked'), 'removeWhite', { + distance: parseInt(this.value, 10), + }); }); -$inputCheckBrightness.on('change', function() { - applyOrRemoveFilter(this.checked, 'brightness', { - brightness: parseInt($inputRangeBrightnessValue.val(), 10) - }); +$inputCheckBrightness.on('change', function () { + applyOrRemoveFilter(this.checked, 'brightness', { + brightness: parseInt($inputRangeBrightnessValue.val(), 10), + }); }); -$inputRangeBrightnessValue.on('change', function() { - applyOrRemoveFilter($inputCheckBrightness.is(':checked'), 'brightness', { - brightness: parseInt(this.value, 10) - }); +$inputRangeBrightnessValue.on('change', function () { + applyOrRemoveFilter($inputCheckBrightness.is(':checked'), 'brightness', { + brightness: parseInt(this.value, 10), + }); }); -$inputCheckNoise.on('change', function() { - applyOrRemoveFilter(this.checked, 'noise', { - noise: parseInt($inputRangeNoiseValue.val(), 10) - }); +$inputCheckNoise.on('change', function () { + applyOrRemoveFilter(this.checked, 'noise', { + noise: parseInt($inputRangeNoiseValue.val(), 10), + }); }); -$inputRangeNoiseValue.on('change', function() { - applyOrRemoveFilter($inputCheckNoise.is(':checked'), 'noise', { - noise: parseInt(this.value, 10) - }); +$inputRangeNoiseValue.on('change', function () { + applyOrRemoveFilter($inputCheckNoise.is(':checked'), 'noise', { + noise: parseInt(this.value, 10), + }); }); -$inputCheckPixelate.on('change', function() { - applyOrRemoveFilter(this.checked, 'pixelate', { - blocksize: parseInt($inputRangePixelateValue.val(), 10) - }); +$inputCheckPixelate.on('change', function () { + applyOrRemoveFilter(this.checked, 'pixelate', { + blocksize: parseInt($inputRangePixelateValue.val(), 10), + }); }); -$inputRangePixelateValue.on('change', function() { - applyOrRemoveFilter($inputCheckPixelate.is(':checked'), 'pixelate', { - blocksize: parseInt(this.value, 10) - }); +$inputRangePixelateValue.on('change', function () { + applyOrRemoveFilter($inputCheckPixelate.is(':checked'), 'pixelate', { + blocksize: parseInt(this.value, 10), + }); }); -$inputCheckTint.on('change', function() { - applyOrRemoveFilter(this.checked, 'tint', { - color: tintColorpicker.getColor(), - opacity: parseFloat($inputRangeTintOpacityValue.val()) - }); +$inputCheckTint.on('change', function () { + applyOrRemoveFilter(this.checked, 'tint', { + color: tintColorpicker.getColor(), + opacity: parseFloat($inputRangeTintOpacityValue.val()), + }); }); -tintColorpicker.on('selectColor', function(e) { - applyOrRemoveFilter($inputCheckTint.is(':checked'), 'tint', { - color: e.color - }); +tintColorpicker.on('selectColor', function (e) { + applyOrRemoveFilter($inputCheckTint.is(':checked'), 'tint', { + color: e.color, + }); }); -$inputRangeTintOpacityValue.on('change', function() { - applyOrRemoveFilter($inputCheckTint.is(':checked'), 'tint', { - opacity: parseFloat($inputRangeTintOpacityValue.val()) - }); +$inputRangeTintOpacityValue.on('change', function () { + applyOrRemoveFilter($inputCheckTint.is(':checked'), 'tint', { + opacity: parseFloat($inputRangeTintOpacityValue.val()), + }); }); -$inputCheckMultiply.on('change', function() { - applyOrRemoveFilter(this.checked, 'multiply', { - color: multiplyColorpicker.getColor() - }); +$inputCheckMultiply.on('change', function () { + applyOrRemoveFilter(this.checked, 'multiply', { + color: multiplyColorpicker.getColor(), + }); }); -multiplyColorpicker.on('selectColor', function(e) { - applyOrRemoveFilter($inputCheckMultiply.is(':checked'), 'multiply', { - color: e.color - }); +multiplyColorpicker.on('selectColor', function (e) { + applyOrRemoveFilter($inputCheckMultiply.is(':checked'), 'multiply', { + color: e.color, + }); }); -$inputCheckBlend.on('change', function() { - applyOrRemoveFilter(this.checked, 'blend', { - color: blendColorpicker.getColor(), - mode: $selectBlendType.val() - }); +$inputCheckBlend.on('change', function () { + applyOrRemoveFilter(this.checked, 'blend', { + color: blendColorpicker.getColor(), + mode: $selectBlendType.val(), + }); }); -blendColorpicker.on('selectColor', function(e) { - applyOrRemoveFilter($inputCheckBlend.is(':checked'), 'blend', { - color: e.color - }); +blendColorpicker.on('selectColor', function (e) { + applyOrRemoveFilter($inputCheckBlend.is(':checked'), 'blend', { + color: e.color, + }); }); -$selectBlendType.on('change', function() { - applyOrRemoveFilter($inputCheckBlend.is(':checked'), 'blend', { - mode: this.value - }); +$selectBlendType.on('change', function () { + applyOrRemoveFilter($inputCheckBlend.is(':checked'), 'blend', { + mode: this.value, + }); }); -$inputCheckColorFilter.on('change', function() { - applyOrRemoveFilter(this.checked, 'colorFilter', { - color: '#FFFFFF', - threshold: $inputRangeColorFilterValue.val() - }); +$inputCheckColorFilter.on('change', function () { + applyOrRemoveFilter(this.checked, 'colorFilter', { + color: '#FFFFFF', + threshold: $inputRangeColorFilterValue.val(), + }); }); -$inputRangeColorFilterValue.on('change', function() { - applyOrRemoveFilter($inputCheckColorFilter.is(':checked'), 'colorFilter', { - threshold: this.value - }); +$inputRangeColorFilterValue.on('change', function () { + applyOrRemoveFilter($inputCheckColorFilter.is(':checked'), 'colorFilter', { + threshold: this.value, + }); }); // Etc.. // Load sample image -imageEditor.loadImageFromURL('img/sampleImage.jpg', 'SampleImage').then(function(sizeValue) { - console.log(sizeValue); - imageEditor.clearUndoStack(); +imageEditor.loadImageFromURL('img/sampleImage.jpg', 'SampleImage').then(function (sizeValue) { + console.log(sizeValue); + imageEditor.clearUndoStack(); }); // IE9 Unselectable -$('.menu').on('selectstart', function() { - return false; +$('.menu').on('selectstart', function () { + return false; }); diff --git a/examples/js/service-mobile.js b/examples/js/service-mobile.js index 9d741399b..24031b6c9 100644 --- a/examples/js/service-mobile.js +++ b/examples/js/service-mobile.js @@ -11,9 +11,9 @@ var MAX_RESOLUTION = 3264 * 2448; // 8MP (Mega Pixel) var supportingFileAPI = !!(window.File && window.FileList && window.FileReader); var rImageType = /data:(image\/.+);base64,/; var shapeOpt = { - fill: '#fff', - stroke: '#000', - strokeWidth: 10 + fill: '#fff', + stroke: '#000', + strokeWidth: 10, }; var activeObjectId; @@ -67,504 +67,534 @@ var $inputCheckTransparent = $('#input-check-transparent'); // Colorpicker var iconColorpicker = tui.colorPicker.create({ - container: $('#tui-icon-color-picker')[0], - color: '#000000' + container: $('#tui-icon-color-picker')[0], + color: '#000000', }); var textColorpicker = tui.colorPicker.create({ - container: $('#tui-text-color-picker')[0], - color: '#000000' + container: $('#tui-text-color-picker')[0], + color: '#000000', }); var brushColorpicker = tui.colorPicker.create({ - container: $('#tui-brush-color-picker')[0], - color: '#000000' + container: $('#tui-brush-color-picker')[0], + color: '#000000', }); var shapeColorpicker = tui.colorPicker.create({ - container: $('#tui-shape-color-picker')[0], - color: '#000000' + container: $('#tui-shape-color-picker')[0], + color: '#000000', }); // Create image editor var imageEditor = new tui.ImageEditor('.tui-image-editor', { - cssMaxWidth: document.documentElement.clientWidth, - cssMaxHeight: document.documentElement.clientHeight, - selectionStyle: { - cornerSize: 50, - rotatingPointOffset: 100 - } + cssMaxWidth: document.documentElement.clientWidth, + cssMaxHeight: document.documentElement.clientHeight, + selectionStyle: { + cornerSize: 50, + rotatingPointOffset: 100, + }, }); var $displayingSubMenu, $displayingHiddenMenu; function hexToRGBa(hex, alpha) { - var r = parseInt(hex.slice(1, 3), 16); - var g = parseInt(hex.slice(3, 5), 16); - var b = parseInt(hex.slice(5, 7), 16); - var a = alpha || 1; + var r = parseInt(hex.slice(1, 3), 16); + var g = parseInt(hex.slice(3, 5), 16); + var b = parseInt(hex.slice(5, 7), 16); + var a = alpha || 1; - return 'rgba(' + r + ', ' + g + ', ' + b + ', ' + a + ')'; + return 'rgba(' + r + ', ' + g + ', ' + b + ', ' + a + ')'; } function base64ToBlob(data) { - var mimeString = ''; - var raw, uInt8Array, i, rawLength; + var mimeString = ''; + var raw, uInt8Array, i, rawLength; - raw = data.replace(rImageType, function(header, imageType) { - mimeString = imageType; + raw = data.replace(rImageType, function (header, imageType) { + mimeString = imageType; - return ''; - }); + return ''; + }); - raw = atob(raw); - rawLength = raw.length; - uInt8Array = new Uint8Array(rawLength); // eslint-disable-line + raw = atob(raw); + rawLength = raw.length; + uInt8Array = new Uint8Array(rawLength); // eslint-disable-line - for (i = 0; i < rawLength; i += 1) { - uInt8Array[i] = raw.charCodeAt(i); - } + for (i = 0; i < rawLength; i += 1) { + uInt8Array[i] = raw.charCodeAt(i); + } - return new Blob([uInt8Array], {type: mimeString}); + return new Blob([uInt8Array], { type: mimeString }); } function getBrushSettings() { - var brushWidth = $inputBrushWidthRange.val(); - var brushColor = brushColorpicker.getColor(); + var brushWidth = $inputBrushWidthRange.val(); + var brushColor = brushColorpicker.getColor(); - return { - width: brushWidth, - color: hexToRGBa(brushColor, 0.5) - }; + return { + width: brushWidth, + color: hexToRGBa(brushColor, 0.5), + }; } function activateShapeMode() { - imageEditor.stopDrawingMode(); + imageEditor.stopDrawingMode(); } function activateIconMode() { - imageEditor.stopDrawingMode(); + imageEditor.stopDrawingMode(); } function activateTextMode() { - if (imageEditor.getDrawingMode() !== 'TEXT') { - imageEditor.stopDrawingMode(); - imageEditor.startDrawingMode('TEXT'); - } + if (imageEditor.getDrawingMode() !== 'TEXT') { + imageEditor.stopDrawingMode(); + imageEditor.startDrawingMode('TEXT'); + } } function setTextToolbar(obj) { - var fontSize = obj.fontSize; - var fontColor = obj.fill; + var fontSize = obj.fontSize; + var fontColor = obj.fill; - $inputTextSizeRange.val(fontSize); - textColorpicker.setColor(fontColor); + $inputTextSizeRange.val(fontSize); + textColorpicker.setColor(fontColor); } function setIconToolbar(obj) { - var iconColor = obj.fill; + var iconColor = obj.fill; - iconColorpicker.setColor(iconColor); + iconColorpicker.setColor(iconColor); } function setShapeToolbar(obj) { - var strokeColor, fillColor, isTransparent; - var colorType = $('[name="select-color-type"]:checked').val(); - - if (colorType === 'stroke') { - strokeColor = obj.stroke; - isTransparent = (strokeColor === 'transparent'); - - if (!isTransparent) { - shapeColorpicker.setColor(strokeColor); - } - } else if (colorType === 'fill') { - fillColor = obj.fill; - isTransparent = (fillColor === 'transparent'); - - if (!isTransparent) { - shapeColorpicker.setColor(fillColor); - } - } + var strokeColor, fillColor, isTransparent; + var colorType = $('[name="select-color-type"]:checked').val(); - $inputCheckTransparent.prop('checked', isTransparent); - $inputStrokeWidthRange.val(obj.strokeWith); -} + if (colorType === 'stroke') { + strokeColor = obj.stroke; + isTransparent = strokeColor === 'transparent'; -function showSubMenu(type) { - var index; - - switch (type) { - case 'shape': - index = 3; - break; - case 'icon': - index = 4; - break; - case 'text': - index = 5; - break; - default: - index = 0; + if (!isTransparent) { + shapeColorpicker.setColor(strokeColor); + } + } else if (colorType === 'fill') { + fillColor = obj.fill; + isTransparent = fillColor === 'transparent'; + + if (!isTransparent) { + shapeColorpicker.setColor(fillColor); } + } - $displayingSubMenu.hide(); - $displayingHiddenMenu.hide(); + $inputCheckTransparent.prop('checked', isTransparent); + $inputStrokeWidthRange.val(obj.strokeWith); +} - $displayingSubMenu = $menuButtons.eq(index).parent().find(submenuClass).show(); +function showSubMenu(type) { + var index; + + switch (type) { + case 'shape': + index = 3; + break; + case 'icon': + index = 4; + break; + case 'text': + index = 5; + break; + default: + index = 0; + } + + $displayingSubMenu.hide(); + $displayingHiddenMenu.hide(); + + $displayingSubMenu = $menuButtons.eq(index).parent().find(submenuClass).show(); } // Bind custom event of image editor imageEditor.on({ - undoStackChanged: function(length) { - if (length) { - $btnUndo.removeClass('disabled'); - } else { - $btnUndo.addClass('disabled'); - } - }, - redoStackChanged: function(length) { - if (length) { - $btnRedo.removeClass('disabled'); - } else { - $btnRedo.addClass('disabled'); - } - }, - objectScaled: function(obj) { - if (obj.type === 'text') { - $inputTextSizeRange.val(obj.fontSize); - } - }, - objectActivated: function(obj) { - activeObjectId = obj.id; - if (obj.type === 'rect' || obj.type === 'circle' || obj.type === 'triangle') { - showSubMenu('shape'); - setShapeToolbar(obj); - activateShapeMode(); - } else if (obj.type === 'icon') { - showSubMenu('icon'); - setIconToolbar(obj); - activateIconMode(); - } else if (obj.type === 'text') { - showSubMenu('text'); - setTextToolbar(obj); - activateTextMode(); - } + undoStackChanged: function (length) { + if (length) { + $btnUndo.removeClass('disabled'); + } else { + $btnUndo.addClass('disabled'); + } + }, + redoStackChanged: function (length) { + if (length) { + $btnRedo.removeClass('disabled'); + } else { + $btnRedo.addClass('disabled'); + } + }, + objectScaled: function (obj) { + if (obj.type === 'text') { + $inputTextSizeRange.val(obj.fontSize); + } + }, + objectActivated: function (obj) { + activeObjectId = obj.id; + if (obj.type === 'rect' || obj.type === 'circle' || obj.type === 'triangle') { + showSubMenu('shape'); + setShapeToolbar(obj); + activateShapeMode(); + } else if (obj.type === 'icon') { + showSubMenu('icon'); + setIconToolbar(obj); + activateIconMode(); + } else if (obj.type === 'text') { + showSubMenu('text'); + setTextToolbar(obj); + activateTextMode(); } + }, }); // Image editor controls action -$menuButtons.on('click', function() { - $displayingSubMenu = $(this).parent().find(submenuClass).show(); - $displayingHiddenMenu = $(this).parent().find(hiddenmenuClass); +$menuButtons.on('click', function () { + $displayingSubMenu = $(this).parent().find(submenuClass).show(); + $displayingHiddenMenu = $(this).parent().find(hiddenmenuClass); }); -$submenuButtons.on('click', function() { - $displayingHiddenMenu.hide(); - $displayingHiddenMenu = $(this).parent().find(hiddenmenuClass).show(); +$submenuButtons.on('click', function () { + $displayingHiddenMenu.hide(); + $displayingHiddenMenu = $(this).parent().find(hiddenmenuClass).show(); }); -$btnShowMenu.on('click', function() { - $displayingSubMenu.hide(); - $displayingHiddenMenu.hide(); - $msg.show(); +$btnShowMenu.on('click', function () { + $displayingSubMenu.hide(); + $displayingHiddenMenu.hide(); + $msg.show(); - imageEditor.stopDrawingMode(); + imageEditor.stopDrawingMode(); }); // Image load action -$inputImage.on('change', function(event) { - var file; - var img; - var resolution; +$inputImage.on('change', function (event) { + var file; + var img; + var resolution; - if (!supportingFileAPI) { - alert('This browser does not support file-api'); - } + if (!supportingFileAPI) { + alert('This browser does not support file-api'); + } - file = event.target.files[0]; + file = event.target.files[0]; - if (file) { - img = new Image(); + if (file) { + img = new Image(); - img.onload = function() { - resolution = this.width * this.height; + img.onload = function () { + resolution = this.width * this.height; - if (resolution <= MAX_RESOLUTION) { - imageEditor.loadImageFromFile(file).then(function() { - imageEditor.clearUndoStack(); - }); - } else { - alert('Loaded image\'s resolution is too large!\nRecommended resolution is 3264 * 2448!'); - } + if (resolution <= MAX_RESOLUTION) { + imageEditor.loadImageFromFile(file).then(function () { + imageEditor.clearUndoStack(); + }); + } else { + alert("Loaded image's resolution is too large!\nRecommended resolution is 3264 * 2448!"); + } - URL.revokeObjectURL(file); - }; + URL.revokeObjectURL(file); + }; - img.src = URL.createObjectURL(file); - } + img.src = URL.createObjectURL(file); + } }); // Undo action -$btnUndo.on('click', function() { - if (!$(this).hasClass('disabled')) { - imageEditor.undo(); - } +$btnUndo.on('click', function () { + if (!$(this).hasClass('disabled')) { + imageEditor.undo(); + } }); // Redo action -$btnRedo.on('click', function() { - if (!$(this).hasClass('disabled')) { - imageEditor.redo(); - } +$btnRedo.on('click', function () { + if (!$(this).hasClass('disabled')) { + imageEditor.redo(); + } }); // Remove active object action -$btnRemoveActiveObject.on('click', function() { - imageEditor.removeObject(activeObjectId); +$btnRemoveActiveObject.on('click', function () { + imageEditor.removeObject(activeObjectId); }); // Download action -$btnDownload.on('click', function() { - var imageName = imageEditor.getImageName(); - var dataURL = imageEditor.toDataURL(); - var blob, type, w; - - if (supportingFileAPI) { - blob = base64ToBlob(dataURL); - type = blob.type.split('/')[1]; - if (imageName.split('.').pop() !== type) { - imageName += '.' + type; - } - - // Library: FileSaver - saveAs - saveAs(blob, imageName); // eslint-disable-line - } else { - alert('This browser needs a file-server'); - w = window.open(); - w.document.body.innerHTML = ''; +$btnDownload.on('click', function () { + var imageName = imageEditor.getImageName(); + var dataURL = imageEditor.toDataURL(); + var blob, type, w; + + if (supportingFileAPI) { + blob = base64ToBlob(dataURL); + type = blob.type.split('/')[1]; + if (imageName.split('.').pop() !== type) { + imageName += '.' + type; } + + // Library: FileSaver - saveAs + saveAs(blob, imageName); // eslint-disable-line + } else { + alert('This browser needs a file-server'); + w = window.open(); + w.document.body.innerHTML = ''; + } }); // Crop menu action -$btnCrop.on('click', function() { - imageEditor.startDrawingMode('CROPPER'); +$btnCrop.on('click', function () { + imageEditor.startDrawingMode('CROPPER'); }); -$btnApplyCrop.on('click', function() { - imageEditor.crop(imageEditor.getCropzoneRect()).then(function() { - imageEditor.stopDrawingMode(); - $subMenus.removeClass('show'); - $hiddenMenus.removeClass('show'); - }); +$btnApplyCrop.on('click', function () { + imageEditor.crop(imageEditor.getCropzoneRect()).then(function () { + imageEditor.stopDrawingMode(); + $subMenus.removeClass('show'); + $hiddenMenus.removeClass('show'); + }); }); // Orientation menu action -$btnRotateClockwise.on('click', function() { - imageEditor.rotate(90); +$btnRotateClockwise.on('click', function () { + imageEditor.rotate(90); }); -$btnRotateCounterClockWise.on('click', function() { - imageEditor.rotate(-90); +$btnRotateCounterClockWise.on('click', function () { + imageEditor.rotate(-90); }); -$btnFlipX.on('click', function() { - imageEditor.flipX(); +$btnFlipX.on('click', function () { + imageEditor.flipX(); }); -$btnFlipY.on('click', function() { - imageEditor.flipY(); +$btnFlipY.on('click', function () { + imageEditor.flipY(); }); // Icon menu action -$btnAddArrowIcon.on('click', function() { - imageEditor.addIcon('arrow'); +$btnAddArrowIcon.on('click', function () { + imageEditor.addIcon('arrow'); }); -$btnAddCancelIcon.on('click', function() { - imageEditor.addIcon('cancel'); +$btnAddCancelIcon.on('click', function () { + imageEditor.addIcon('cancel'); }); -$btnAddCustomIcon.on('click', function() { - imageEditor.addIcon('customArrow'); +$btnAddCustomIcon.on('click', function () { + imageEditor.addIcon('customArrow'); }); -iconColorpicker.on('selectColor', function(event) { - imageEditor.changeIconColor(activeObjectId, event.color); +iconColorpicker.on('selectColor', function (event) { + imageEditor.changeIconColor(activeObjectId, event.color); }); // Text menu action -$btnAddText.on('click', function() { - var initText = 'DoubleClick'; - - imageEditor.startDrawingMode('TEXT'); - imageEditor.addText(initText, { - styles: { - fontSize: parseInt($inputTextSizeRange.val(), 10) - } - }); -}); +$btnAddText.on('click', function () { + var initText = 'DoubleClick'; -$btnChangeTextStyle.on('click', function() { - var styleType = $(this).attr('data-style-type'); - var styleObj = {}; - var styleObjKey; - - switch (styleType) { - case 'bold': - styleObjKey = 'fontWeight'; - break; - case 'italic': - styleObjKey = 'fontStyle'; - break; - case 'underline': - styleObjKey = 'underline'; - break; - case 'left': - styleObjKey = 'textAlign'; - break; - case 'center': - styleObjKey = 'textAlign'; - break; - case 'right': - styleObjKey = 'textAlign'; - break; - default: - styleObjKey = ''; - } - - styleObj[styleObjKey] = styleType; - - imageEditor.changeTextStyle(activeObjectId, styleObj); -}); - -$inputTextSizeRange.on('change', function() { - imageEditor.changeTextStyle(activeObjectId, { - fontSize: parseInt($(this).val(), 10) - }); -}); - -textColorpicker.on('selectColor', function(event) { - imageEditor.changeTextStyle(activeObjectId, { - fill: event.color - }); + imageEditor.startDrawingMode('TEXT'); + imageEditor.addText(initText, { + styles: { + fontSize: parseInt($inputTextSizeRange.val(), 10), + }, + }); +}); + +$btnChangeTextStyle.on('click', function () { + var styleType = $(this).attr('data-style-type'); + var styleObj = {}; + var styleObjKey; + + switch (styleType) { + case 'bold': + styleObjKey = 'fontWeight'; + break; + case 'italic': + styleObjKey = 'fontStyle'; + break; + case 'underline': + styleObjKey = 'underline'; + break; + case 'left': + styleObjKey = 'textAlign'; + break; + case 'center': + styleObjKey = 'textAlign'; + break; + case 'right': + styleObjKey = 'textAlign'; + break; + default: + styleObjKey = ''; + } + + styleObj[styleObjKey] = styleType; + + imageEditor.changeTextStyle(activeObjectId, styleObj); +}); + +$inputTextSizeRange.on('change', function () { + imageEditor.changeTextStyle(activeObjectId, { + fontSize: parseInt($(this).val(), 10), + }); +}); + +textColorpicker.on('selectColor', function (event) { + imageEditor.changeTextStyle(activeObjectId, { + fill: event.color, + }); }); // Draw line menu action -$btnFreeDrawing.on('click', function() { - var settings = getBrushSettings(); +$btnFreeDrawing.on('click', function () { + var settings = getBrushSettings(); - imageEditor.stopDrawingMode(); - imageEditor.startDrawingMode('FREE_DRAWING', settings); + imageEditor.stopDrawingMode(); + imageEditor.startDrawingMode('FREE_DRAWING', settings); }); -$btnLineDrawing.on('click', function() { - var settings = getBrushSettings(); +$btnLineDrawing.on('click', function () { + var settings = getBrushSettings(); - imageEditor.stopDrawingMode(); - imageEditor.startDrawingMode('LINE_DRAWING', settings); + imageEditor.stopDrawingMode(); + imageEditor.startDrawingMode('LINE_DRAWING', settings); }); -$inputBrushWidthRange.on('change', function() { - imageEditor.setBrush({ - width: parseInt($(this).val(), 10) - }); +$inputBrushWidthRange.on('change', function () { + imageEditor.setBrush({ + width: parseInt($(this).val(), 10), + }); }); -brushColorpicker.on('selectColor', function(event) { - imageEditor.setBrush({ - color: hexToRGBa(event.color, 0.5) - }); +brushColorpicker.on('selectColor', function (event) { + imageEditor.setBrush({ + color: hexToRGBa(event.color, 0.5), + }); }); // Add shape menu action -$btnAddRect.on('click', function() { - imageEditor.addShape('rect', tui.util.extend({ +$btnAddRect.on('click', function () { + imageEditor.addShape( + 'rect', + tui.util.extend( + { width: 500, - height: 300 - }, shapeOpt)); -}); - -$btnAddSquare.on('click', function() { - imageEditor.addShape('rect', tui.util.extend({ + height: 300, + }, + shapeOpt + ) + ); +}); + +$btnAddSquare.on('click', function () { + imageEditor.addShape( + 'rect', + tui.util.extend( + { width: 400, height: 400, - isRegular: true - }, shapeOpt)); -}); - -$btnAddEllipse.on('click', function() { - imageEditor.addShape('circle', tui.util.extend({ + isRegular: true, + }, + shapeOpt + ) + ); +}); + +$btnAddEllipse.on('click', function () { + imageEditor.addShape( + 'circle', + tui.util.extend( + { rx: 300, - ry: 200 - }, shapeOpt)); + ry: 200, + }, + shapeOpt + ) + ); }); -$btnAddCircle.on('click', function() { - imageEditor.addShape('circle', tui.util.extend({ +$btnAddCircle.on('click', function () { + imageEditor.addShape( + 'circle', + tui.util.extend( + { rx: 200, ry: 200, - isRegular: true - }, shapeOpt)); -}); - -$btnAddTriangle.on('click', function() { - imageEditor.addShape('triangle', tui.util.extend({ + isRegular: true, + }, + shapeOpt + ) + ); +}); + +$btnAddTriangle.on('click', function () { + imageEditor.addShape( + 'triangle', + tui.util.extend( + { width: 500, height: 400, - isRegular: true - }, shapeOpt)); + isRegular: true, + }, + shapeOpt + ) + ); }); -$inputStrokeWidthRange.on('change', function() { - imageEditor.changeShape(activeObjectId, { - strokeWidth: parseInt($(this).val(), 10) - }); +$inputStrokeWidthRange.on('change', function () { + imageEditor.changeShape(activeObjectId, { + strokeWidth: parseInt($(this).val(), 10), + }); }); -$inputCheckTransparent.on('change', function() { - var colorType = $('[name="select-color-type"]:checked').val(); - var isTransparent = $(this).prop('checked'); - var color; +$inputCheckTransparent.on('change', function () { + var colorType = $('[name="select-color-type"]:checked').val(); + var isTransparent = $(this).prop('checked'); + var color; - if (!isTransparent) { - color = shapeColorpicker.getColor(); - } else { - color = 'transparent'; - } + if (!isTransparent) { + color = shapeColorpicker.getColor(); + } else { + color = 'transparent'; + } - if (colorType === 'stroke') { - imageEditor.changeShape(activeObjectId, { - stroke: color - }); - } else if (colorType === 'fill') { - imageEditor.changeShape(activeObjectId, { - fill: color - }); - } + if (colorType === 'stroke') { + imageEditor.changeShape(activeObjectId, { + stroke: color, + }); + } else if (colorType === 'fill') { + imageEditor.changeShape(activeObjectId, { + fill: color, + }); + } }); -shapeColorpicker.on('selectColor', function(event) { - var colorType = $('[name="select-color-type"]:checked').val(); - var isTransparent = $inputCheckTransparent.prop('checked'); - var color = event.color; +shapeColorpicker.on('selectColor', function (event) { + var colorType = $('[name="select-color-type"]:checked').val(); + var isTransparent = $inputCheckTransparent.prop('checked'); + var color = event.color; - if (isTransparent) { - return; - } + if (isTransparent) { + return; + } - if (colorType === 'stroke') { - imageEditor.changeShape(activeObjectId, { - stroke: color - }); - } else if (colorType === 'fill') { - imageEditor.changeShape(activeObjectId, { - fill: color - }); - } + if (colorType === 'stroke') { + imageEditor.changeShape(activeObjectId, { + stroke: color, + }); + } else if (colorType === 'fill') { + imageEditor.changeShape(activeObjectId, { + fill: color, + }); + } }); // Load sample image -imageEditor.loadImageFromURL('img/sampleImage.jpg', 'SampleImage').then(function() { - imageEditor.clearUndoStack(); +imageEditor.loadImageFromURL('img/sampleImage.jpg', 'SampleImage').then(function () { + imageEditor.clearUndoStack(); }); diff --git a/examples/js/theme/black-theme.js b/examples/js/theme/black-theme.js index 8da037c35..9954a497c 100644 --- a/examples/js/theme/black-theme.js +++ b/examples/js/theme/black-theme.js @@ -1,76 +1,76 @@ var blackTheme = { - 'common.bi.image': 'https://uicdn.toast.com/toastui/img/tui-image-editor-bi.png', - 'common.bisize.width': '251px', - 'common.bisize.height': '21px', - 'common.backgroundImage': 'none', - 'common.backgroundColor': '#1e1e1e', - 'common.border': '0px', + 'common.bi.image': 'https://uicdn.toast.com/toastui/img/tui-image-editor-bi.png', + 'common.bisize.width': '251px', + 'common.bisize.height': '21px', + 'common.backgroundImage': 'none', + 'common.backgroundColor': '#1e1e1e', + 'common.border': '0px', - // header - 'header.backgroundImage': 'none', - 'header.backgroundColor': 'transparent', - 'header.border': '0px', + // header + 'header.backgroundImage': 'none', + 'header.backgroundColor': 'transparent', + 'header.border': '0px', - // load button - 'loadButton.backgroundColor': '#fff', - 'loadButton.border': '1px solid #ddd', - 'loadButton.color': '#222', - 'loadButton.fontFamily': '\'Noto Sans\', sans-serif', - 'loadButton.fontSize': '12px', + // load button + 'loadButton.backgroundColor': '#fff', + 'loadButton.border': '1px solid #ddd', + 'loadButton.color': '#222', + 'loadButton.fontFamily': "'Noto Sans', sans-serif", + 'loadButton.fontSize': '12px', - // download button - 'downloadButton.backgroundColor': '#fdba3b', - 'downloadButton.border': '1px solid #fdba3b', - 'downloadButton.color': '#fff', - 'downloadButton.fontFamily': '\'Noto Sans\', sans-serif', - 'downloadButton.fontSize': '12px', + // download button + 'downloadButton.backgroundColor': '#fdba3b', + 'downloadButton.border': '1px solid #fdba3b', + 'downloadButton.color': '#fff', + 'downloadButton.fontFamily': "'Noto Sans', sans-serif", + 'downloadButton.fontSize': '12px', - // main icons - 'menu.normalIcon.color': '#8a8a8a', - 'menu.activeIcon.color': '#555555', - 'menu.disabledIcon.color': '#434343', - 'menu.hoverIcon.color': '#e9e9e9', - 'menu.iconSize.width': '24px', - 'menu.iconSize.height': '24px', + // main icons + 'menu.normalIcon.color': '#8a8a8a', + 'menu.activeIcon.color': '#555555', + 'menu.disabledIcon.color': '#434343', + 'menu.hoverIcon.color': '#e9e9e9', + 'menu.iconSize.width': '24px', + 'menu.iconSize.height': '24px', - // submenu icons - 'submenu.normalIcon.color': '#8a8a8a', - 'submenu.activeIcon.color': '#e9e9e9', - 'submenu.iconSize.width': '32px', - 'submenu.iconSize.height': '32px', + // submenu icons + 'submenu.normalIcon.color': '#8a8a8a', + 'submenu.activeIcon.color': '#e9e9e9', + 'submenu.iconSize.width': '32px', + 'submenu.iconSize.height': '32px', - // submenu primary color - 'submenu.backgroundColor': '#1e1e1e', - 'submenu.partition.color': '#3c3c3c', + // submenu primary color + 'submenu.backgroundColor': '#1e1e1e', + 'submenu.partition.color': '#3c3c3c', - // submenu labels - 'submenu.normalLabel.color': '#8a8a8a', - 'submenu.normalLabel.fontWeight': 'lighter', - 'submenu.activeLabel.color': '#fff', - 'submenu.activeLabel.fontWeight': 'lighter', + // submenu labels + 'submenu.normalLabel.color': '#8a8a8a', + 'submenu.normalLabel.fontWeight': 'lighter', + 'submenu.activeLabel.color': '#fff', + 'submenu.activeLabel.fontWeight': 'lighter', - // checkbox style - 'checkbox.border': '0px', - 'checkbox.backgroundColor': '#fff', + // checkbox style + 'checkbox.border': '0px', + 'checkbox.backgroundColor': '#fff', - // range style - 'range.pointer.color': '#fff', - 'range.bar.color': '#666', - 'range.subbar.color': '#d1d1d1', + // range style + 'range.pointer.color': '#fff', + 'range.bar.color': '#666', + 'range.subbar.color': '#d1d1d1', - 'range.disabledPointer.color': '#414141', - 'range.disabledBar.color': '#282828', - 'range.disabledSubbar.color': '#414141', + 'range.disabledPointer.color': '#414141', + 'range.disabledBar.color': '#282828', + 'range.disabledSubbar.color': '#414141', - 'range.value.color': '#fff', - 'range.value.fontWeight': 'lighter', - 'range.value.fontSize': '11px', - 'range.value.border': '1px solid #353535', - 'range.value.backgroundColor': '#151515', - 'range.title.color': '#fff', - 'range.title.fontWeight': 'lighter', + 'range.value.color': '#fff', + 'range.value.fontWeight': 'lighter', + 'range.value.fontSize': '11px', + 'range.value.border': '1px solid #353535', + 'range.value.backgroundColor': '#151515', + 'range.title.color': '#fff', + 'range.title.fontWeight': 'lighter', - // colorpicker style - 'colorpicker.button.border': '1px solid #1e1e1e', - 'colorpicker.title.color': '#fff' + // colorpicker style + 'colorpicker.button.border': '1px solid #1e1e1e', + 'colorpicker.title.color': '#fff', }; diff --git a/examples/js/theme/white-theme.js b/examples/js/theme/white-theme.js index 8fc55ddc4..9815882d3 100644 --- a/examples/js/theme/white-theme.js +++ b/examples/js/theme/white-theme.js @@ -1,76 +1,76 @@ var whiteTheme = { - 'common.bi.image': 'https://uicdn.toast.com/toastui/img/tui-image-editor-bi.png', - 'common.bisize.width': '251px', - 'common.bisize.height': '21px', - 'common.backgroundImage': './img/bg.png', - 'common.backgroundColor': '#fff', - 'common.border': '1px solid #c1c1c1', + 'common.bi.image': 'https://uicdn.toast.com/toastui/img/tui-image-editor-bi.png', + 'common.bisize.width': '251px', + 'common.bisize.height': '21px', + 'common.backgroundImage': './img/bg.png', + 'common.backgroundColor': '#fff', + 'common.border': '1px solid #c1c1c1', - // header - 'header.backgroundImage': 'none', - 'header.backgroundColor': 'transparent', - 'header.border': '0px', + // header + 'header.backgroundImage': 'none', + 'header.backgroundColor': 'transparent', + 'header.border': '0px', - // load button - 'loadButton.backgroundColor': '#fff', - 'loadButton.border': '1px solid #ddd', - 'loadButton.color': '#222', - 'loadButton.fontFamily': '\'Noto Sans\', sans-serif', - 'loadButton.fontSize': '12px', + // load button + 'loadButton.backgroundColor': '#fff', + 'loadButton.border': '1px solid #ddd', + 'loadButton.color': '#222', + 'loadButton.fontFamily': "'Noto Sans', sans-serif", + 'loadButton.fontSize': '12px', - // download button - 'downloadButton.backgroundColor': '#fdba3b', - 'downloadButton.border': '1px solid #fdba3b', - 'downloadButton.color': '#fff', - 'downloadButton.fontFamily': '\'Noto Sans\', sans-serif', - 'downloadButton.fontSize': '12px', + // download button + 'downloadButton.backgroundColor': '#fdba3b', + 'downloadButton.border': '1px solid #fdba3b', + 'downloadButton.color': '#fff', + 'downloadButton.fontFamily': "'Noto Sans', sans-serif", + 'downloadButton.fontSize': '12px', - // main icons - 'menu.normalIcon.color': '#8a8a8a', - 'menu.activeIcon.color': '#555555', - 'menu.disabledIcon.color': '#434343', - 'menu.hoverIcon.color': '#e9e9e9', - 'menu.iconSize.width': '24px', - 'menu.iconSize.height': '24px', + // main icons + 'menu.normalIcon.color': '#8a8a8a', + 'menu.activeIcon.color': '#555555', + 'menu.disabledIcon.color': '#434343', + 'menu.hoverIcon.color': '#e9e9e9', + 'menu.iconSize.width': '24px', + 'menu.iconSize.height': '24px', - // submenu icons - 'submenu.normalIcon.color': '#8a8a8a', - 'submenu.activeIcon.color': '#555555', - 'submenu.iconSize.width': '32px', - 'submenu.iconSize.height': '32px', + // submenu icons + 'submenu.normalIcon.color': '#8a8a8a', + 'submenu.activeIcon.color': '#555555', + 'submenu.iconSize.width': '32px', + 'submenu.iconSize.height': '32px', - // submenu primary color - 'submenu.backgroundColor': 'transparent', - 'submenu.partition.color': '#e5e5e5', + // submenu primary color + 'submenu.backgroundColor': 'transparent', + 'submenu.partition.color': '#e5e5e5', - // submenu labels - 'submenu.normalLabel.color': '#858585', - 'submenu.normalLabel.fontWeight': 'normal', - 'submenu.activeLabel.color': '#000', - 'submenu.activeLabel.fontWeight': 'normal', + // submenu labels + 'submenu.normalLabel.color': '#858585', + 'submenu.normalLabel.fontWeight': 'normal', + 'submenu.activeLabel.color': '#000', + 'submenu.activeLabel.fontWeight': 'normal', - // checkbox style - 'checkbox.border': '1px solid #ccc', - 'checkbox.backgroundColor': '#fff', + // checkbox style + 'checkbox.border': '1px solid #ccc', + 'checkbox.backgroundColor': '#fff', - // rango style - 'range.pointer.color': '#333', - 'range.bar.color': '#ccc', - 'range.subbar.color': '#606060', + // rango style + 'range.pointer.color': '#333', + 'range.bar.color': '#ccc', + 'range.subbar.color': '#606060', - 'range.disabledPointer.color': '#d3d3d3', - 'range.disabledBar.color': 'rgba(85,85,85,0.06)', - 'range.disabledSubbar.color': 'rgba(51,51,51,0.2)', + 'range.disabledPointer.color': '#d3d3d3', + 'range.disabledBar.color': 'rgba(85,85,85,0.06)', + 'range.disabledSubbar.color': 'rgba(51,51,51,0.2)', - 'range.value.color': '#000', - 'range.value.fontWeight': 'normal', - 'range.value.fontSize': '11px', - 'range.value.border': '0', - 'range.value.backgroundColor': '#f5f5f5', - 'range.title.color': '#000', - 'range.title.fontWeight': 'lighter', + 'range.value.color': '#000', + 'range.value.fontWeight': 'normal', + 'range.value.fontSize': '11px', + 'range.value.border': '0', + 'range.value.backgroundColor': '#f5f5f5', + 'range.title.color': '#000', + 'range.title.fontWeight': 'lighter', - // colorpicker style - 'colorpicker.button.border': '0px', - 'colorpicker.title.color': '#000' + // colorpicker style + 'colorpicker.button.border': '0px', + 'colorpicker.title.color': '#000', }; diff --git a/index.d.ts b/index.d.ts index dc445f50b..20d708b9f 100644 --- a/index.d.ts +++ b/index.d.ts @@ -2,314 +2,326 @@ // TypeScript Version: 3.2.2 declare namespace tuiImageEditor { - type AngleType = number; + type AngleType = number; - interface IThemeConfig { - 'common.bi.image'?: string; - 'common.bisize.width'?: string; - 'common.bisize.height'?: string; - 'common.backgroundImage'?: string; - 'common.backgroundColor'?: string; - 'common.border'?: string; - 'header.backgroundImage'?: string; - 'header.backgroundColor'?: string; - 'header.border'?: string; - 'loadButton.backgroundColor'?: string; - 'loadButton.border'?: string; - 'loadButton.color'?: string; - 'loadButton.fontFamily'?: string; - 'loadButton.fontSize'?: string; - 'downloadButton.backgroundColor'?: string; - 'downloadButton.border'?: string; - 'downloadButton.color'?: string; - 'downloadButton.fontFamily'?: string; - 'downloadButton.fontSize'?: string; - 'menu.normalIcon.path'?: string; - 'menu.normalIcon.name'?: string; - 'menu.activeIcon.path'?: string; - 'menu.activeIcon.name'?: string; - 'menu.iconSize.width'?: string; - 'menu.iconSize.height'?: string; - 'submenu.backgroundColor'?: string; - 'submenu.partition.color'?: string; - 'submenu.normalIcon.path'?: string; - 'submenu.normalIcon.name'?: string; - 'submenu.activeIcon.path'?: string; - 'submenu.activeIcon.name'?: string; - 'submenu.iconSize.width'?: string; - 'submenu.iconSize.height'?: string; - 'submenu.normalLabel.color'?: string; - 'submenu.normalLabel.fontWeight'?: string; - 'submenu.activeLabel.color'?: string; - 'submenu.activeLabel.fontWeight'?: string; - 'checkbox.border'?: string; - 'checkbox.backgroundColor'?: string; - 'range.pointer.color'?: string; - 'range.bar.color'?: string; - 'range.subbar.color'?: string; - 'range.value.color'?: string; - 'range.value.fontWeight'?: string; - 'range.value.fontSize'?: string; - 'range.value.border'?: string; - 'range.value.backgroundColor'?: string; - 'range.title.color'?: string; - 'range.title.fontWeight'?: string; - 'colorpicker.button.border'?: string; - 'colorpicker.title.color'?: string; - } + interface IThemeConfig { + 'common.bi.image'?: string; + 'common.bisize.width'?: string; + 'common.bisize.height'?: string; + 'common.backgroundImage'?: string; + 'common.backgroundColor'?: string; + 'common.border'?: string; + 'header.backgroundImage'?: string; + 'header.backgroundColor'?: string; + 'header.border'?: string; + 'loadButton.backgroundColor'?: string; + 'loadButton.border'?: string; + 'loadButton.color'?: string; + 'loadButton.fontFamily'?: string; + 'loadButton.fontSize'?: string; + 'downloadButton.backgroundColor'?: string; + 'downloadButton.border'?: string; + 'downloadButton.color'?: string; + 'downloadButton.fontFamily'?: string; + 'downloadButton.fontSize'?: string; + 'menu.normalIcon.path'?: string; + 'menu.normalIcon.name'?: string; + 'menu.activeIcon.path'?: string; + 'menu.activeIcon.name'?: string; + 'menu.iconSize.width'?: string; + 'menu.iconSize.height'?: string; + 'submenu.backgroundColor'?: string; + 'submenu.partition.color'?: string; + 'submenu.normalIcon.path'?: string; + 'submenu.normalIcon.name'?: string; + 'submenu.activeIcon.path'?: string; + 'submenu.activeIcon.name'?: string; + 'submenu.iconSize.width'?: string; + 'submenu.iconSize.height'?: string; + 'submenu.normalLabel.color'?: string; + 'submenu.normalLabel.fontWeight'?: string; + 'submenu.activeLabel.color'?: string; + 'submenu.activeLabel.fontWeight'?: string; + 'checkbox.border'?: string; + 'checkbox.backgroundColor'?: string; + 'range.pointer.color'?: string; + 'range.bar.color'?: string; + 'range.subbar.color'?: string; + 'range.value.color'?: string; + 'range.value.fontWeight'?: string; + 'range.value.fontSize'?: string; + 'range.value.border'?: string; + 'range.value.backgroundColor'?: string; + 'range.title.color'?: string; + 'range.title.fontWeight'?: string; + 'colorpicker.button.border'?: string; + 'colorpicker.title.color'?: string; + } - interface IIconInfo { - [propName: string]: string; - } + interface IIconInfo { + [propName: string]: string; + } - interface IIconOptions { - fill?: string; - left?: number; - top?: number; - } + interface IIconOptions { + fill?: string; + left?: number; + top?: number; + } - interface IShapeOptions { - fill?: string; - stroke?: string; - strokeWidth?: number; - width?: number; - height?: number; - rx?: number; - ry?: number; - left?: number; - top?: number; - isRegular?: boolean; - } + interface IShapeOptions { + fill?: string; + stroke?: string; + strokeWidth?: number; + width?: number; + height?: number; + rx?: number; + ry?: number; + left?: number; + top?: number; + isRegular?: boolean; + } - interface IGenerateTextOptions { - styles?: ITextStyleConfig; - position?: { - x: number; - y: number; - }; - } + interface IGenerateTextOptions { + styles?: ITextStyleConfig; + position?: { + x: number; + y: number; + }; + } - interface ITextStyleConfig { - fill?: string; - fontFamily?: string; - fontSize?: number; - fontStyle?: string; - fontWeight?: string; - textAlign?: string; - textDecoration?: string; - } + interface ITextStyleConfig { + fill?: string; + fontFamily?: string; + fontSize?: number; + fontStyle?: string; + fontWeight?: string; + textAlign?: string; + textDecoration?: string; + } - interface IRectConfig { - left: number; - top: number; - width: number; - height: number; - } + interface IRectConfig { + left: number; + top: number; + width: number; + height: number; + } - interface ICanvasSize { - width: number; - height: number; - } + interface ICanvasSize { + width: number; + height: number; + } - interface IBrushOptions { - width: number; - color: string; - } + interface IBrushOptions { + width: number; + color: string; + } - interface IPositionConfig { - x: number; - y: number; - originX: string; - originY: string; - } + interface IPositionConfig { + x: number; + y: number; + originX: string; + originY: string; + } - interface IToDataURLOptions { - format?: string; - quality?: number; - multiplier?: number; - left?: number; - top?: number; - width?: number; - height?: number; - } + interface IToDataURLOptions { + format?: string; + quality?: number; + multiplier?: number; + left?: number; + top?: number; + width?: number; + height?: number; + } - interface IGraphicObjectProps { - id?: number; - type?: string; - text?: string; - left?: string | number; - top?: string | number; - width?: string | number; - height?: string | number; - fill?: string; - stroke?: string; - strokeWidth?: string | number; - fontFamily?: string; - fontSize?: number; - fontStyle?: string; - fontWeight?: string; - textAlign?: string; - textDecoration?: string; - opacity?: number; - [propName: string]: number | string | boolean | undefined; - } + interface IGraphicObjectProps { + id?: number; + type?: string; + text?: string; + left?: string | number; + top?: string | number; + width?: string | number; + height?: string | number; + fill?: string; + stroke?: string; + strokeWidth?: string | number; + fontFamily?: string; + fontSize?: number; + fontStyle?: string; + fontWeight?: string; + textAlign?: string; + textDecoration?: string; + opacity?: number; + [propName: string]: number | string | boolean | undefined; + } - interface IIncludeUIOptions { - loadImage?: { - path: string; - name: string; - }; - theme?: IThemeConfig; - menu?: string[]; - initMenu?: string; - uiSize?: { - width: string; - height: string; - }; - menuBarPosition?: string; - usageStatistics?: boolean; - } + interface IIncludeUIOptions { + loadImage?: { + path: string; + name: string; + }; + theme?: IThemeConfig; + menu?: string[]; + initMenu?: string; + uiSize?: { + width: string; + height: string; + }; + menuBarPosition?: string; + usageStatistics?: boolean; + } - interface ISelectionStyleConfig { - cornerStyle?: string; - cornerSize?: number; - cornerColor?: string; - cornerStrokeColor?: string; - transparentCorners?: boolean; - lineWidth?: number; - borderColor?: string; - rotatingPointOffset?: number; - } + interface ISelectionStyleConfig { + cornerStyle?: string; + cornerSize?: number; + cornerColor?: string; + cornerStrokeColor?: string; + transparentCorners?: boolean; + lineWidth?: number; + borderColor?: string; + rotatingPointOffset?: number; + } - interface IObjectProps { // icon, shape - fill: string; - height: number; - id: number; - left: number; - opacity: number; - stroke: string | null; - strokeWidth: number | null; - top: number; - type: string; - width: number; - } + interface IObjectProps { + // icon, shape + fill: string; + height: number; + id: number; + left: number; + opacity: number; + stroke: string | null; + strokeWidth: number | null; + top: number; + type: string; + width: number; + } - interface ITextObjectProps extends IObjectProps { - fontFamily: string; - fontSize: string; - fontStyle: string; - text: string; - textAlign: string; - textDecoration: string; - } + interface ITextObjectProps extends IObjectProps { + fontFamily: string; + fontSize: string; + fontStyle: string; + text: string; + textAlign: string; + textDecoration: string; + } - interface IFilterResolveObject { - type: string; - action: string; - } + interface IFilterResolveObject { + type: string; + action: string; + } - interface ICropResolveObject { - oldWidth: number; - oldHeight: number; - newWidth: number; - newHeight: number; - } + interface ICropResolveObject { + oldWidth: number; + oldHeight: number; + newWidth: number; + newHeight: number; + } - interface IFlipXYResolveObject { - flipX: boolean; - flipY: boolean; - angle: AngleType; - } + interface IFlipXYResolveObject { + flipX: boolean; + flipY: boolean; + angle: AngleType; + } - interface IOptions { - includeUI?: IIncludeUIOptions; - cssMaxWidth?: number; - cssMaxHeight?: number; - usageStatistics?: boolean; - selectionStyle?: ISelectionStyleConfig; - } + interface IOptions { + includeUI?: IIncludeUIOptions; + cssMaxWidth?: number; + cssMaxHeight?: number; + usageStatistics?: boolean; + selectionStyle?: ISelectionStyleConfig; + } - interface IUIDimension { - height?: string; - width?: string; - } - - interface IImageDimension { - oldHeight?: number; - oldWidth?: number; - newHeight?: number; - newWidth?: number; - } - - interface IEditorSize { - uiSize?: IUIDimension, - imageSize?: IImageDimension - } + interface IUIDimension { + height?: string; + width?: string; + } - interface UI { - resizeEditor(dimension: IEditorSize): Promise; - } + interface IImageDimension { + oldHeight?: number; + oldWidth?: number; + newHeight?: number; + newWidth?: number; + } - class ImageEditor { - constructor(wrapper: string | Element, options: IOptions); - public ui: UI; + interface IEditorSize { + uiSize?: IUIDimension; + imageSize?: IImageDimension; + } - public addIcon(type: string, options?: IIconOptions): Promise; - public addImageObject(imgUrl: string): Promise; - public addShape(type: string, options?: IShapeOptions): Promise; - public addText(text: string, options?: IGenerateTextOptions): Promise; - public applyFilter(type: string, options?: { - maskObjId: number - }, isSilent?: boolean): Promise; - public changeCursor(cursorType: string): void; - public changeIconColor(id: number, color: string): Promise; - public changeSelectableAll(selectable: boolean): void; - public changeShape(id: number, options?: IShapeOptions, isSilent?: boolean): Promise; - public changeText(id: number, text?: string): Promise; - public changeTextStyle(id: number, styleObj: ITextStyleConfig, isSilent?: boolean): Promise; - public clearObjects(): Promise; - public clearRedoStack(): void; - public clearUndoStack(): void; - public crop(rect: IRectConfig): Promise; - public deactivateAll(): void; - public destroy(): void; - public discardSelection(): void; - public flipX(): Promise; - public flipY(): Promise; - public getCanvasSize(): ICanvasSize; - public getCropzoneRect(): IRectConfig; - public getDrawingMode(): string; - public getImageName(): string; - public getObjectPosition(id: number, originX: string, originY: string): ICanvasSize; - public getObjectProperties(id: number, keys: string | string[] | IGraphicObjectProps): IGraphicObjectProps; - public hasFilter(type: string): boolean; - public isEmptyRedoStack(): boolean; - public isEmptyUndoStack(): boolean; - public loadImageFromFile(imgFile: File, imageName?: string): Promise; - public loadImageFromURL(url: string, imageName?: string): Promise; - public redo(): Promise; - public registerIcons(infos: IIconInfo): void; - public removeActiveObject(): void; - public removeFilter(type?: string): Promise; - public removeObject(id: number): Promise; - public resetFlip(): Promise; - public resizeCanvasDimension(dimension: ICanvasSize): Promise; - public rotate(angle: AngleType, isSilent?: boolean): Promise; - public setAngle(angle: AngleType, isSilent?: boolean): Promise; - public setBrush(option: IBrushOptions): void; - public setCropzoneRect(mode?: number): void; - public setDrawingShape(type: string, options?: IShapeOptions): void; - public setObjectPosition(id: number, posInfo?: IPositionConfig): Promise; - public setObjectProperties(id: number, keyValue?: IGraphicObjectProps): Promise; - public setObjectPropertiesQuietly(id: number, keyValue?: IGraphicObjectProps): Promise; - public startDrawingMode(mode: string, option?: {width?: number, color?: string}): boolean; - public stopDrawingMode(): void; - public toDataURL(options?: IToDataURLOptions): string; - public undo(): Promise; - public on(eventName: string, handler: (...args: any[]) => void): void; - } + interface UI { + resizeEditor(dimension: IEditorSize): Promise; + } + + class ImageEditor { + constructor(wrapper: string | Element, options: IOptions); + public ui: UI; + + public addIcon(type: string, options?: IIconOptions): Promise; + public addImageObject(imgUrl: string): Promise; + public addShape(type: string, options?: IShapeOptions): Promise; + public addText(text: string, options?: IGenerateTextOptions): Promise; + public applyFilter( + type: string, + options?: { + maskObjId: number; + }, + isSilent?: boolean + ): Promise; + public changeCursor(cursorType: string): void; + public changeIconColor(id: number, color: string): Promise; + public changeSelectableAll(selectable: boolean): void; + public changeShape(id: number, options?: IShapeOptions, isSilent?: boolean): Promise; + public changeText(id: number, text?: string): Promise; + public changeTextStyle( + id: number, + styleObj: ITextStyleConfig, + isSilent?: boolean + ): Promise; + public clearObjects(): Promise; + public clearRedoStack(): void; + public clearUndoStack(): void; + public crop(rect: IRectConfig): Promise; + public deactivateAll(): void; + public destroy(): void; + public discardSelection(): void; + public flipX(): Promise; + public flipY(): Promise; + public getCanvasSize(): ICanvasSize; + public getCropzoneRect(): IRectConfig; + public getDrawingMode(): string; + public getImageName(): string; + public getObjectPosition(id: number, originX: string, originY: string): ICanvasSize; + public getObjectProperties( + id: number, + keys: string | string[] | IGraphicObjectProps + ): IGraphicObjectProps; + public hasFilter(type: string): boolean; + public isEmptyRedoStack(): boolean; + public isEmptyUndoStack(): boolean; + public loadImageFromFile(imgFile: File, imageName?: string): Promise; + public loadImageFromURL(url: string, imageName?: string): Promise; + public redo(): Promise; + public registerIcons(infos: IIconInfo): void; + public removeActiveObject(): void; + public removeFilter(type?: string): Promise; + public removeObject(id: number): Promise; + public resetFlip(): Promise; + public resizeCanvasDimension(dimension: ICanvasSize): Promise; + public rotate(angle: AngleType, isSilent?: boolean): Promise; + public setAngle(angle: AngleType, isSilent?: boolean): Promise; + public setBrush(option: IBrushOptions): void; + public setCropzoneRect(mode?: number): void; + public setDrawingShape(type: string, options?: IShapeOptions): void; + public setObjectPosition(id: number, posInfo?: IPositionConfig): Promise; + public setObjectProperties(id: number, keyValue?: IGraphicObjectProps): Promise; + public setObjectPropertiesQuietly(id: number, keyValue?: IGraphicObjectProps): Promise; + public startDrawingMode(mode: string, option?: { width?: number; color?: string }): boolean; + public stopDrawingMode(): void; + public toDataURL(options?: IToDataURLOptions): string; + public undo(): Promise; + public on(eventName: string, handler: (...args: any[]) => void): void; + } } declare module 'tui-image-editor' { - export = tuiImageEditor.ImageEditor; + export = tuiImageEditor.ImageEditor; } diff --git a/jsdoc.conf.json b/jsdoc.conf.json index 00f15329b..dfb11538d 100644 --- a/jsdoc.conf.json +++ b/jsdoc.conf.json @@ -1,16 +1,11 @@ { "source": { - "include": [ - "src", - "README.md" - ], + "include": ["src", "README.md"], "exclude": [], "includePattern": ".+\\.js(doc)?$", "excludePattern": "(^|\\/|\\\\)_" }, - "plugins": [ - "plugins/markdown" - ], + "plugins": ["plugins/markdown"], "templates": { "name": "ImageEditor", "logo": { diff --git a/karma.conf.js b/karma.conf.js index 62bff46d6..34d9dcca6 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -1,178 +1,174 @@ /* eslint-disable consts-on-top, no-process-env, require-jsdoc */ /* eslint-disable no-process-env, require-jsdoc */ const webdriverConfig = { - hostname: 'fe.nhnent.com', - port: 4444, - remoteHost: true + hostname: 'fe.nhnent.com', + port: 4444, + remoteHost: true, }; function setConfig(defaultConfig, server) { - if (server === 'ne') { - defaultConfig.customLaunchers = { - 'IE9': { - base: 'WebDriver', - config: webdriverConfig, - browserName: 'internet explorer', - version: '9' - }, - 'IE10': { - base: 'WebDriver', - config: webdriverConfig, - browserName: 'internet explorer', - version: '10' - }, - 'IE11': { - base: 'WebDriver', - config: webdriverConfig, - browserName: 'internet explorer', - version: '11' - }, - 'Edge': { - base: 'WebDriver', - config: webdriverConfig, - browserName: 'MicrosoftEdge' - }, - 'Chrome-WebDriver': { - base: 'WebDriver', - config: webdriverConfig, - browserName: 'chrome' - }, - 'Firefox-WebDriver': { - base: 'WebDriver', - config: webdriverConfig, - browserName: 'firefox' - }, - 'Safari-WebDriver': { - base: 'WebDriver', - config: webdriverConfig, - browserName: 'safari' - } - }; - defaultConfig.browsers = [ - 'IE9', - 'IE10', - // 'IE11', - // 'Edge', - 'Chrome-WebDriver', - 'Firefox-WebDriver' - // 'Safari-WebDriver' - ]; - defaultConfig.reporters.push('coverage'); - defaultConfig.reporters.push('junit'); - defaultConfig.coverageReporter = { - dir: 'report/coverage/', - reporters: [ - { - type: 'html', - subdir(browser) { - return `report-html/${browser}`; - } - }, - { - type: 'cobertura', - subdir(browser) { - return `report-cobertura/${browser}`; - }, - file: 'cobertura.txt' - } - ] - }; - defaultConfig.junitReporter = { - outputDir: 'report/junit', - suite: '' - }; - } else { - defaultConfig.browsers = [ - 'ChromeHeadless' - ]; - } + if (server === 'ne') { + defaultConfig.customLaunchers = { + IE9: { + base: 'WebDriver', + config: webdriverConfig, + browserName: 'internet explorer', + version: '9', + }, + IE10: { + base: 'WebDriver', + config: webdriverConfig, + browserName: 'internet explorer', + version: '10', + }, + IE11: { + base: 'WebDriver', + config: webdriverConfig, + browserName: 'internet explorer', + version: '11', + }, + Edge: { + base: 'WebDriver', + config: webdriverConfig, + browserName: 'MicrosoftEdge', + }, + 'Chrome-WebDriver': { + base: 'WebDriver', + config: webdriverConfig, + browserName: 'chrome', + }, + 'Firefox-WebDriver': { + base: 'WebDriver', + config: webdriverConfig, + browserName: 'firefox', + }, + 'Safari-WebDriver': { + base: 'WebDriver', + config: webdriverConfig, + browserName: 'safari', + }, + }; + defaultConfig.browsers = [ + 'IE9', + 'IE10', + // 'IE11', + // 'Edge', + 'Chrome-WebDriver', + 'Firefox-WebDriver', + // 'Safari-WebDriver' + ]; + defaultConfig.reporters.push('coverage'); + defaultConfig.reporters.push('junit'); + defaultConfig.coverageReporter = { + dir: 'report/coverage/', + reporters: [ + { + type: 'html', + subdir(browser) { + return `report-html/${browser}`; + }, + }, + { + type: 'cobertura', + subdir(browser) { + return `report-cobertura/${browser}`; + }, + file: 'cobertura.txt', + }, + ], + }; + defaultConfig.junitReporter = { + outputDir: 'report/junit', + suite: '', + }; + } else { + defaultConfig.browsers = ['ChromeHeadless']; + } } -module.exports = function(config) { - const defaultConfig = { - basePath: './', - frameworks: [ - 'jasmine', - 'jquery-3.2.1', - 'es5-shim' - ], - files: [ - // reason for not using karma-jasmine-jquery framework is that including older jasmine-karma file - // included jasmine-karma version is 2.0.5 and this version don't support ie8 - 'node_modules/jasmine-jquery/lib/jasmine-jquery.js', - 'node_modules/fabric/dist/fabric.js', - 'test/index.js', - { - pattern: 'test/fixtures/*.jpg', - watched: false, - included: false, - served: true +module.exports = function (config) { + const defaultConfig = { + basePath: './', + frameworks: ['jasmine', 'jquery-3.2.1', 'es5-shim'], + files: [ + // reason for not using karma-jasmine-jquery framework is that including older jasmine-karma file + // included jasmine-karma version is 2.0.5 and this version don't support ie8 + 'node_modules/jasmine-jquery/lib/jasmine-jquery.js', + 'node_modules/fabric/dist/fabric.js', + 'test/index.js', + { + pattern: 'test/fixtures/*.jpg', + watched: false, + included: false, + served: true, + }, + { + pattern: 'test/fixtures/*.png', + watched: false, + included: false, + served: true, + }, + { + pattern: 'test/fixtures/*.svg', + watched: false, + included: false, + served: true, + }, + ], + preprocessors: { + 'test/index.js': ['webpack', 'sourcemap'], + }, + reporters: ['dots'], + webpack: { + mode: 'development', + devtool: 'inline-source-map', + externals: { + fabric: 'fabric', + }, + module: { + rules: [ + { + test: /\.js$/, + include: /src/, + exclude: /node_modules/, + loader: 'eslint-loader', + enforce: 'pre', + }, + { + test: /\.js$/, + exclude: /(test|node_modules)/, + loader: 'istanbul-instrumenter-loader', + query: { + esModules: true, }, - { - pattern: 'test/fixtures/*.png', - watched: false, - included: false, - served: true + }, + { + test: /\.js$/, + exclude: /node_modules/, + loader: 'babel-loader?cacheDirectory', + options: { + babelrc: true, }, - { - pattern: 'test/fixtures/*.svg', - watched: false, - included: false, - served: true - } + }, + { + test: /\.styl$/, + use: ['css-loader', 'stylus-loader'], + }, + { + test: /\.svg$/, + loader: 'svg-inline-loader', + }, ], - preprocessors: { - 'test/index.js': ['webpack', 'sourcemap'] - }, - reporters: ['dots'], - webpack: { - mode: 'development', - devtool: 'inline-source-map', - externals: { - fabric: 'fabric' - }, - module: { - rules: [ - { - test: /\.js$/, - include: /src/, - exclude: /node_modules/, - loader: 'eslint-loader', - enforce: 'pre' - }, - { - test: /\.js$/, - exclude: /(test|node_modules)/, - loader: 'istanbul-instrumenter-loader', - query: { - esModules: true - } - }, - { - test: /\.js$/, - exclude: /node_modules/, - loader: 'babel-loader?cacheDirectory', - options: { - babelrc: true - } - }, { - test: /\.styl$/, - use: ['css-loader', 'stylus-loader'] - }, { - test: /\.svg$/, - loader: 'svg-inline-loader' - } - ] - } - }, - port: 9876, - colors: true, - logLevel: config.LOG_INFO, - autoWatch: true, - singleRun: true - }; + }, + }, + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + singleRun: true, + }; - /* eslint-disable */ - setConfig(defaultConfig, process.env.KARMA_SERVER); - config.set(defaultConfig); + /* eslint-disable */ + setConfig(defaultConfig, process.env.KARMA_SERVER); + config.set(defaultConfig); }; diff --git a/makesvg.js b/makesvg.js index 837d5e4fc..abc3f5763 100644 --- a/makesvg.js +++ b/makesvg.js @@ -4,28 +4,28 @@ const svgstore = require('svgstore'); const svgDir = './src/svg'; function getFileList(dir) { - const targetDir = `${svgDir}/${dir}`; - const sprites = svgstore(); - fs.readdir(targetDir, (err, files) => { - if (!files) return; - files.forEach(file => { - if (file.match(/^\./)) return; - const id = `${dir}-${file.replace(/\.svg$/, '')}`; - const svg = fs.readFileSync(`${targetDir}/${file}`); - sprites.add(id, svg); - }); - fs.writeFileSync(`./dist/svg/${dir}.svg`, sprites); + const targetDir = `${svgDir}/${dir}`; + const sprites = svgstore(); + fs.readdir(targetDir, (err, files) => { + if (!files) return; + files.forEach((file) => { + if (file.match(/^\./)) return; + const id = `${dir}-${file.replace(/\.svg$/, '')}`; + const svg = fs.readFileSync(`${targetDir}/${file}`); + sprites.add(id, svg); }); + fs.writeFileSync(`./dist/svg/${dir}.svg`, sprites); + }); } mkdirp('./dist/svg', (mkdirpErr) => { - if (mkdirpErr) { - console.error(mkdirpErr); - } else { - fs.readdir(svgDir, (err, dirs) => { - dirs.forEach(dir => { - getFileList(dir); - }); - }); - } + if (mkdirpErr) { + console.error(mkdirpErr); + } else { + fs.readdir(svgDir, (err, dirs) => { + dirs.forEach((dir) => { + getFileList(dir); + }); + }); + } }); diff --git a/src/js/action.js b/src/js/action.js index 87bf46481..55329f02b 100644 --- a/src/js/action.js +++ b/src/js/action.js @@ -1,512 +1,533 @@ -import {extend} from 'tui-code-snippet'; -import {isSupportFileApi, base64ToBlob, toInteger} from './util'; +import { extend } from 'tui-code-snippet'; +import { isSupportFileApi, base64ToBlob, toInteger } from './util'; import Imagetracer from './helper/imagetracer'; export default { + /** + * Get ui actions + * @returns {Object} actions for ui + * @private + */ + getActions() { + return { + main: this._mainAction(), + shape: this._shapeAction(), + crop: this._cropAction(), + flip: this._flipAction(), + rotate: this._rotateAction(), + text: this._textAction(), + mask: this._maskAction(), + draw: this._drawAction(), + icon: this._iconAction(), + filter: this._filterAction(), + }; + }, - /** - * Get ui actions - * @returns {Object} actions for ui - * @private - */ - getActions() { - return { - main: this._mainAction(), - shape: this._shapeAction(), - crop: this._cropAction(), - flip: this._flipAction(), - rotate: this._rotateAction(), - text: this._textAction(), - mask: this._maskAction(), - draw: this._drawAction(), - icon: this._iconAction(), - filter: this._filterAction() - }; - }, + /** + * Main Action + * @returns {Object} actions for ui main + * @private + */ + _mainAction() { + const exitCropOnAction = () => { + if (this.ui.submenu === 'crop') { + this.stopDrawingMode(); + this.ui.changeMenu('crop'); + } + }; + const setAngleRangeBarOnAction = (angle) => { + if (this.ui.submenu === 'rotate') { + this.ui.rotate.setRangeBarAngle('setAngle', angle); + } + }; + const setFilterStateRangeBarOnAction = (filterOptions) => { + if (this.ui.submenu === 'filter') { + this.ui.filter.setFilterState(filterOptions); + } + }; + const onEndUndoRedo = (result) => { + setAngleRangeBarOnAction(result); + setFilterStateRangeBarOnAction(result); - /** - * Main Action - * @returns {Object} actions for ui main - * @private - */ - _mainAction() { - const exitCropOnAction = () => { - if (this.ui.submenu === 'crop') { - this.stopDrawingMode(); - this.ui.changeMenu('crop'); - } - }; - const setAngleRangeBarOnAction = angle => { - if (this.ui.submenu === 'rotate') { - this.ui.rotate.setRangeBarAngle('setAngle', angle); - } - }; - const setFilterStateRangeBarOnAction = filterOptions => { - if (this.ui.submenu === 'filter') { - this.ui.filter.setFilterState(filterOptions); - } - }; - const onEndUndoRedo = result => { - setAngleRangeBarOnAction(result); - setFilterStateRangeBarOnAction(result); - - return result; - }; + return result; + }; - return extend({ - initLoadImage: (imagePath, imageName) => ( - this.loadImageFromURL(imagePath, imageName).then(sizeValue => { - exitCropOnAction(); - this.ui.initializeImgUrl = imagePath; - this.ui.resizeEditor({imageSize: sizeValue}); - this.clearUndoStack(); - }) - ), - undo: () => { - if (!this.isEmptyUndoStack()) { - exitCropOnAction(); - this.deactivateAll(); - this.undo().then(onEndUndoRedo); - } - }, - redo: () => { - if (!this.isEmptyRedoStack()) { - exitCropOnAction(); - this.deactivateAll(); - this.redo().then(onEndUndoRedo); - } - }, - reset: () => { - exitCropOnAction(); - this.loadImageFromURL(this.ui.initializeImgUrl, 'resetImage').then(sizeValue => { - exitCropOnAction(); - this.ui.resizeEditor({imageSize: sizeValue}); - this.clearUndoStack(); - }); - }, - delete: () => { - this.ui.changeHelpButtonEnabled('delete', false); - exitCropOnAction(); - this.removeActiveObject(); - this.activeObjectId = null; - }, - deleteAll: () => { - exitCropOnAction(); - this.clearObjects(); - this.ui.changeHelpButtonEnabled('delete', false); - this.ui.changeHelpButtonEnabled('deleteAll', false); - }, - load: file => { - if (!isSupportFileApi()) { - alert('This browser does not support file-api'); - } + return extend( + { + initLoadImage: (imagePath, imageName) => + this.loadImageFromURL(imagePath, imageName).then((sizeValue) => { + exitCropOnAction(); + this.ui.initializeImgUrl = imagePath; + this.ui.resizeEditor({ imageSize: sizeValue }); + this.clearUndoStack(); + }), + undo: () => { + if (!this.isEmptyUndoStack()) { + exitCropOnAction(); + this.deactivateAll(); + this.undo().then(onEndUndoRedo); + } + }, + redo: () => { + if (!this.isEmptyRedoStack()) { + exitCropOnAction(); + this.deactivateAll(); + this.redo().then(onEndUndoRedo); + } + }, + reset: () => { + exitCropOnAction(); + this.loadImageFromURL(this.ui.initializeImgUrl, 'resetImage').then((sizeValue) => { + exitCropOnAction(); + this.ui.resizeEditor({ imageSize: sizeValue }); + this.clearUndoStack(); + }); + }, + delete: () => { + this.ui.changeHelpButtonEnabled('delete', false); + exitCropOnAction(); + this.removeActiveObject(); + this.activeObjectId = null; + }, + deleteAll: () => { + exitCropOnAction(); + this.clearObjects(); + this.ui.changeHelpButtonEnabled('delete', false); + this.ui.changeHelpButtonEnabled('deleteAll', false); + }, + load: (file) => { + if (!isSupportFileApi()) { + alert('This browser does not support file-api'); + } - this.ui.initializeImgUrl = URL.createObjectURL(file); - this.loadImageFromFile(file).then(sizeValue => { - exitCropOnAction(); - this.clearUndoStack(); - this.ui.activeMenuEvent(); - this.ui.resizeEditor({imageSize: sizeValue}); - })['catch'](message => ( - Promise.reject(message) - )); - }, - download: () => { - const dataURL = this.toDataURL(); - let imageName = this.getImageName(); - let blob, type, w; + this.ui.initializeImgUrl = URL.createObjectURL(file); + this.loadImageFromFile(file) + .then((sizeValue) => { + exitCropOnAction(); + this.clearUndoStack(); + this.ui.activeMenuEvent(); + this.ui.resizeEditor({ imageSize: sizeValue }); + }) + ['catch']((message) => Promise.reject(message)); + }, + download: () => { + const dataURL = this.toDataURL(); + let imageName = this.getImageName(); + let blob, type, w; - if (isSupportFileApi() && window.saveAs) { - blob = base64ToBlob(dataURL); - type = blob.type.split('/')[1]; - if (imageName.split('.').pop() !== type) { - imageName += `.${type}`; - } - saveAs(blob, imageName); // eslint-disable-line - } else { - w = window.open(); - w.document.body.innerHTML = ``; - } + if (isSupportFileApi() && window.saveAs) { + blob = base64ToBlob(dataURL); + type = blob.type.split('/')[1]; + if (imageName.split('.').pop() !== type) { + imageName += `.${type}`; } - }, this._commonAction()); - }, + saveAs(blob, imageName); // eslint-disable-line + } else { + w = window.open(); + w.document.body.innerHTML = ``; + } + }, + }, + this._commonAction() + ); + }, - /** - * Icon Action - * @returns {Object} actions for ui icon - * @private - */ - _iconAction() { - return extend({ - changeColor: color => { - if (this.activeObjectId) { - this.changeIconColor(this.activeObjectId, color); - } + /** + * Icon Action + * @returns {Object} actions for ui icon + * @private + */ + _iconAction() { + return extend( + { + changeColor: (color) => { + if (this.activeObjectId) { + this.changeIconColor(this.activeObjectId, color); + } + }, + addIcon: (iconType, iconColor) => { + this.startDrawingMode('ICON'); + this.setDrawingIcon(iconType, iconColor); + }, + cancelAddIcon: () => { + this.ui.icon.clearIconType(); + this.changeSelectableAll(true); + this.changeCursor('default'); + this.stopDrawingMode(); + }, + registDefalutIcons: (type, path) => { + const iconObj = {}; + iconObj[type] = path; + this.registerIcons(iconObj); + }, + registCustomIcon: (imgUrl, file) => { + const imagetracer = new Imagetracer(); + imagetracer.imageToSVG( + imgUrl, + (svgstr) => { + const [, svgPath] = svgstr.match(/path[^>]*d="([^"]*)"/); + const iconObj = {}; + iconObj[file.name] = svgPath; + this.registerIcons(iconObj); + this.addIcon(file.name, { + left: 100, + top: 100, + }); }, - addIcon: (iconType, iconColor) => { - this.startDrawingMode('ICON'); - this.setDrawingIcon(iconType, iconColor); - }, - cancelAddIcon: () => { - this.ui.icon.clearIconType(); - this.changeSelectableAll(true); - this.changeCursor('default'); - this.stopDrawingMode(); - }, - registDefalutIcons: (type, path) => { - const iconObj = {}; - iconObj[type] = path; - this.registerIcons(iconObj); - }, - registCustomIcon: (imgUrl, file) => { - const imagetracer = new Imagetracer(); - imagetracer.imageToSVG( - imgUrl, - svgstr => { - const [, svgPath] = svgstr.match(/path[^>]*d="([^"]*)"/); - const iconObj = {}; - iconObj[file.name] = svgPath; - this.registerIcons(iconObj); - this.addIcon(file.name, { - left: 100, - top: 100 - }); - }, Imagetracer.tracerDefaultOption() - ); - } - }, this._commonAction()); - }, + Imagetracer.tracerDefaultOption() + ); + }, + }, + this._commonAction() + ); + }, - /** - * Draw Action - * @returns {Object} actions for ui draw - * @private - */ - _drawAction() { - return extend({ - setDrawMode: (type, settings) => { - this.stopDrawingMode(); - if (type === 'free') { - this.startDrawingMode('FREE_DRAWING', settings); - } else { - this.startDrawingMode('LINE_DRAWING', settings); - } - }, - setColor: color => { - this.setBrush({ - color - }); - } - }, this._commonAction()); - }, + /** + * Draw Action + * @returns {Object} actions for ui draw + * @private + */ + _drawAction() { + return extend( + { + setDrawMode: (type, settings) => { + this.stopDrawingMode(); + if (type === 'free') { + this.startDrawingMode('FREE_DRAWING', settings); + } else { + this.startDrawingMode('LINE_DRAWING', settings); + } + }, + setColor: (color) => { + this.setBrush({ + color, + }); + }, + }, + this._commonAction() + ); + }, - /** - * Mask Action - * @returns {Object} actions for ui mask - * @private - */ - _maskAction() { - return extend({ - loadImageFromURL: (imgUrl, file) => ( - this.loadImageFromURL(this.toDataURL(), 'FilterImage').then(() => { - this.addImageObject(imgUrl).then(() => { - URL.revokeObjectURL(file); - }); - }) - ), - applyFilter: () => { - this.applyFilter('mask', { - maskObjId: this.activeObjectId - }); - } - }, this._commonAction()); - }, + /** + * Mask Action + * @returns {Object} actions for ui mask + * @private + */ + _maskAction() { + return extend( + { + loadImageFromURL: (imgUrl, file) => + this.loadImageFromURL(this.toDataURL(), 'FilterImage').then(() => { + this.addImageObject(imgUrl).then(() => { + URL.revokeObjectURL(file); + }); + }), + applyFilter: () => { + this.applyFilter('mask', { + maskObjId: this.activeObjectId, + }); + }, + }, + this._commonAction() + ); + }, - /** - * Text Action - * @returns {Object} actions for ui text - * @private - */ - _textAction() { - return extend({ - changeTextStyle: (styleObj, isSilent) => { - if (this.activeObjectId) { - this.changeTextStyle(this.activeObjectId, styleObj, isSilent); - } - } - }, this._commonAction()); - }, + /** + * Text Action + * @returns {Object} actions for ui text + * @private + */ + _textAction() { + return extend( + { + changeTextStyle: (styleObj, isSilent) => { + if (this.activeObjectId) { + this.changeTextStyle(this.activeObjectId, styleObj, isSilent); + } + }, + }, + this._commonAction() + ); + }, - /** - * Rotate Action - * @returns {Object} actions for ui rotate - * @private - */ - _rotateAction() { - return extend({ - rotate: (angle, isSilent) => { - this.rotate(angle, isSilent); - this.ui.resizeEditor(); - this.ui.rotate.setRangeBarAngle('rotate', angle); - }, - setAngle: (angle, isSilent) => { - this.setAngle(angle, isSilent); - this.ui.resizeEditor(); - this.ui.rotate.setRangeBarAngle('setAngle', angle); - } - }, this._commonAction()); - }, + /** + * Rotate Action + * @returns {Object} actions for ui rotate + * @private + */ + _rotateAction() { + return extend( + { + rotate: (angle, isSilent) => { + this.rotate(angle, isSilent); + this.ui.resizeEditor(); + this.ui.rotate.setRangeBarAngle('rotate', angle); + }, + setAngle: (angle, isSilent) => { + this.setAngle(angle, isSilent); + this.ui.resizeEditor(); + this.ui.rotate.setRangeBarAngle('setAngle', angle); + }, + }, + this._commonAction() + ); + }, - /** - * Shape Action - * @returns {Object} actions for ui shape - * @private - */ - _shapeAction() { - return extend({ - changeShape: (changeShapeObject, isSilent) => { - if (this.activeObjectId) { - this.changeShape(this.activeObjectId, changeShapeObject, isSilent); - } - }, - setDrawingShape: shapeType => { - this.setDrawingShape(shapeType); - } - }, this._commonAction()); - }, + /** + * Shape Action + * @returns {Object} actions for ui shape + * @private + */ + _shapeAction() { + return extend( + { + changeShape: (changeShapeObject, isSilent) => { + if (this.activeObjectId) { + this.changeShape(this.activeObjectId, changeShapeObject, isSilent); + } + }, + setDrawingShape: (shapeType) => { + this.setDrawingShape(shapeType); + }, + }, + this._commonAction() + ); + }, - /** - * Crop Action - * @returns {Object} actions for ui crop - * @private - */ - _cropAction() { - return extend({ - crop: () => { - const cropRect = this.getCropzoneRect(); - if (cropRect) { - this.crop(cropRect).then(() => { - this.stopDrawingMode(); - this.ui.resizeEditor(); - this.ui.changeMenu('crop'); - })['catch'](message => ( - Promise.reject(message) - )); - } - }, - cancel: () => { + /** + * Crop Action + * @returns {Object} actions for ui crop + * @private + */ + _cropAction() { + return extend( + { + crop: () => { + const cropRect = this.getCropzoneRect(); + if (cropRect) { + this.crop(cropRect) + .then(() => { this.stopDrawingMode(); + this.ui.resizeEditor(); this.ui.changeMenu('crop'); - }, - /* eslint-disable */ - preset: presetType => { - switch (presetType) { - case 'preset-square': - this.setCropzoneRect(1 / 1); - break; - case 'preset-3-2': - this.setCropzoneRect(3 / 2); - break; - case 'preset-4-3': - this.setCropzoneRect(4 / 3); - break; - case 'preset-5-4': - this.setCropzoneRect(5 / 4); - break; - case 'preset-7-5': - this.setCropzoneRect(7 / 5); - break; - case 'preset-16-9': - this.setCropzoneRect(16 / 9); - break; - default: - this.setCropzoneRect(); - this.ui.crop.changeApplyButtonStatus(false); - break; - } - } - }, this._commonAction()); - }, + }) + ['catch']((message) => Promise.reject(message)); + } + }, + cancel: () => { + this.stopDrawingMode(); + this.ui.changeMenu('crop'); + }, + /* eslint-disable */ + preset: (presetType) => { + switch (presetType) { + case 'preset-square': + this.setCropzoneRect(1 / 1); + break; + case 'preset-3-2': + this.setCropzoneRect(3 / 2); + break; + case 'preset-4-3': + this.setCropzoneRect(4 / 3); + break; + case 'preset-5-4': + this.setCropzoneRect(5 / 4); + break; + case 'preset-7-5': + this.setCropzoneRect(7 / 5); + break; + case 'preset-16-9': + this.setCropzoneRect(16 / 9); + break; + default: + this.setCropzoneRect(); + this.ui.crop.changeApplyButtonStatus(false); + break; + } + }, + }, + this._commonAction() + ); + }, - /** - * Flip Action - * @returns {Object} actions for ui flip - * @private - */ - _flipAction() { - return extend({ - flip: flipType => this[flipType]() - }, this._commonAction()); - }, + /** + * Flip Action + * @returns {Object} actions for ui flip + * @private + */ + _flipAction() { + return extend( + { + flip: (flipType) => this[flipType](), + }, + this._commonAction() + ); + }, - /** - * Filter Action - * @returns {Object} actions for ui filter - * @private - */ - _filterAction() { - return extend({ - applyFilter: (applying, type, options, isSilent) => { + /** + * Filter Action + * @returns {Object} actions for ui filter + * @private + */ + _filterAction() { + return extend( + { + applyFilter: (applying, type, options, isSilent) => { + if (applying) { + this.applyFilter(type, options, isSilent); + } else if (this.hasFilter(type)) { + this.removeFilter(type); + } + }, + }, + this._commonAction() + ); + }, - if (applying) { - this.applyFilter(type, options, isSilent); - } else if (this.hasFilter(type)) { - this.removeFilter(type); - } - } - }, this._commonAction()); - }, + /** + * Image Editor Event Observer + */ + setReAction() { + this.on({ + undoStackChanged: (length) => { + if (length) { + this.ui.changeHelpButtonEnabled('undo', true); + this.ui.changeHelpButtonEnabled('reset', true); + } else { + this.ui.changeHelpButtonEnabled('undo', false); + this.ui.changeHelpButtonEnabled('reset', false); + } + this.ui.resizeEditor(); + }, + redoStackChanged: (length) => { + if (length) { + this.ui.changeHelpButtonEnabled('redo', true); + } else { + this.ui.changeHelpButtonEnabled('redo', false); + } + this.ui.resizeEditor(); + }, + /* eslint-disable complexity */ + objectActivated: (obj) => { + this.activeObjectId = obj.id; - /** - * Image Editor Event Observer - */ - setReAction() { - this.on({ - undoStackChanged: length => { - if (length) { - this.ui.changeHelpButtonEnabled('undo', true); - this.ui.changeHelpButtonEnabled('reset', true); - } else { - this.ui.changeHelpButtonEnabled('undo', false); - this.ui.changeHelpButtonEnabled('reset', false); - } - this.ui.resizeEditor(); - }, - redoStackChanged: length => { - if (length) { - this.ui.changeHelpButtonEnabled('redo', true); - } else { - this.ui.changeHelpButtonEnabled('redo', false); - } - this.ui.resizeEditor(); - }, - /* eslint-disable complexity */ - objectActivated: obj => { - this.activeObjectId = obj.id; - - this.ui.changeHelpButtonEnabled('delete', true); - this.ui.changeHelpButtonEnabled('deleteAll', true); + this.ui.changeHelpButtonEnabled('delete', true); + this.ui.changeHelpButtonEnabled('deleteAll', true); - if (obj.type === 'cropzone') { - this.ui.crop.changeApplyButtonStatus(true); - } else if (['rect', 'circle', 'triangle'].indexOf(obj.type) > -1) { - this.stopDrawingMode(); - if (this.ui.submenu !== 'shape') { - this.ui.changeMenu('shape', false, false); - } - this.ui.shape.setShapeStatus({ - strokeColor: obj.stroke, - strokeWidth: obj.strokeWidth, - fillColor: obj.fill - }); + if (obj.type === 'cropzone') { + this.ui.crop.changeApplyButtonStatus(true); + } else if (['rect', 'circle', 'triangle'].indexOf(obj.type) > -1) { + this.stopDrawingMode(); + if (this.ui.submenu !== 'shape') { + this.ui.changeMenu('shape', false, false); + } + this.ui.shape.setShapeStatus({ + strokeColor: obj.stroke, + strokeWidth: obj.strokeWidth, + fillColor: obj.fill, + }); - this.ui.shape.setMaxStrokeValue(Math.min(obj.width, obj.height)); - } else if (obj.type === 'path' || obj.type === 'line') { - if (this.ui.submenu !== 'draw') { - this.ui.changeMenu('draw', false, false); - this.ui.draw.changeStandbyMode(); - } - } else if (['i-text', 'text'].indexOf(obj.type) > -1) { - if (this.ui.submenu !== 'text') { - this.ui.changeMenu('text', false, false); - } + this.ui.shape.setMaxStrokeValue(Math.min(obj.width, obj.height)); + } else if (obj.type === 'path' || obj.type === 'line') { + if (this.ui.submenu !== 'draw') { + this.ui.changeMenu('draw', false, false); + this.ui.draw.changeStandbyMode(); + } + } else if (['i-text', 'text'].indexOf(obj.type) > -1) { + if (this.ui.submenu !== 'text') { + this.ui.changeMenu('text', false, false); + } - this.ui.text.setTextStyleStateOnAction(obj); - } else if (obj.type === 'icon') { - this.stopDrawingMode(); - if (this.ui.submenu !== 'icon') { - this.ui.changeMenu('icon', false, false); - } - this.ui.icon.setIconPickerColor(obj.fill); - } - }, - /* eslint-enable complexity */ - addText: pos => { - const { - textColor: fill, - fontSize, - fontStyle, - fontWeight, - underline - } = this.ui.text; - const fontFamily = 'Noto Sans'; + this.ui.text.setTextStyleStateOnAction(obj); + } else if (obj.type === 'icon') { + this.stopDrawingMode(); + if (this.ui.submenu !== 'icon') { + this.ui.changeMenu('icon', false, false); + } + this.ui.icon.setIconPickerColor(obj.fill); + } + }, + /* eslint-enable complexity */ + addText: (pos) => { + const { textColor: fill, fontSize, fontStyle, fontWeight, underline } = this.ui.text; + const fontFamily = 'Noto Sans'; - this.addText('Double Click', { - position: pos.originPosition, - styles: {fill, fontSize, fontFamily, fontStyle, fontWeight, underline} - }).then(() => { - this.changeCursor('default'); - }); - }, - addObjectAfter: obj => { - if (obj.type === 'icon') { - this.ui.icon.changeStandbyMode(); - } else if (['rect', 'circle', 'triangle'].indexOf(obj.type) > -1) { - this.ui.shape.setMaxStrokeValue(Math.min(obj.width, obj.height)); - this.ui.shape.changeStandbyMode(); - } - }, - objectScaled: obj => { - if (['i-text', 'text'].indexOf(obj.type) > -1) { - this.ui.text.fontSize = toInteger(obj.fontSize); - } else if (['rect', 'circle', 'triangle'].indexOf(obj.type) >= 0) { - const {width, height} = obj; - const strokeValue = this.ui.shape.getStrokeValue(); - - if (width < strokeValue) { - this.ui.shape.setStrokeValue(width); - } - if (height < strokeValue) { - this.ui.shape.setStrokeValue(height); - } - } - }, - selectionCleared: () => { - this.activeObjectId = null; - if (this.ui.submenu === 'text') { - this.changeCursor('text'); - } else if (this.ui.submenu !== 'draw' && this.ui.submenu !== 'crop') { - this.stopDrawingMode(); - } - } + this.addText('Double Click', { + position: pos.originPosition, + styles: { fill, fontSize, fontFamily, fontStyle, fontWeight, underline }, + }).then(() => { + this.changeCursor('default'); }); - }, + }, + addObjectAfter: (obj) => { + if (obj.type === 'icon') { + this.ui.icon.changeStandbyMode(); + } else if (['rect', 'circle', 'triangle'].indexOf(obj.type) > -1) { + this.ui.shape.setMaxStrokeValue(Math.min(obj.width, obj.height)); + this.ui.shape.changeStandbyMode(); + } + }, + objectScaled: (obj) => { + if (['i-text', 'text'].indexOf(obj.type) > -1) { + this.ui.text.fontSize = toInteger(obj.fontSize); + } else if (['rect', 'circle', 'triangle'].indexOf(obj.type) >= 0) { + const { width, height } = obj; + const strokeValue = this.ui.shape.getStrokeValue(); - /** - * Common Action - * @returns {Object} common actions for ui - * @private - */ - _commonAction() { - return { - modeChange: menu => { - switch (menu) { - case 'text': - this._changeActivateMode('TEXT'); - break; - case 'crop': - this.startDrawingMode('CROPPER'); - break; - case 'shape': - this._changeActivateMode('SHAPE'); - this.setDrawingShape(this.ui.shape.type, this.ui.shape.options); - break; - default: - break; - } - }, - deactivateAll: this.deactivateAll.bind(this), - changeSelectableAll: this.changeSelectableAll.bind(this), - discardSelection: this.discardSelection.bind(this), - stopDrawingMode: this.stopDrawingMode.bind(this) - }; - }, + if (width < strokeValue) { + this.ui.shape.setStrokeValue(width); + } + if (height < strokeValue) { + this.ui.shape.setStrokeValue(height); + } + } + }, + selectionCleared: () => { + this.activeObjectId = null; + if (this.ui.submenu === 'text') { + this.changeCursor('text'); + } else if (this.ui.submenu !== 'draw' && this.ui.submenu !== 'crop') { + this.stopDrawingMode(); + } + }, + }); + }, + + /** + * Common Action + * @returns {Object} common actions for ui + * @private + */ + _commonAction() { + return { + modeChange: (menu) => { + switch (menu) { + case 'text': + this._changeActivateMode('TEXT'); + break; + case 'crop': + this.startDrawingMode('CROPPER'); + break; + case 'shape': + this._changeActivateMode('SHAPE'); + this.setDrawingShape(this.ui.shape.type, this.ui.shape.options); + break; + default: + break; + } + }, + deactivateAll: this.deactivateAll.bind(this), + changeSelectableAll: this.changeSelectableAll.bind(this), + discardSelection: this.discardSelection.bind(this), + stopDrawingMode: this.stopDrawingMode.bind(this), + }; + }, - /** - * Mixin - * @param {ImageEditor} ImageEditor instance - */ - mixin(ImageEditor) { - extend(ImageEditor.prototype, this); - } + /** + * Mixin + * @param {ImageEditor} ImageEditor instance + */ + mixin(ImageEditor) { + extend(ImageEditor.prototype, this); + }, }; diff --git a/src/js/command/addIcon.js b/src/js/command/addIcon.js index d85d03c9f..f3cb25277 100644 --- a/src/js/command/addIcon.js +++ b/src/js/command/addIcon.js @@ -3,42 +3,42 @@ * @fileoverview Add an icon */ import commandFactory from '../factory/command'; -import {Promise} from '../util'; -import {componentNames, commandNames} from '../consts'; +import { Promise } from '../util'; +import { componentNames, commandNames } from '../consts'; -const {ICON} = componentNames; +const { ICON } = componentNames; const command = { - name: commandNames.ADD_ICON, - - /** - * Add an icon - * @param {Graphics} graphics - Graphics instance - * @param {string} type - Icon type ('arrow', 'cancel', custom icon name) - * @param {Object} options - Icon options - * @param {string} [options.fill] - Icon foreground color - * @param {string} [options.left] - Icon x position - * @param {string} [options.top] - Icon y position - * @returns {Promise} - */ - execute(graphics, type, options) { - const iconComp = graphics.getComponent(ICON); - - return iconComp.add(type, options).then(objectProps => { - this.undoData.object = graphics.getObject(objectProps.id); - - return objectProps; - }); - }, - /** - * @param {Graphics} graphics - Graphics instance - * @returns {Promise} - */ - undo(graphics) { - graphics.remove(this.undoData.object); - - return Promise.resolve(); - } + name: commandNames.ADD_ICON, + + /** + * Add an icon + * @param {Graphics} graphics - Graphics instance + * @param {string} type - Icon type ('arrow', 'cancel', custom icon name) + * @param {Object} options - Icon options + * @param {string} [options.fill] - Icon foreground color + * @param {string} [options.left] - Icon x position + * @param {string} [options.top] - Icon y position + * @returns {Promise} + */ + execute(graphics, type, options) { + const iconComp = graphics.getComponent(ICON); + + return iconComp.add(type, options).then((objectProps) => { + this.undoData.object = graphics.getObject(objectProps.id); + + return objectProps; + }); + }, + /** + * @param {Graphics} graphics - Graphics instance + * @returns {Promise} + */ + undo(graphics) { + graphics.remove(this.undoData.object); + + return Promise.resolve(); + }, }; commandFactory.register(command); diff --git a/src/js/command/addImageObject.js b/src/js/command/addImageObject.js index b59611156..bcdc39bb1 100644 --- a/src/js/command/addImageObject.js +++ b/src/js/command/addImageObject.js @@ -3,34 +3,34 @@ * @fileoverview Add an image object */ import commandFactory from '../factory/command'; -import {Promise} from '../util'; -import {commandNames} from '../consts'; +import { Promise } from '../util'; +import { commandNames } from '../consts'; const command = { - name: commandNames.ADD_IMAGE_OBJECT, + name: commandNames.ADD_IMAGE_OBJECT, - /** - * Add an image object - * @param {Graphics} graphics - Graphics instance - * @param {string} imgUrl - Image url to make object - * @returns {Promise} - */ - execute(graphics, imgUrl) { - return graphics.addImageObject(imgUrl).then(objectProps => { - this.undoData.object = graphics.getObject(objectProps.id); + /** + * Add an image object + * @param {Graphics} graphics - Graphics instance + * @param {string} imgUrl - Image url to make object + * @returns {Promise} + */ + execute(graphics, imgUrl) { + return graphics.addImageObject(imgUrl).then((objectProps) => { + this.undoData.object = graphics.getObject(objectProps.id); - return objectProps; - }); - }, - /** - * @param {Graphics} graphics - Graphics instance - * @returns {Promise} - */ - undo(graphics) { - graphics.remove(this.undoData.object); + return objectProps; + }); + }, + /** + * @param {Graphics} graphics - Graphics instance + * @returns {Promise} + */ + undo(graphics) { + graphics.remove(this.undoData.object); - return Promise.resolve(); - } + return Promise.resolve(); + }, }; commandFactory.register(command); diff --git a/src/js/command/addObject.js b/src/js/command/addObject.js index 929f535e1..9c969a097 100644 --- a/src/js/command/addObject.js +++ b/src/js/command/addObject.js @@ -3,43 +3,43 @@ * @fileoverview Add an object */ import commandFactory from '../factory/command'; -import {Promise} from '../util'; -import {commandNames, rejectMessages} from '../consts'; +import { Promise } from '../util'; +import { commandNames, rejectMessages } from '../consts'; const command = { - name: commandNames.ADD_OBJECT, + name: commandNames.ADD_OBJECT, - /** - * Add an object - * @param {Graphics} graphics - Graphics instance - * @param {Object} object - Fabric object - * @returns {Promise} - */ - execute(graphics, object) { - return new Promise((resolve, reject) => { - if (!graphics.contains(object)) { - graphics.add(object); - resolve(object); - } else { - reject(rejectMessages.addedObject); - } - }); - }, - /** - * @param {Graphics} graphics - Graphics instance - * @param {Object} object - Fabric object - * @returns {Promise} - */ - undo(graphics, object) { - return new Promise((resolve, reject) => { - if (graphics.contains(object)) { - graphics.remove(object); - resolve(object); - } else { - reject(rejectMessages.noObject); - } - }); - } + /** + * Add an object + * @param {Graphics} graphics - Graphics instance + * @param {Object} object - Fabric object + * @returns {Promise} + */ + execute(graphics, object) { + return new Promise((resolve, reject) => { + if (!graphics.contains(object)) { + graphics.add(object); + resolve(object); + } else { + reject(rejectMessages.addedObject); + } + }); + }, + /** + * @param {Graphics} graphics - Graphics instance + * @param {Object} object - Fabric object + * @returns {Promise} + */ + undo(graphics, object) { + return new Promise((resolve, reject) => { + if (graphics.contains(object)) { + graphics.remove(object); + resolve(object); + } else { + reject(rejectMessages.noObject); + } + }); + }, }; commandFactory.register(command); diff --git a/src/js/command/addShape.js b/src/js/command/addShape.js index 23ac1e658..6ba342173 100644 --- a/src/js/command/addShape.js +++ b/src/js/command/addShape.js @@ -3,49 +3,49 @@ * @fileoverview Add a shape */ import commandFactory from '../factory/command'; -import {Promise} from '../util'; -import {componentNames, commandNames} from '../consts'; +import { Promise } from '../util'; +import { componentNames, commandNames } from '../consts'; -const {SHAPE} = componentNames; +const { SHAPE } = componentNames; const command = { - name: commandNames.ADD_SHAPE, - - /** - * Add a shape - * @param {Graphics} graphics - Graphics instance - * @param {string} type - Shape type (ex: 'rect', 'circle', 'triangle') - * @param {Object} options - Shape options - * @param {string} [options.fill] - Shape foreground color (ex: '#fff', 'transparent') - * @param {string} [options.stroke] - Shape outline color - * @param {number} [options.strokeWidth] - Shape outline width - * @param {number} [options.width] - Width value (When type option is 'rect', this options can use) - * @param {number} [options.height] - Height value (When type option is 'rect', this options can use) - * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use) - * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use) - * @param {number} [options.left] - Shape x position - * @param {number} [options.top] - Shape y position - * @param {number} [options.isRegular] - Whether resizing shape has 1:1 ratio or not - * @returns {Promise} - */ - execute(graphics, type, options) { - const shapeComp = graphics.getComponent(SHAPE); - - return shapeComp.add(type, options).then(objectProps => { - this.undoData.object = graphics.getObject(objectProps.id); - - return objectProps; - }); - }, - /** - * @param {Graphics} graphics - Graphics instance - * @returns {Promise} - */ - undo(graphics) { - graphics.remove(this.undoData.object); - - return Promise.resolve(); - } + name: commandNames.ADD_SHAPE, + + /** + * Add a shape + * @param {Graphics} graphics - Graphics instance + * @param {string} type - Shape type (ex: 'rect', 'circle', 'triangle') + * @param {Object} options - Shape options + * @param {string} [options.fill] - Shape foreground color (ex: '#fff', 'transparent') + * @param {string} [options.stroke] - Shape outline color + * @param {number} [options.strokeWidth] - Shape outline width + * @param {number} [options.width] - Width value (When type option is 'rect', this options can use) + * @param {number} [options.height] - Height value (When type option is 'rect', this options can use) + * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use) + * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use) + * @param {number} [options.left] - Shape x position + * @param {number} [options.top] - Shape y position + * @param {number} [options.isRegular] - Whether resizing shape has 1:1 ratio or not + * @returns {Promise} + */ + execute(graphics, type, options) { + const shapeComp = graphics.getComponent(SHAPE); + + return shapeComp.add(type, options).then((objectProps) => { + this.undoData.object = graphics.getObject(objectProps.id); + + return objectProps; + }); + }, + /** + * @param {Graphics} graphics - Graphics instance + * @returns {Promise} + */ + undo(graphics) { + graphics.remove(this.undoData.object); + + return Promise.resolve(); + }, }; commandFactory.register(command); diff --git a/src/js/command/addText.js b/src/js/command/addText.js index 9455e7570..edc1d1433 100644 --- a/src/js/command/addText.js +++ b/src/js/command/addText.js @@ -3,63 +3,63 @@ * @fileoverview Add a text object */ import commandFactory from '../factory/command'; -import {Promise} from '../util'; -import {componentNames, commandNames, rejectMessages} from '../consts'; -const {TEXT} = componentNames; +import { Promise } from '../util'; +import { componentNames, commandNames, rejectMessages } from '../consts'; +const { TEXT } = componentNames; const command = { - name: commandNames.ADD_TEXT, + name: commandNames.ADD_TEXT, - /** - * Add a text object - * @param {Graphics} graphics - Graphics instance - * @param {string} text - Initial input text - * @param {Object} [options] Options for text styles - * @param {Object} [options.styles] Initial styles - * @param {string} [options.styles.fill] Color - * @param {string} [options.styles.fontFamily] Font type for text - * @param {number} [options.styles.fontSize] Size - * @param {string} [options.styles.fontStyle] Type of inclination (normal / italic) - * @param {string} [options.styles.fontWeight] Type of thicker or thinner looking (normal / bold) - * @param {string} [options.styles.textAlign] Type of text align (left / center / right) - * @param {string} [options.styles.textDecoration] Type of line (underline / line-through / overline) - * @param {{x: number, y: number}} [options.position] - Initial position - * @returns {Promise} - */ - execute(graphics, text, options) { - const textComp = graphics.getComponent(TEXT); + /** + * Add a text object + * @param {Graphics} graphics - Graphics instance + * @param {string} text - Initial input text + * @param {Object} [options] Options for text styles + * @param {Object} [options.styles] Initial styles + * @param {string} [options.styles.fill] Color + * @param {string} [options.styles.fontFamily] Font type for text + * @param {number} [options.styles.fontSize] Size + * @param {string} [options.styles.fontStyle] Type of inclination (normal / italic) + * @param {string} [options.styles.fontWeight] Type of thicker or thinner looking (normal / bold) + * @param {string} [options.styles.textAlign] Type of text align (left / center / right) + * @param {string} [options.styles.textDecoration] Type of line (underline / line-through / overline) + * @param {{x: number, y: number}} [options.position] - Initial position + * @returns {Promise} + */ + execute(graphics, text, options) { + const textComp = graphics.getComponent(TEXT); - if (this.undoData.object) { - const undoObject = this.undoData.object; + if (this.undoData.object) { + const undoObject = this.undoData.object; - return new Promise((resolve, reject) => { - if (!graphics.contains(undoObject)) { - graphics.add(undoObject); - resolve(undoObject); - } else { - reject(rejectMessages.redo); - } - }); + return new Promise((resolve, reject) => { + if (!graphics.contains(undoObject)) { + graphics.add(undoObject); + resolve(undoObject); + } else { + reject(rejectMessages.redo); } + }); + } - return textComp.add(text, options).then(objectProps => { - const {id} = objectProps; - const textObject = graphics.getObject(id); + return textComp.add(text, options).then((objectProps) => { + const { id } = objectProps; + const textObject = graphics.getObject(id); - this.undoData.object = textObject; + this.undoData.object = textObject; - return objectProps; - }); - }, - /** - * @param {Graphics} graphics - Graphics instance - * @returns {Promise} - */ - undo(graphics) { - graphics.remove(this.undoData.object); + return objectProps; + }); + }, + /** + * @param {Graphics} graphics - Graphics instance + * @returns {Promise} + */ + undo(graphics) { + graphics.remove(this.undoData.object); - return Promise.resolve(); - } + return Promise.resolve(); + }, }; commandFactory.register(command); diff --git a/src/js/command/applyFilter.js b/src/js/command/applyFilter.js index 4cc0f12d3..e759177d1 100644 --- a/src/js/command/applyFilter.js +++ b/src/js/command/applyFilter.js @@ -4,9 +4,9 @@ */ import snippet from 'tui-code-snippet'; import commandFactory from '../factory/command'; -import {componentNames, rejectMessages, commandNames} from '../consts'; +import { componentNames, rejectMessages, commandNames } from '../consts'; -const {FILTER} = componentNames; +const { FILTER } = componentNames; /** * Chched data for undo @@ -16,81 +16,81 @@ let chchedUndoDataForSilent = null; /** * Make undoData - * @param {string} type - Filter type + * @param {string} type - Filter type * @param {Object} prevfilterOption - prev Filter options * @param {Object} options - Filter options * @returns {object} - undo data */ function makeUndoData(type, prevfilterOption, options) { - const undoData = {}; + const undoData = {}; - if (type === 'mask') { - undoData.object = options.mask; - } + if (type === 'mask') { + undoData.object = options.mask; + } - undoData.options = prevfilterOption; + undoData.options = prevfilterOption; - return undoData; + return undoData; } const command = { - name: commandNames.APPLY_FILTER, - - /** - * Apply a filter into an image - * @param {Graphics} graphics - Graphics instance - * @param {string} type - Filter type - * @param {Object} options - Filter options - * @param {number} options.maskObjId - masking image object id - * @param {boolean} isSilent - is silent execution or not - * @returns {Promise} - */ - execute(graphics, type, options, isSilent) { - const filterComp = graphics.getComponent(FILTER); - - if (type === 'mask') { - const maskObj = graphics.getObject(options.maskObjId); - - if (!(maskObj && maskObj.isType('image'))) { - return Promise.reject(rejectMessages.invalidParameters); - } - - snippet.extend(options, {mask: maskObj}); - graphics.remove(options.mask); - } - if (!this.isRedo) { - const prevfilterOption = filterComp.getOptions(type); - const undoData = makeUndoData(type, prevfilterOption, options); - - chchedUndoDataForSilent = this.setUndoData(undoData, chchedUndoDataForSilent, isSilent); - } - - return filterComp.add(type, options); - }, - /** - * @param {Graphics} graphics - Graphics instance - * @param {string} type - Filter type - * @returns {Promise} - */ - undo(graphics, type) { - const filterComp = graphics.getComponent(FILTER); - - if (type === 'mask') { - const mask = this.undoData.object; - graphics.add(mask); - graphics.setActiveObject(mask); - - return filterComp.remove(type); - } - - // options changed case - if (this.undoData.options) { - return filterComp.add(type, this.undoData.options); - } - - // filter added case - return filterComp.remove(type); + name: commandNames.APPLY_FILTER, + + /** + * Apply a filter into an image + * @param {Graphics} graphics - Graphics instance + * @param {string} type - Filter type + * @param {Object} options - Filter options + * @param {number} options.maskObjId - masking image object id + * @param {boolean} isSilent - is silent execution or not + * @returns {Promise} + */ + execute(graphics, type, options, isSilent) { + const filterComp = graphics.getComponent(FILTER); + + if (type === 'mask') { + const maskObj = graphics.getObject(options.maskObjId); + + if (!(maskObj && maskObj.isType('image'))) { + return Promise.reject(rejectMessages.invalidParameters); + } + + snippet.extend(options, { mask: maskObj }); + graphics.remove(options.mask); + } + if (!this.isRedo) { + const prevfilterOption = filterComp.getOptions(type); + const undoData = makeUndoData(type, prevfilterOption, options); + + chchedUndoDataForSilent = this.setUndoData(undoData, chchedUndoDataForSilent, isSilent); } + + return filterComp.add(type, options); + }, + /** + * @param {Graphics} graphics - Graphics instance + * @param {string} type - Filter type + * @returns {Promise} + */ + undo(graphics, type) { + const filterComp = graphics.getComponent(FILTER); + + if (type === 'mask') { + const mask = this.undoData.object; + graphics.add(mask); + graphics.setActiveObject(mask); + + return filterComp.remove(type); + } + + // options changed case + if (this.undoData.options) { + return filterComp.add(type, this.undoData.options); + } + + // filter added case + return filterComp.remove(type); + }, }; commandFactory.register(command); diff --git a/src/js/command/changeIconColor.js b/src/js/command/changeIconColor.js index 750e3b65a..e9541a8ec 100644 --- a/src/js/command/changeIconColor.js +++ b/src/js/command/changeIconColor.js @@ -3,48 +3,48 @@ * @fileoverview Change icon color */ import commandFactory from '../factory/command'; -import {Promise} from '../util'; -import {componentNames, rejectMessages, commandNames} from '../consts'; +import { Promise } from '../util'; +import { componentNames, rejectMessages, commandNames } from '../consts'; -const {ICON} = componentNames; +const { ICON } = componentNames; const command = { - name: commandNames.CHANGE_ICON_COLOR, - - /** - * Change icon color - * @param {Graphics} graphics - Graphics instance - * @param {number} id - object id - * @param {string} color - Color for icon - * @returns {Promise} - */ - execute(graphics, id, color) { - return new Promise((resolve, reject) => { - const iconComp = graphics.getComponent(ICON); - const targetObj = graphics.getObject(id); - - if (!targetObj) { - reject(rejectMessages.noObject); - } - - this.undoData.object = targetObj; - this.undoData.color = iconComp.getColor(targetObj); - iconComp.setColor(color, targetObj); - resolve(); - }); - }, - /** - * @param {Graphics} graphics - Graphics instance - * @returns {Promise} - */ - undo(graphics) { - const iconComp = graphics.getComponent(ICON); - const {object: icon, color} = this.undoData; - - iconComp.setColor(color, icon); - - return Promise.resolve(); - } + name: commandNames.CHANGE_ICON_COLOR, + + /** + * Change icon color + * @param {Graphics} graphics - Graphics instance + * @param {number} id - object id + * @param {string} color - Color for icon + * @returns {Promise} + */ + execute(graphics, id, color) { + return new Promise((resolve, reject) => { + const iconComp = graphics.getComponent(ICON); + const targetObj = graphics.getObject(id); + + if (!targetObj) { + reject(rejectMessages.noObject); + } + + this.undoData.object = targetObj; + this.undoData.color = iconComp.getColor(targetObj); + iconComp.setColor(color, targetObj); + resolve(); + }); + }, + /** + * @param {Graphics} graphics - Graphics instance + * @returns {Promise} + */ + undo(graphics) { + const iconComp = graphics.getComponent(ICON); + const { object: icon, color } = this.undoData; + + iconComp.setColor(color, icon); + + return Promise.resolve(); + }, }; commandFactory.register(command); diff --git a/src/js/command/changeSelection.js b/src/js/command/changeSelection.js index 994376537..7b239f9b5 100644 --- a/src/js/command/changeSelection.js +++ b/src/js/command/changeSelection.js @@ -3,31 +3,31 @@ * @fileoverview change selection */ import commandFactory from '../factory/command'; -import {Promise} from '../util'; -import {commandNames} from '../consts'; -import {getCachedUndoDataForDimension} from '../helper/selectionModifyHelper'; +import { Promise } from '../util'; +import { commandNames } from '../consts'; +import { getCachedUndoDataForDimension } from '../helper/selectionModifyHelper'; const command = { - name: commandNames.CHANGE_SELECTION, + name: commandNames.CHANGE_SELECTION, - execute(graphics, props) { - if (this.isRedo) { - props.forEach(prop => { - graphics.setObjectProperties(prop.id, prop); - }); - } else { - this.undoData = getCachedUndoDataForDimension(); - } + execute(graphics, props) { + if (this.isRedo) { + props.forEach((prop) => { + graphics.setObjectProperties(prop.id, prop); + }); + } else { + this.undoData = getCachedUndoDataForDimension(); + } - return Promise.resolve(); - }, - undo(graphics) { - this.undoData.forEach(datum => { - graphics.setObjectProperties(datum.id, datum); - }); + return Promise.resolve(); + }, + undo(graphics) { + this.undoData.forEach((datum) => { + graphics.setObjectProperties(datum.id, datum); + }); - return Promise.resolve(); - } + return Promise.resolve(); + }, }; commandFactory.register(command); diff --git a/src/js/command/changeShape.js b/src/js/command/changeShape.js index 7983b0baf..a38c92ce1 100644 --- a/src/js/command/changeShape.js +++ b/src/js/command/changeShape.js @@ -3,11 +3,11 @@ * @fileoverview change a shape */ import snippet from 'tui-code-snippet'; -import {Promise} from '../util'; +import { Promise } from '../util'; import commandFactory from '../factory/command'; -import {componentNames, rejectMessages, commandNames} from '../consts'; +import { componentNames, rejectMessages, commandNames } from '../consts'; -const {SHAPE} = componentNames; +const { SHAPE } = componentNames; /** * Chched data for undo @@ -22,65 +22,65 @@ let chchedUndoDataForSilent = null; * @returns {object} - undo data */ function makeUndoData(options, targetObj) { - const undoData = { - object: targetObj, - options: {} - }; + const undoData = { + object: targetObj, + options: {}, + }; - snippet.forEachOwnProperties(options, (value, key) => { - undoData.options[key] = targetObj[key]; - }); + snippet.forEachOwnProperties(options, (value, key) => { + undoData.options[key] = targetObj[key]; + }); - return undoData; + return undoData; } const command = { - name: commandNames.CHANGE_SHAPE, + name: commandNames.CHANGE_SHAPE, - /** - * Change a shape - * @param {Graphics} graphics - Graphics instance - * @param {number} id - object id - * @param {Object} options - Shape options - * @param {string} [options.fill] - Shape foreground color (ex: '#fff', 'transparent') - * @param {string} [options.stroke] - Shape outline color - * @param {number} [options.strokeWidth] - Shape outline width - * @param {number} [options.width] - Width value (When type option is 'rect', this options can use) - * @param {number} [options.height] - Height value (When type option is 'rect', this options can use) - * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use) - * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use) - * @param {number} [options.left] - Shape x position - * @param {number} [options.top] - Shape y position - * @param {number} [options.isRegular] - Whether resizing shape has 1:1 ratio or not - * @param {boolean} isSilent - is silent execution or not - * @returns {Promise} - */ - execute(graphics, id, options, isSilent) { - const shapeComp = graphics.getComponent(SHAPE); - const targetObj = graphics.getObject(id); + /** + * Change a shape + * @param {Graphics} graphics - Graphics instance + * @param {number} id - object id + * @param {Object} options - Shape options + * @param {string} [options.fill] - Shape foreground color (ex: '#fff', 'transparent') + * @param {string} [options.stroke] - Shape outline color + * @param {number} [options.strokeWidth] - Shape outline width + * @param {number} [options.width] - Width value (When type option is 'rect', this options can use) + * @param {number} [options.height] - Height value (When type option is 'rect', this options can use) + * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use) + * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use) + * @param {number} [options.left] - Shape x position + * @param {number} [options.top] - Shape y position + * @param {number} [options.isRegular] - Whether resizing shape has 1:1 ratio or not + * @param {boolean} isSilent - is silent execution or not + * @returns {Promise} + */ + execute(graphics, id, options, isSilent) { + const shapeComp = graphics.getComponent(SHAPE); + const targetObj = graphics.getObject(id); - if (!targetObj) { - return Promise.reject(rejectMessages.noObject); - } + if (!targetObj) { + return Promise.reject(rejectMessages.noObject); + } - if (!this.isRedo) { - const undoData = makeUndoData(options, targetObj); + if (!this.isRedo) { + const undoData = makeUndoData(options, targetObj); - chchedUndoDataForSilent = this.setUndoData(undoData, chchedUndoDataForSilent, isSilent); - } + chchedUndoDataForSilent = this.setUndoData(undoData, chchedUndoDataForSilent, isSilent); + } - return shapeComp.change(targetObj, options); - }, - /** - * @param {Graphics} graphics - Graphics instance - * @returns {Promise} - */ - undo(graphics) { - const shapeComp = graphics.getComponent(SHAPE); - const {object: shape, options} = this.undoData; + return shapeComp.change(targetObj, options); + }, + /** + * @param {Graphics} graphics - Graphics instance + * @returns {Promise} + */ + undo(graphics) { + const shapeComp = graphics.getComponent(SHAPE); + const { object: shape, options } = this.undoData; - return shapeComp.change(shape, options); - } + return shapeComp.change(shape, options); + }, }; commandFactory.register(command); diff --git a/src/js/command/changeText.js b/src/js/command/changeText.js index b09997214..cda188af1 100644 --- a/src/js/command/changeText.js +++ b/src/js/command/changeText.js @@ -3,44 +3,44 @@ * @fileoverview Change a text */ import commandFactory from '../factory/command'; -import {Promise} from '../util'; -import {componentNames, rejectMessages, commandNames} from '../consts'; +import { Promise } from '../util'; +import { componentNames, rejectMessages, commandNames } from '../consts'; -const {TEXT} = componentNames; +const { TEXT } = componentNames; const command = { - name: commandNames.CHANGE_TEXT, - - /** - * Change a text - * @param {Graphics} graphics - Graphics instance - * @param {number} id - object id - * @param {string} text - Changing text - * @returns {Promise} - */ - execute(graphics, id, text) { - const textComp = graphics.getComponent(TEXT); - const targetObj = graphics.getObject(id); - - if (!targetObj) { - return Promise.reject(rejectMessages.noObject); - } - - this.undoData.object = targetObj; - this.undoData.text = textComp.getText(targetObj); - - return textComp.change(targetObj, text); - }, - /** - * @param {Graphics} graphics - Graphics instance - * @returns {Promise} - */ - undo(graphics) { - const textComp = graphics.getComponent(TEXT); - const {object: textObj, text} = this.undoData; - - return textComp.change(textObj, text); + name: commandNames.CHANGE_TEXT, + + /** + * Change a text + * @param {Graphics} graphics - Graphics instance + * @param {number} id - object id + * @param {string} text - Changing text + * @returns {Promise} + */ + execute(graphics, id, text) { + const textComp = graphics.getComponent(TEXT); + const targetObj = graphics.getObject(id); + + if (!targetObj) { + return Promise.reject(rejectMessages.noObject); } + + this.undoData.object = targetObj; + this.undoData.text = textComp.getText(targetObj); + + return textComp.change(targetObj, text); + }, + /** + * @param {Graphics} graphics - Graphics instance + * @returns {Promise} + */ + undo(graphics) { + const textComp = graphics.getComponent(TEXT); + const { object: textObj, text } = this.undoData; + + return textComp.change(textObj, text); + }, }; commandFactory.register(command); diff --git a/src/js/command/changeTextStyle.js b/src/js/command/changeTextStyle.js index 8049d1123..1d0aa4ee2 100644 --- a/src/js/command/changeTextStyle.js +++ b/src/js/command/changeTextStyle.js @@ -4,10 +4,10 @@ */ import snippet from 'tui-code-snippet'; import commandFactory from '../factory/command'; -import {Promise} from '../util'; -import {componentNames, rejectMessages, commandNames} from '../consts'; +import { Promise } from '../util'; +import { componentNames, rejectMessages, commandNames } from '../consts'; -const {TEXT} = componentNames; +const { TEXT } = componentNames; /** * Chched data for undo @@ -22,61 +22,61 @@ let chchedUndoDataForSilent = null; * @returns {object} - undo data */ function makeUndoData(styles, targetObj) { - const undoData = { - object: targetObj, - styles: {} - }; - snippet.forEachOwnProperties(styles, (value, key) => { - const undoValue = targetObj[key]; - undoData.styles[key] = undoValue; - }); + const undoData = { + object: targetObj, + styles: {}, + }; + snippet.forEachOwnProperties(styles, (value, key) => { + const undoValue = targetObj[key]; + undoData.styles[key] = undoValue; + }); - return undoData; + return undoData; } const command = { - name: commandNames.CHANGE_TEXT_STYLE, + name: commandNames.CHANGE_TEXT_STYLE, - /** - * Change text styles - * @param {Graphics} graphics - Graphics instance - * @param {number} id - object id - * @param {Object} styles - text styles - * @param {string} [styles.fill] Color - * @param {string} [styles.fontFamily] Font type for text - * @param {number} [styles.fontSize] Size - * @param {string} [styles.fontStyle] Type of inclination (normal / italic) - * @param {string} [styles.fontWeight] Type of thicker or thinner looking (normal / bold) - * @param {string} [styles.textAlign] Type of text align (left / center / right) - * @param {string} [styles.textDecoration] Type of line (underline / line-through / overline) - * @param {boolean} isSilent - is silent execution or not - * @returns {Promise} - */ - execute(graphics, id, styles, isSilent) { - const textComp = graphics.getComponent(TEXT); - const targetObj = graphics.getObject(id); + /** + * Change text styles + * @param {Graphics} graphics - Graphics instance + * @param {number} id - object id + * @param {Object} styles - text styles + * @param {string} [styles.fill] Color + * @param {string} [styles.fontFamily] Font type for text + * @param {number} [styles.fontSize] Size + * @param {string} [styles.fontStyle] Type of inclination (normal / italic) + * @param {string} [styles.fontWeight] Type of thicker or thinner looking (normal / bold) + * @param {string} [styles.textAlign] Type of text align (left / center / right) + * @param {string} [styles.textDecoration] Type of line (underline / line-through / overline) + * @param {boolean} isSilent - is silent execution or not + * @returns {Promise} + */ + execute(graphics, id, styles, isSilent) { + const textComp = graphics.getComponent(TEXT); + const targetObj = graphics.getObject(id); - if (!targetObj) { - return Promise.reject(rejectMessages.noObject); - } - if (!this.isRedo) { - const undoData = makeUndoData(styles, targetObj); + if (!targetObj) { + return Promise.reject(rejectMessages.noObject); + } + if (!this.isRedo) { + const undoData = makeUndoData(styles, targetObj); - chchedUndoDataForSilent = this.setUndoData(undoData, chchedUndoDataForSilent, isSilent); - } + chchedUndoDataForSilent = this.setUndoData(undoData, chchedUndoDataForSilent, isSilent); + } - return textComp.setStyle(targetObj, styles); - }, - /** - * @param {Graphics} graphics - Graphics instance - * @returns {Promise} - */ - undo(graphics) { - const textComp = graphics.getComponent(TEXT); - const {object: textObj, styles} = this.undoData; + return textComp.setStyle(targetObj, styles); + }, + /** + * @param {Graphics} graphics - Graphics instance + * @returns {Promise} + */ + undo(graphics) { + const textComp = graphics.getComponent(TEXT); + const { object: textObj, styles } = this.undoData; - return textComp.setStyle(textObj, styles); - } + return textComp.setStyle(textObj, styles); + }, }; commandFactory.register(command); diff --git a/src/js/command/clearObjects.js b/src/js/command/clearObjects.js index ec2c12571..a453bb241 100644 --- a/src/js/command/clearObjects.js +++ b/src/js/command/clearObjects.js @@ -3,33 +3,33 @@ * @fileoverview Clear all objects */ import commandFactory from '../factory/command'; -import {Promise} from '../util'; -import {commandNames} from '../consts'; +import { Promise } from '../util'; +import { commandNames } from '../consts'; const command = { - name: commandNames.CLEAR_OBJECTS, + name: commandNames.CLEAR_OBJECTS, - /** - * Clear all objects without background (main) image - * @param {Graphics} graphics - Graphics instance - * @returns {Promise} - */ - execute(graphics) { - return new Promise(resolve => { - this.undoData.objects = graphics.removeAll(); - resolve(); - }); - }, - /** - * @param {Graphics} graphics - Graphics instance - * @returns {Promise} - * @ignore - */ - undo(graphics) { - graphics.add(this.undoData.objects); + /** + * Clear all objects without background (main) image + * @param {Graphics} graphics - Graphics instance + * @returns {Promise} + */ + execute(graphics) { + return new Promise((resolve) => { + this.undoData.objects = graphics.removeAll(); + resolve(); + }); + }, + /** + * @param {Graphics} graphics - Graphics instance + * @returns {Promise} + * @ignore + */ + undo(graphics) { + graphics.add(this.undoData.objects); - return Promise.resolve(); - } + return Promise.resolve(); + }, }; commandFactory.register(command); diff --git a/src/js/command/flip.js b/src/js/command/flip.js index 6bbd01bf8..6f21cd9d9 100644 --- a/src/js/command/flip.js +++ b/src/js/command/flip.js @@ -3,35 +3,35 @@ * @fileoverview Flip an image */ import commandFactory from '../factory/command'; -import {componentNames, commandNames} from '../consts'; +import { componentNames, commandNames } from '../consts'; -const {FLIP} = componentNames; +const { FLIP } = componentNames; const command = { - name: commandNames.FLIP_IMAGE, - - /** - * flip an image - * @param {Graphics} graphics - Graphics instance - * @param {string} type - 'flipX' or 'flipY' or 'reset' - * @returns {Promise} - */ - execute(graphics, type) { - const flipComp = graphics.getComponent(FLIP); - - this.undoData.setting = flipComp.getCurrentSetting(); - - return flipComp[type](); - }, - /** - * @param {Graphics} graphics - Graphics instance - * @returns {Promise} - */ - undo(graphics) { - const flipComp = graphics.getComponent(FLIP); - - return flipComp.set(this.undoData.setting); - } + name: commandNames.FLIP_IMAGE, + + /** + * flip an image + * @param {Graphics} graphics - Graphics instance + * @param {string} type - 'flipX' or 'flipY' or 'reset' + * @returns {Promise} + */ + execute(graphics, type) { + const flipComp = graphics.getComponent(FLIP); + + this.undoData.setting = flipComp.getCurrentSetting(); + + return flipComp[type](); + }, + /** + * @param {Graphics} graphics - Graphics instance + * @returns {Promise} + */ + undo(graphics) { + const flipComp = graphics.getComponent(FLIP); + + return flipComp.set(this.undoData.setting); + }, }; commandFactory.register(command); diff --git a/src/js/command/loadImage.js b/src/js/command/loadImage.js index 1e292004e..af9d17fc0 100644 --- a/src/js/command/loadImage.js +++ b/src/js/command/loadImage.js @@ -3,58 +3,58 @@ * @fileoverview Load a background (main) image */ import commandFactory from '../factory/command'; -import {componentNames, commandNames} from '../consts'; +import { componentNames, commandNames } from '../consts'; -const {IMAGE_LOADER} = componentNames; +const { IMAGE_LOADER } = componentNames; const command = { - name: commandNames.LOAD_IMAGE, - - /** - * Load a background (main) image - * @param {Graphics} graphics - Graphics instance - * @param {string} imageName - Image name - * @param {string} imgUrl - Image Url - * @returns {Promise} - */ - execute(graphics, imageName, imgUrl) { - const loader = graphics.getComponent(IMAGE_LOADER); - const prevImage = loader.getCanvasImage(); - const prevImageWidth = prevImage ? prevImage.width : 0; - const prevImageHeight = prevImage ? prevImage.height : 0; - const objects = graphics.removeAll(true).filter(objectItem => objectItem.type !== 'cropzone'); - - objects.forEach(objectItem => { - objectItem.evented = true; - }); - - this.undoData = { - name: loader.getImageName(), - image: prevImage, - objects - }; - - return loader.load(imageName, imgUrl).then(newImage => ({ - oldWidth: prevImageWidth, - oldHeight: prevImageHeight, - newWidth: newImage.width, - newHeight: newImage.height - })); - }, - - /** - * @param {Graphics} graphics - Graphics instance - * @returns {Promise} - */ - undo(graphics) { - const loader = graphics.getComponent(IMAGE_LOADER); - const {objects, name, image} = this.undoData; - - graphics.removeAll(true); - graphics.add(objects); - - return loader.load(name, image); - } + name: commandNames.LOAD_IMAGE, + + /** + * Load a background (main) image + * @param {Graphics} graphics - Graphics instance + * @param {string} imageName - Image name + * @param {string} imgUrl - Image Url + * @returns {Promise} + */ + execute(graphics, imageName, imgUrl) { + const loader = graphics.getComponent(IMAGE_LOADER); + const prevImage = loader.getCanvasImage(); + const prevImageWidth = prevImage ? prevImage.width : 0; + const prevImageHeight = prevImage ? prevImage.height : 0; + const objects = graphics.removeAll(true).filter((objectItem) => objectItem.type !== 'cropzone'); + + objects.forEach((objectItem) => { + objectItem.evented = true; + }); + + this.undoData = { + name: loader.getImageName(), + image: prevImage, + objects, + }; + + return loader.load(imageName, imgUrl).then((newImage) => ({ + oldWidth: prevImageWidth, + oldHeight: prevImageHeight, + newWidth: newImage.width, + newHeight: newImage.height, + })); + }, + + /** + * @param {Graphics} graphics - Graphics instance + * @returns {Promise} + */ + undo(graphics) { + const loader = graphics.getComponent(IMAGE_LOADER); + const { objects, name, image } = this.undoData; + + graphics.removeAll(true); + graphics.add(objects); + + return loader.load(name, image); + }, }; commandFactory.register(command); diff --git a/src/js/command/removeFilter.js b/src/js/command/removeFilter.js index f98f08ae6..5390994a9 100644 --- a/src/js/command/removeFilter.js +++ b/src/js/command/removeFilter.js @@ -3,37 +3,37 @@ * @fileoverview Remove a filter from an image */ import commandFactory from '../factory/command'; -import {componentNames, commandNames} from '../consts'; +import { componentNames, commandNames } from '../consts'; -const {FILTER} = componentNames; +const { FILTER } = componentNames; const command = { - name: commandNames.REMOVE_FILTER, - - /** - * Remove a filter from an image - * @param {Graphics} graphics - Graphics instance - * @param {string} type - Filter type - * @returns {Promise} - */ - execute(graphics, type) { - const filterComp = graphics.getComponent(FILTER); - - this.undoData.options = filterComp.getOptions(type); - - return filterComp.remove(type); - }, - /** - * @param {Graphics} graphics - Graphics instance - * @param {string} type - Filter type - * @returns {Promise} - */ - undo(graphics, type) { - const filterComp = graphics.getComponent(FILTER); - const {options} = this.undoData; - - return filterComp.add(type, options); - } + name: commandNames.REMOVE_FILTER, + + /** + * Remove a filter from an image + * @param {Graphics} graphics - Graphics instance + * @param {string} type - Filter type + * @returns {Promise} + */ + execute(graphics, type) { + const filterComp = graphics.getComponent(FILTER); + + this.undoData.options = filterComp.getOptions(type); + + return filterComp.remove(type); + }, + /** + * @param {Graphics} graphics - Graphics instance + * @param {string} type - Filter type + * @returns {Promise} + */ + undo(graphics, type) { + const filterComp = graphics.getComponent(FILTER); + const { options } = this.undoData; + + return filterComp.add(type, options); + }, }; commandFactory.register(command); diff --git a/src/js/command/removeObject.js b/src/js/command/removeObject.js index e9540f8d3..05c4d3094 100644 --- a/src/js/command/removeObject.js +++ b/src/js/command/removeObject.js @@ -3,37 +3,37 @@ * @fileoverview Remove an object */ import commandFactory from '../factory/command'; -import {Promise} from '../util'; -import {commandNames, rejectMessages} from '../consts'; +import { Promise } from '../util'; +import { commandNames, rejectMessages } from '../consts'; const command = { - name: commandNames.REMOVE_OBJECT, + name: commandNames.REMOVE_OBJECT, - /** - * Remove an object - * @param {Graphics} graphics - Graphics instance - * @param {number} id - object id - * @returns {Promise} - */ - execute(graphics, id) { - return new Promise((resolve, reject) => { - this.undoData.objects = graphics.removeObjectById(id); - if (this.undoData.objects.length) { - resolve(); - } else { - reject(rejectMessages.noObject); - } - }); - }, - /** - * @param {Graphics} graphics - Graphics instance - * @returns {Promise} - */ - undo(graphics) { - graphics.add(this.undoData.objects); + /** + * Remove an object + * @param {Graphics} graphics - Graphics instance + * @param {number} id - object id + * @returns {Promise} + */ + execute(graphics, id) { + return new Promise((resolve, reject) => { + this.undoData.objects = graphics.removeObjectById(id); + if (this.undoData.objects.length) { + resolve(); + } else { + reject(rejectMessages.noObject); + } + }); + }, + /** + * @param {Graphics} graphics - Graphics instance + * @returns {Promise} + */ + undo(graphics) { + graphics.add(this.undoData.objects); - return Promise.resolve(); - } + return Promise.resolve(); + }, }; commandFactory.register(command); diff --git a/src/js/command/resizeCanvasDimension.js b/src/js/command/resizeCanvasDimension.js index 61f22b7b2..288aafcb3 100644 --- a/src/js/command/resizeCanvasDimension.js +++ b/src/js/command/resizeCanvasDimension.js @@ -3,40 +3,40 @@ * @fileoverview Resize a canvas */ import commandFactory from '../factory/command'; -import {Promise} from '../util'; -import {commandNames} from '../consts'; +import { Promise } from '../util'; +import { commandNames } from '../consts'; const command = { - name: commandNames.RESIZE_CANVAS_DIMENSION, + name: commandNames.RESIZE_CANVAS_DIMENSION, - /** - * resize the canvas with given dimension - * @param {Graphics} graphics - Graphics instance - * @param {{width: number, height: number}} dimension - Max width & height - * @returns {Promise} - */ - execute(graphics, dimension) { - return new Promise(resolve => { - this.undoData.size = { - width: graphics.cssMaxWidth, - height: graphics.cssMaxHeight - }; + /** + * resize the canvas with given dimension + * @param {Graphics} graphics - Graphics instance + * @param {{width: number, height: number}} dimension - Max width & height + * @returns {Promise} + */ + execute(graphics, dimension) { + return new Promise((resolve) => { + this.undoData.size = { + width: graphics.cssMaxWidth, + height: graphics.cssMaxHeight, + }; - graphics.setCssMaxDimension(dimension); - graphics.adjustCanvasDimension(); - resolve(); - }); - }, - /** - * @param {Graphics} graphics - Graphics instance - * @returns {Promise} - */ - undo(graphics) { - graphics.setCssMaxDimension(this.undoData.size); - graphics.adjustCanvasDimension(); + graphics.setCssMaxDimension(dimension); + graphics.adjustCanvasDimension(); + resolve(); + }); + }, + /** + * @param {Graphics} graphics - Graphics instance + * @returns {Promise} + */ + undo(graphics) { + graphics.setCssMaxDimension(this.undoData.size); + graphics.adjustCanvasDimension(); - return Promise.resolve(); - } + return Promise.resolve(); + }, }; commandFactory.register(command); diff --git a/src/js/command/rotate.js b/src/js/command/rotate.js index 368560d77..56567aebb 100644 --- a/src/js/command/rotate.js +++ b/src/js/command/rotate.js @@ -3,9 +3,9 @@ * @fileoverview Rotate an image */ import commandFactory from '../factory/command'; -import {componentNames, commandNames} from '../consts'; +import { componentNames, commandNames } from '../consts'; -const {ROTATION} = componentNames; +const { ROTATION } = componentNames; /** * Chched data for undo @@ -19,47 +19,47 @@ let chchedUndoDataForSilent = null; * @returns {object} - undodata */ function makeUndoData(rotationComp) { - return { - angle: rotationComp.getCurrentAngle() - }; + return { + angle: rotationComp.getCurrentAngle(), + }; } const command = { - name: commandNames.ROTATE_IMAGE, + name: commandNames.ROTATE_IMAGE, - /** - * Rotate an image - * @param {Graphics} graphics - Graphics instance - * @param {string} type - 'rotate' or 'setAngle' - * @param {number} angle - angle value (degree) - * @param {boolean} isSilent - is silent execution or not - * @returns {Promise} - */ - execute(graphics, type, angle, isSilent) { - const rotationComp = graphics.getComponent(ROTATION); + /** + * Rotate an image + * @param {Graphics} graphics - Graphics instance + * @param {string} type - 'rotate' or 'setAngle' + * @param {number} angle - angle value (degree) + * @param {boolean} isSilent - is silent execution or not + * @returns {Promise} + */ + execute(graphics, type, angle, isSilent) { + const rotationComp = graphics.getComponent(ROTATION); - if (!this.isRedo) { - const undoData = makeUndoData(rotationComp); + if (!this.isRedo) { + const undoData = makeUndoData(rotationComp); - chchedUndoDataForSilent = this.setUndoData(undoData, chchedUndoDataForSilent, isSilent); - } - - return rotationComp[type](angle); - }, - /** - * @param {Graphics} graphics - Graphics instance - * @returns {Promise} - */ - undo(graphics) { - const rotationComp = graphics.getComponent(ROTATION); - const [, type, angle] = this.args; + chchedUndoDataForSilent = this.setUndoData(undoData, chchedUndoDataForSilent, isSilent); + } - if (type === 'setAngle') { - return rotationComp[type](this.undoData.angle); - } + return rotationComp[type](angle); + }, + /** + * @param {Graphics} graphics - Graphics instance + * @returns {Promise} + */ + undo(graphics) { + const rotationComp = graphics.getComponent(ROTATION); + const [, type, angle] = this.args; - return rotationComp.rotate(-angle); + if (type === 'setAngle') { + return rotationComp[type](this.undoData.angle); } + + return rotationComp.rotate(-angle); + }, }; commandFactory.register(command); diff --git a/src/js/command/setObjectPosition.js b/src/js/command/setObjectPosition.js index bb729d188..e27312c4a 100644 --- a/src/js/command/setObjectPosition.js +++ b/src/js/command/setObjectPosition.js @@ -3,50 +3,50 @@ * @fileoverview Set object properties */ import commandFactory from '../factory/command'; -import {Promise} from '../util'; -import {commandNames, rejectMessages} from '../consts'; +import { Promise } from '../util'; +import { commandNames, rejectMessages } from '../consts'; const command = { - name: commandNames.SET_OBJECT_POSITION, - - /** - * Set object properties - * @param {Graphics} graphics - Graphics instance - * @param {number} id - object id - * @param {Object} posInfo - position object - * @param {number} posInfo.x - x position - * @param {number} posInfo.y - y position - * @param {string} posInfo.originX - can be 'left', 'center', 'right' - * @param {string} posInfo.originY - can be 'top', 'center', 'bottom' - * @returns {Promise} - */ - execute(graphics, id, posInfo) { - const targetObj = graphics.getObject(id); - - if (!targetObj) { - return Promise.reject(rejectMessages.noObject); - } - - this.undoData.objectId = id; - this.undoData.props = graphics.getObjectProperties(id, ['left', 'top']); - - graphics.setObjectPosition(id, posInfo); - graphics.renderAll(); - - return Promise.resolve(); - }, - /** - * @param {Graphics} graphics - Graphics instance - * @returns {Promise} - */ - undo(graphics) { - const {objectId, props} = this.undoData; - - graphics.setObjectProperties(objectId, props); - graphics.renderAll(); - - return Promise.resolve(); + name: commandNames.SET_OBJECT_POSITION, + + /** + * Set object properties + * @param {Graphics} graphics - Graphics instance + * @param {number} id - object id + * @param {Object} posInfo - position object + * @param {number} posInfo.x - x position + * @param {number} posInfo.y - y position + * @param {string} posInfo.originX - can be 'left', 'center', 'right' + * @param {string} posInfo.originY - can be 'top', 'center', 'bottom' + * @returns {Promise} + */ + execute(graphics, id, posInfo) { + const targetObj = graphics.getObject(id); + + if (!targetObj) { + return Promise.reject(rejectMessages.noObject); } + + this.undoData.objectId = id; + this.undoData.props = graphics.getObjectProperties(id, ['left', 'top']); + + graphics.setObjectPosition(id, posInfo); + graphics.renderAll(); + + return Promise.resolve(); + }, + /** + * @param {Graphics} graphics - Graphics instance + * @returns {Promise} + */ + undo(graphics) { + const { objectId, props } = this.undoData; + + graphics.setObjectProperties(objectId, props); + graphics.renderAll(); + + return Promise.resolve(); + }, }; commandFactory.register(command); diff --git a/src/js/command/setObjectProperties.js b/src/js/command/setObjectProperties.js index f97ef413e..5fdf4c4ac 100644 --- a/src/js/command/setObjectProperties.js +++ b/src/js/command/setObjectProperties.js @@ -4,54 +4,54 @@ */ import snippet from 'tui-code-snippet'; import commandFactory from '../factory/command'; -import {Promise} from '../util'; -import {commandNames, rejectMessages} from '../consts'; +import { Promise } from '../util'; +import { commandNames, rejectMessages } from '../consts'; const command = { - name: commandNames.SET_OBJECT_PROPERTIES, - - /** - * Set object properties - * @param {Graphics} graphics - Graphics instance - * @param {number} id - object id - * @param {Object} props - properties - * @param {string} [props.fill] Color - * @param {string} [props.fontFamily] Font type for text - * @param {number} [props.fontSize] Size - * @param {string} [props.fontStyle] Type of inclination (normal / italic) - * @param {string} [props.fontWeight] Type of thicker or thinner looking (normal / bold) - * @param {string} [props.textAlign] Type of text align (left / center / right) - * @param {string} [props.textDecoration] Type of line (underline / line-through / overline) - * @returns {Promise} - */ - execute(graphics, id, props) { - const targetObj = graphics.getObject(id); - - if (!targetObj) { - return Promise.reject(rejectMessages.noObject); - } - - this.undoData.props = {}; - snippet.forEachOwnProperties(props, (value, key) => { - this.undoData.props[key] = targetObj[key]; - }); - - graphics.setObjectProperties(id, props); - - return Promise.resolve(); - }, - /** - * @param {Graphics} graphics - Graphics instance - * @param {number} id - object id - * @returns {Promise} - */ - undo(graphics, id) { - const {props} = this.undoData; - - graphics.setObjectProperties(id, props); - - return Promise.resolve(); + name: commandNames.SET_OBJECT_PROPERTIES, + + /** + * Set object properties + * @param {Graphics} graphics - Graphics instance + * @param {number} id - object id + * @param {Object} props - properties + * @param {string} [props.fill] Color + * @param {string} [props.fontFamily] Font type for text + * @param {number} [props.fontSize] Size + * @param {string} [props.fontStyle] Type of inclination (normal / italic) + * @param {string} [props.fontWeight] Type of thicker or thinner looking (normal / bold) + * @param {string} [props.textAlign] Type of text align (left / center / right) + * @param {string} [props.textDecoration] Type of line (underline / line-through / overline) + * @returns {Promise} + */ + execute(graphics, id, props) { + const targetObj = graphics.getObject(id); + + if (!targetObj) { + return Promise.reject(rejectMessages.noObject); } + + this.undoData.props = {}; + snippet.forEachOwnProperties(props, (value, key) => { + this.undoData.props[key] = targetObj[key]; + }); + + graphics.setObjectProperties(id, props); + + return Promise.resolve(); + }, + /** + * @param {Graphics} graphics - Graphics instance + * @param {number} id - object id + * @returns {Promise} + */ + undo(graphics, id) { + const { props } = this.undoData; + + graphics.setObjectProperties(id, props); + + return Promise.resolve(); + }, }; commandFactory.register(command); diff --git a/src/js/component/cropper.js b/src/js/component/cropper.js index 8799431c5..a142bedd7 100644 --- a/src/js/component/cropper.js +++ b/src/js/component/cropper.js @@ -6,16 +6,16 @@ import snippet from 'tui-code-snippet'; import fabric from 'fabric'; import Component from '../interface/component'; import Cropzone from '../extension/cropzone'; -import {keyCodes, componentNames, CROPZONE_DEFAULT_OPTIONS} from '../consts'; -import {clamp, fixFloatingPoint} from '../util'; +import { keyCodes, componentNames, CROPZONE_DEFAULT_OPTIONS } from '../consts'; +import { clamp, fixFloatingPoint } from '../util'; const MOUSE_MOVE_THRESHOLD = 10; const DEFAULT_OPTION = { - presetRatio: null, - top: -10, - left: -10, - height: 1, - width: 1 + presetRatio: null, + top: -10, + left: -10, + height: 1, + width: 1, }; /** @@ -26,347 +26,358 @@ const DEFAULT_OPTION = { * @ignore */ class Cropper extends Component { - constructor(graphics) { - super(componentNames.CROPPER, graphics); - - /** - * Cropzone - * @type {Cropzone} - * @private - */ - this._cropzone = null; - - /** - * StartX of Cropzone - * @type {number} - * @private - */ - this._startX = null; - - /** - * StartY of Cropzone - * @type {number} - * @private - */ - this._startY = null; - - /** - * State whether shortcut key is pressed or not - * @type {boolean} - * @private - */ - this._withShiftKey = false; - - /** - * Listeners - * @type {object.} - * @private - */ - this._listeners = { - keydown: this._onKeyDown.bind(this), - keyup: this._onKeyUp.bind(this), - mousedown: this._onFabricMouseDown.bind(this), - mousemove: this._onFabricMouseMove.bind(this), - mouseup: this._onFabricMouseUp.bind(this) - }; - } - - /** - * Start cropping - */ - start() { - if (this._cropzone) { - return; - } - const canvas = this.getCanvas(); - - canvas.forEachObject(obj => { // {@link http://fabricjs.com/docs/fabric.Object.html#evented} - obj.evented = false; - }); - - this._cropzone = new Cropzone(canvas, snippet.extend({ - left: 0, - top: 0, - width: 0.5, - height: 0.5, - strokeWidth: 0, // {@link https://github.com/kangax/fabric.js/issues/2860} - cornerSize: 10, - cornerColor: 'black', - fill: 'transparent' - }, CROPZONE_DEFAULT_OPTIONS, this.graphics.cropSelectionStyle)); - - canvas.discardActiveObject(); - canvas.add(this._cropzone); - canvas.on('mouse:down', this._listeners.mousedown); - canvas.selection = false; - canvas.defaultCursor = 'crosshair'; - - fabric.util.addListener(document, 'keydown', this._listeners.keydown); - fabric.util.addListener(document, 'keyup', this._listeners.keyup); - } + constructor(graphics) { + super(componentNames.CROPPER, graphics); /** - * End cropping + * Cropzone + * @type {Cropzone} + * @private */ - end() { - const canvas = this.getCanvas(); - const cropzone = this._cropzone; - - if (!cropzone) { - return; - } - canvas.remove(cropzone); - canvas.selection = true; - canvas.defaultCursor = 'default'; - canvas.off('mouse:down', this._listeners.mousedown); - canvas.forEachObject(obj => { - obj.evented = true; - }); - - this._cropzone = null; - - fabric.util.removeListener(document, 'keydown', this._listeners.keydown); - fabric.util.removeListener(document, 'keyup', this._listeners.keyup); - } + this._cropzone = null; /** - * Change cropzone visible - * @param {boolean} visible - cropzone visible state + * StartX of Cropzone + * @type {number} + * @private */ - changeVisibility(visible) { - if (this._cropzone) { - this._cropzone.set({visible}); - } - } + this._startX = null; /** - * onMousedown handler in fabric canvas - * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event + * StartY of Cropzone + * @type {number} * @private */ - _onFabricMouseDown(fEvent) { - const canvas = this.getCanvas(); - - if (fEvent.target) { - return; - } - - canvas.selection = false; - const coord = canvas.getPointer(fEvent.e); - - this._startX = coord.x; - this._startY = coord.y; - - canvas.on({ - 'mouse:move': this._listeners.mousemove, - 'mouse:up': this._listeners.mouseup - }); - } + this._startY = null; /** - * onMousemove handler in fabric canvas - * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event + * State whether shortcut key is pressed or not + * @type {boolean} * @private */ - _onFabricMouseMove(fEvent) { - const canvas = this.getCanvas(); - const pointer = canvas.getPointer(fEvent.e); - const {x, y} = pointer; - const cropzone = this._cropzone; - - if (Math.abs(x - this._startX) + Math.abs(y - this._startY) > MOUSE_MOVE_THRESHOLD) { - canvas.remove(cropzone); - cropzone.set(this._calcRectDimensionFromPoint(x, y)); - - canvas.add(cropzone); - canvas.setActiveObject(cropzone); - } - } + this._withShiftKey = false; /** - * Get rect dimension setting from Canvas-Mouse-Position(x, y) - * @param {number} x - Canvas-Mouse-Position x - * @param {number} y - Canvas-Mouse-Position Y - * @returns {{left: number, top: number, width: number, height: number}} + * Listeners + * @type {object.} * @private */ - _calcRectDimensionFromPoint(x, y) { - const canvas = this.getCanvas(); - const canvasWidth = canvas.getWidth(); - const canvasHeight = canvas.getHeight(); - const startX = this._startX; - const startY = this._startY; - let left = clamp(x, 0, startX); - let top = clamp(y, 0, startY); - let width = clamp(x, startX, canvasWidth) - left; // (startX <= x(mouse) <= canvasWidth) - left - let height = clamp(y, startY, canvasHeight) - top; // (startY <= y(mouse) <= canvasHeight) - top - - if (this._withShiftKey) { // make fixed ratio cropzone - if (width > height) { - height = width; - } else if (height > width) { - width = height; - } - - if (startX >= x) { - left = startX - width; - } - - if (startY >= y) { - top = startY - height; - } - } - - return { - left, - top, - width, - height - }; + this._listeners = { + keydown: this._onKeyDown.bind(this), + keyup: this._onKeyUp.bind(this), + mousedown: this._onFabricMouseDown.bind(this), + mousemove: this._onFabricMouseMove.bind(this), + mouseup: this._onFabricMouseUp.bind(this), + }; + } + + /** + * Start cropping + */ + start() { + if (this._cropzone) { + return; + } + const canvas = this.getCanvas(); + + canvas.forEachObject((obj) => { + // {@link http://fabricjs.com/docs/fabric.Object.html#evented} + obj.evented = false; + }); + + this._cropzone = new Cropzone( + canvas, + snippet.extend( + { + left: 0, + top: 0, + width: 0.5, + height: 0.5, + strokeWidth: 0, // {@link https://github.com/kangax/fabric.js/issues/2860} + cornerSize: 10, + cornerColor: 'black', + fill: 'transparent', + }, + CROPZONE_DEFAULT_OPTIONS, + this.graphics.cropSelectionStyle + ) + ); + + canvas.discardActiveObject(); + canvas.add(this._cropzone); + canvas.on('mouse:down', this._listeners.mousedown); + canvas.selection = false; + canvas.defaultCursor = 'crosshair'; + + fabric.util.addListener(document, 'keydown', this._listeners.keydown); + fabric.util.addListener(document, 'keyup', this._listeners.keyup); + } + + /** + * End cropping + */ + end() { + const canvas = this.getCanvas(); + const cropzone = this._cropzone; + + if (!cropzone) { + return; + } + canvas.remove(cropzone); + canvas.selection = true; + canvas.defaultCursor = 'default'; + canvas.off('mouse:down', this._listeners.mousedown); + canvas.forEachObject((obj) => { + obj.evented = true; + }); + + this._cropzone = null; + + fabric.util.removeListener(document, 'keydown', this._listeners.keydown); + fabric.util.removeListener(document, 'keyup', this._listeners.keyup); + } + + /** + * Change cropzone visible + * @param {boolean} visible - cropzone visible state + */ + changeVisibility(visible) { + if (this._cropzone) { + this._cropzone.set({ visible }); + } + } + + /** + * onMousedown handler in fabric canvas + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event + * @private + */ + _onFabricMouseDown(fEvent) { + const canvas = this.getCanvas(); + + if (fEvent.target) { + return; } - /** - * onMouseup handler in fabric canvas - * @private - */ - _onFabricMouseUp() { - const cropzone = this._cropzone; - const listeners = this._listeners; - const canvas = this.getCanvas(); - - canvas.setActiveObject(cropzone); - canvas.off({ - 'mouse:move': listeners.mousemove, - 'mouse:up': listeners.mouseup - }); + canvas.selection = false; + const coord = canvas.getPointer(fEvent.e); + + this._startX = coord.x; + this._startY = coord.y; + + canvas.on({ + 'mouse:move': this._listeners.mousemove, + 'mouse:up': this._listeners.mouseup, + }); + } + + /** + * onMousemove handler in fabric canvas + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event + * @private + */ + _onFabricMouseMove(fEvent) { + const canvas = this.getCanvas(); + const pointer = canvas.getPointer(fEvent.e); + const { x, y } = pointer; + const cropzone = this._cropzone; + + if (Math.abs(x - this._startX) + Math.abs(y - this._startY) > MOUSE_MOVE_THRESHOLD) { + canvas.remove(cropzone); + cropzone.set(this._calcRectDimensionFromPoint(x, y)); + + canvas.add(cropzone); + canvas.setActiveObject(cropzone); + } + } + + /** + * Get rect dimension setting from Canvas-Mouse-Position(x, y) + * @param {number} x - Canvas-Mouse-Position x + * @param {number} y - Canvas-Mouse-Position Y + * @returns {{left: number, top: number, width: number, height: number}} + * @private + */ + _calcRectDimensionFromPoint(x, y) { + const canvas = this.getCanvas(); + const canvasWidth = canvas.getWidth(); + const canvasHeight = canvas.getHeight(); + const startX = this._startX; + const startY = this._startY; + let left = clamp(x, 0, startX); + let top = clamp(y, 0, startY); + let width = clamp(x, startX, canvasWidth) - left; // (startX <= x(mouse) <= canvasWidth) - left + let height = clamp(y, startY, canvasHeight) - top; // (startY <= y(mouse) <= canvasHeight) - top + + if (this._withShiftKey) { + // make fixed ratio cropzone + if (width > height) { + height = width; + } else if (height > width) { + width = height; + } + + if (startX >= x) { + left = startX - width; + } + + if (startY >= y) { + top = startY - height; + } } - /** - * Get cropped image data - * @param {Object} cropRect cropzone rect - * @param {Number} cropRect.left left position - * @param {Number} cropRect.top top position - * @param {Number} cropRect.width width - * @param {Number} cropRect.height height - * @returns {?{imageName: string, url: string}} cropped Image data - */ - getCroppedImageData(cropRect) { - const canvas = this.getCanvas(); - const containsCropzone = canvas.contains(this._cropzone); - if (!cropRect) { - return null; - } - - if (containsCropzone) { - canvas.remove(this._cropzone); - } - - const imageData = { - imageName: this.getImageName(), - url: canvas.toDataURL(cropRect) - }; - - if (containsCropzone) { - canvas.add(this._cropzone); - } - - return imageData; + return { + left, + top, + width, + height, + }; + } + + /** + * onMouseup handler in fabric canvas + * @private + */ + _onFabricMouseUp() { + const cropzone = this._cropzone; + const listeners = this._listeners; + const canvas = this.getCanvas(); + + canvas.setActiveObject(cropzone); + canvas.off({ + 'mouse:move': listeners.mousemove, + 'mouse:up': listeners.mouseup, + }); + } + + /** + * Get cropped image data + * @param {Object} cropRect cropzone rect + * @param {Number} cropRect.left left position + * @param {Number} cropRect.top top position + * @param {Number} cropRect.width width + * @param {Number} cropRect.height height + * @returns {?{imageName: string, url: string}} cropped Image data + */ + getCroppedImageData(cropRect) { + const canvas = this.getCanvas(); + const containsCropzone = canvas.contains(this._cropzone); + if (!cropRect) { + return null; } - /** - * Get cropped rect - * @returns {Object} rect - */ - getCropzoneRect() { - const cropzone = this._cropzone; - - if (!cropzone.isValid()) { - return null; - } - - return { - left: cropzone.left, - top: cropzone.top, - width: cropzone.width, - height: cropzone.height - }; + if (containsCropzone) { + canvas.remove(this._cropzone); } - /** - * Set a cropzone square - * @param {number} [presetRatio] - preset ratio - */ - setCropzoneRect(presetRatio) { - const canvas = this.getCanvas(); - const cropzone = this._cropzone; + const imageData = { + imageName: this.getImageName(), + url: canvas.toDataURL(cropRect), + }; - canvas.discardActiveObject(); - canvas.selection = false; - canvas.remove(cropzone); + if (containsCropzone) { + canvas.add(this._cropzone); + } - cropzone.set(presetRatio ? this._getPresetPropertiesForCropSize(presetRatio) : DEFAULT_OPTION); + return imageData; + } - canvas.add(cropzone); - canvas.selection = true; + /** + * Get cropped rect + * @returns {Object} rect + */ + getCropzoneRect() { + const cropzone = this._cropzone; - if (presetRatio) { - canvas.setActiveObject(cropzone); - } + if (!cropzone.isValid()) { + return null; } - /** - * get a cropzone square info - * @param {number} presetRatio - preset ratio - * @returns {{presetRatio: number, left: number, top: number, width: number, height: number}} - * @private - */ - _getPresetPropertiesForCropSize(presetRatio) { - const canvas = this.getCanvas(); - const originalWidth = canvas.getWidth(); - const originalHeight = canvas.getHeight(); - - const standardSize = (originalWidth >= originalHeight) ? originalWidth : originalHeight; - const getScale = (value, orignalValue) => (value > orignalValue) ? orignalValue / value : 1; - - let width = standardSize * presetRatio; - let height = standardSize; - - const scaleWidth = getScale(width, originalWidth); - [width, height] = snippet.map([width, height], sizeValue => sizeValue * scaleWidth); - - const scaleHeight = getScale(height, originalHeight); - [width, height] = snippet.map([width, height], sizeValue => fixFloatingPoint(sizeValue * scaleHeight)); - - return { - presetRatio, - top: (originalHeight - height) / 2, - left: (originalWidth - width) / 2, - width, - height - }; + return { + left: cropzone.left, + top: cropzone.top, + width: cropzone.width, + height: cropzone.height, + }; + } + + /** + * Set a cropzone square + * @param {number} [presetRatio] - preset ratio + */ + setCropzoneRect(presetRatio) { + const canvas = this.getCanvas(); + const cropzone = this._cropzone; + + canvas.discardActiveObject(); + canvas.selection = false; + canvas.remove(cropzone); + + cropzone.set(presetRatio ? this._getPresetPropertiesForCropSize(presetRatio) : DEFAULT_OPTION); + + canvas.add(cropzone); + canvas.selection = true; + + if (presetRatio) { + canvas.setActiveObject(cropzone); } - - /** - * Keydown event handler - * @param {KeyboardEvent} e - Event object - * @private - */ - _onKeyDown(e) { - if (e.keyCode === keyCodes.SHIFT) { - this._withShiftKey = true; - } + } + + /** + * get a cropzone square info + * @param {number} presetRatio - preset ratio + * @returns {{presetRatio: number, left: number, top: number, width: number, height: number}} + * @private + */ + _getPresetPropertiesForCropSize(presetRatio) { + const canvas = this.getCanvas(); + const originalWidth = canvas.getWidth(); + const originalHeight = canvas.getHeight(); + + const standardSize = originalWidth >= originalHeight ? originalWidth : originalHeight; + const getScale = (value, orignalValue) => (value > orignalValue ? orignalValue / value : 1); + + let width = standardSize * presetRatio; + let height = standardSize; + + const scaleWidth = getScale(width, originalWidth); + [width, height] = snippet.map([width, height], (sizeValue) => sizeValue * scaleWidth); + + const scaleHeight = getScale(height, originalHeight); + [width, height] = snippet.map([width, height], (sizeValue) => + fixFloatingPoint(sizeValue * scaleHeight) + ); + + return { + presetRatio, + top: (originalHeight - height) / 2, + left: (originalWidth - width) / 2, + width, + height, + }; + } + + /** + * Keydown event handler + * @param {KeyboardEvent} e - Event object + * @private + */ + _onKeyDown(e) { + if (e.keyCode === keyCodes.SHIFT) { + this._withShiftKey = true; } - - /** - * Keyup event handler - * @param {KeyboardEvent} e - Event object - * @private - */ - _onKeyUp(e) { - if (e.keyCode === keyCodes.SHIFT) { - this._withShiftKey = false; - } + } + + /** + * Keyup event handler + * @param {KeyboardEvent} e - Event object + * @private + */ + _onKeyUp(e) { + if (e.keyCode === keyCodes.SHIFT) { + this._withShiftKey = false; } + } } export default Cropper; diff --git a/src/js/component/filter.js b/src/js/component/filter.js index 3fa462d82..2e6c9a17b 100644 --- a/src/js/component/filter.js +++ b/src/js/component/filter.js @@ -2,17 +2,17 @@ * @author NHN Ent. FE Development Team * @fileoverview Add filter module */ -import {isUndefined, extend, forEach, filter} from 'tui-code-snippet'; -import {Promise} from '../util'; +import { isUndefined, extend, forEach, filter } from 'tui-code-snippet'; +import { Promise } from '../util'; import fabric from 'fabric'; import Component from '../interface/component'; import Mask from '../extension/mask'; -import {rejectMessages, componentNames} from '../consts'; +import { rejectMessages, componentNames } from '../consts'; import Sharpen from '../extension/sharpen'; import Emboss from '../extension/emboss'; import ColorFilter from '../extension/colorFilter'; -const {filters} = fabric.Image; +const { filters } = fabric.Image; filters.Mask = Mask; filters.Sharpen = Sharpen; filters.Emboss = Emboss; @@ -26,206 +26,206 @@ filters.ColorFilter = ColorFilter; * @ignore */ class Filter extends Component { - constructor(graphics) { - super(componentNames.FILTER, graphics); - } - - /** - * Add filter to source image (a specific filter is added on fabric.js) - * @param {string} type - Filter type - * @param {Object} [options] - Options of filter - * @returns {Promise} - */ - add(type, options) { - return new Promise((resolve, reject) => { - const sourceImg = this._getSourceImage(); - const canvas = this.getCanvas(); - let imgFilter = this._getFilter(sourceImg, type); - if (!imgFilter) { - imgFilter = this._createFilter(sourceImg, type, options); - } - - if (!imgFilter) { - reject(rejectMessages.invalidParameters); - } - - this._changeFilterValues(imgFilter, options); - - this._apply(sourceImg, () => { - canvas.renderAll(); - resolve({ - type, - action: 'add', - options - }); - }); - }); - } - - /** - * Remove filter to source image - * @param {string} type - Filter type - * @returns {Promise} - */ - remove(type) { - return new Promise((resolve, reject) => { - const sourceImg = this._getSourceImage(); - const canvas = this.getCanvas(); - const options = this.getOptions(type); - - if (!sourceImg.filters.length) { - reject(rejectMessages.unsupportedOperation); - } - - this._removeFilter(sourceImg, type); - - this._apply(sourceImg, () => { - canvas.renderAll(); - resolve({ - type, - action: 'remove', - options - }); - }); - }); - } - - /** - * Whether this has the filter or not - * @param {string} type - Filter type - * @returns {boolean} true if it has the filter - */ - hasFilter(type) { - return !!this._getFilter(this._getSourceImage(), type); - } - - /** - * Get a filter options - * @param {string} type - Filter type - * @returns {Object} filter options or null if there is no that filter - */ - getOptions(type) { - const sourceImg = this._getSourceImage(); - const imgFilter = this._getFilter(sourceImg, type); - if (!imgFilter) { - return null; - } - - return extend({}, imgFilter.options); - } - - /** - * Change filter values - * @param {Object} imgFilter object of filter - * @param {Object} options object - * @private - */ - _changeFilterValues(imgFilter, options) { - forEach(options, (value, key) => { - if (!isUndefined(imgFilter[key])) { - imgFilter[key] = value; - } + constructor(graphics) { + super(componentNames.FILTER, graphics); + } + + /** + * Add filter to source image (a specific filter is added on fabric.js) + * @param {string} type - Filter type + * @param {Object} [options] - Options of filter + * @returns {Promise} + */ + add(type, options) { + return new Promise((resolve, reject) => { + const sourceImg = this._getSourceImage(); + const canvas = this.getCanvas(); + let imgFilter = this._getFilter(sourceImg, type); + if (!imgFilter) { + imgFilter = this._createFilter(sourceImg, type, options); + } + + if (!imgFilter) { + reject(rejectMessages.invalidParameters); + } + + this._changeFilterValues(imgFilter, options); + + this._apply(sourceImg, () => { + canvas.renderAll(); + resolve({ + type, + action: 'add', + options, }); - forEach(imgFilter.options, (value, key) => { - if (!isUndefined(options[key])) { - imgFilter.options[key] = options[key]; - } + }); + }); + } + + /** + * Remove filter to source image + * @param {string} type - Filter type + * @returns {Promise} + */ + remove(type) { + return new Promise((resolve, reject) => { + const sourceImg = this._getSourceImage(); + const canvas = this.getCanvas(); + const options = this.getOptions(type); + + if (!sourceImg.filters.length) { + reject(rejectMessages.unsupportedOperation); + } + + this._removeFilter(sourceImg, type); + + this._apply(sourceImg, () => { + canvas.renderAll(); + resolve({ + type, + action: 'remove', + options, }); + }); + }); + } + + /** + * Whether this has the filter or not + * @param {string} type - Filter type + * @returns {boolean} true if it has the filter + */ + hasFilter(type) { + return !!this._getFilter(this._getSourceImage(), type); + } + + /** + * Get a filter options + * @param {string} type - Filter type + * @returns {Object} filter options or null if there is no that filter + */ + getOptions(type) { + const sourceImg = this._getSourceImage(); + const imgFilter = this._getFilter(sourceImg, type); + if (!imgFilter) { + return null; } - /** - * Apply filter - * @param {fabric.Image} sourceImg - Source image to apply filter - * @param {function} callback - Executed function after applying filter - * @private - */ - _apply(sourceImg, callback) { - sourceImg.filters.push(); - const result = sourceImg.applyFilters(); - if (result) { - callback(); - } - } - - /** - * Get source image on canvas - * @returns {fabric.Image} Current source image on canvas - * @private - */ - _getSourceImage() { - return this.getCanvasImage(); + return extend({}, imgFilter.options); + } + + /** + * Change filter values + * @param {Object} imgFilter object of filter + * @param {Object} options object + * @private + */ + _changeFilterValues(imgFilter, options) { + forEach(options, (value, key) => { + if (!isUndefined(imgFilter[key])) { + imgFilter[key] = value; + } + }); + forEach(imgFilter.options, (value, key) => { + if (!isUndefined(options[key])) { + imgFilter.options[key] = options[key]; + } + }); + } + + /** + * Apply filter + * @param {fabric.Image} sourceImg - Source image to apply filter + * @param {function} callback - Executed function after applying filter + * @private + */ + _apply(sourceImg, callback) { + sourceImg.filters.push(); + const result = sourceImg.applyFilters(); + if (result) { + callback(); } - - /** - * Create filter instance - * @param {fabric.Image} sourceImg - Source image to apply filter - * @param {string} type - Filter type - * @param {Object} [options] - Options of filter - * @returns {Object} Fabric object of filter - * @private - */ - _createFilter(sourceImg, type, options) { - let filterObj; - // capitalize first letter for matching with fabric image filter name - const fabricType = this._getFabricFilterType(type); - const ImageFilter = fabric.Image.filters[fabricType]; - if (ImageFilter) { - filterObj = new ImageFilter(options); - filterObj.options = options; - sourceImg.filters.push(filterObj); - } - - return filterObj; + } + + /** + * Get source image on canvas + * @returns {fabric.Image} Current source image on canvas + * @private + */ + _getSourceImage() { + return this.getCanvasImage(); + } + + /** + * Create filter instance + * @param {fabric.Image} sourceImg - Source image to apply filter + * @param {string} type - Filter type + * @param {Object} [options] - Options of filter + * @returns {Object} Fabric object of filter + * @private + */ + _createFilter(sourceImg, type, options) { + let filterObj; + // capitalize first letter for matching with fabric image filter name + const fabricType = this._getFabricFilterType(type); + const ImageFilter = fabric.Image.filters[fabricType]; + if (ImageFilter) { + filterObj = new ImageFilter(options); + filterObj.options = options; + sourceImg.filters.push(filterObj); } - /** - * Get applied filter instance - * @param {fabric.Image} sourceImg - Source image to apply filter - * @param {string} type - Filter type - * @returns {Object} Fabric object of filter - * @private - */ - _getFilter(sourceImg, type) { - let imgFilter = null; - - if (sourceImg) { - const fabricType = this._getFabricFilterType(type); - const {length} = sourceImg.filters; - let item, i; - - for (i = 0; i < length; i += 1) { - item = sourceImg.filters[i]; - if (item.type === fabricType) { - imgFilter = item; - break; - } - } + return filterObj; + } + + /** + * Get applied filter instance + * @param {fabric.Image} sourceImg - Source image to apply filter + * @param {string} type - Filter type + * @returns {Object} Fabric object of filter + * @private + */ + _getFilter(sourceImg, type) { + let imgFilter = null; + + if (sourceImg) { + const fabricType = this._getFabricFilterType(type); + const { length } = sourceImg.filters; + let item, i; + + for (i = 0; i < length; i += 1) { + item = sourceImg.filters[i]; + if (item.type === fabricType) { + imgFilter = item; + break; } - - return imgFilter; + } } - /** - * Remove applied filter instance - * @param {fabric.Image} sourceImg - Source image to apply filter - * @param {string} type - Filter type - * @private - */ - _removeFilter(sourceImg, type) { - const fabricType = this._getFabricFilterType(type); - sourceImg.filters = filter(sourceImg.filters, value => value.type !== fabricType); - } - - /** - * Change filter class name to fabric's, especially capitalizing first letter - * @param {string} type - Filter type - * @example - * 'grayscale' -> 'Grayscale' - * @returns {string} Fabric filter class name - */ - _getFabricFilterType(type) { - return type.charAt(0).toUpperCase() + type.slice(1); - } + return imgFilter; + } + + /** + * Remove applied filter instance + * @param {fabric.Image} sourceImg - Source image to apply filter + * @param {string} type - Filter type + * @private + */ + _removeFilter(sourceImg, type) { + const fabricType = this._getFabricFilterType(type); + sourceImg.filters = filter(sourceImg.filters, (value) => value.type !== fabricType); + } + + /** + * Change filter class name to fabric's, especially capitalizing first letter + * @param {string} type - Filter type + * @example + * 'grayscale' -> 'Grayscale' + * @returns {string} Fabric filter class name + */ + _getFabricFilterType(type) { + return type.charAt(0).toUpperCase() + type.slice(1); + } } export default Filter; diff --git a/src/js/component/flip.js b/src/js/component/flip.js index 5abd91f87..9e3932d8e 100644 --- a/src/js/component/flip.js +++ b/src/js/component/flip.js @@ -3,9 +3,9 @@ * @fileoverview Image flip module */ import snippet from 'tui-code-snippet'; -import {Promise} from '../util'; +import { Promise } from '../util'; import Component from '../interface/component'; -import {componentNames, rejectMessages} from '../consts'; +import { componentNames, rejectMessages } from '../consts'; /** * Flip @@ -15,133 +15,137 @@ import {componentNames, rejectMessages} from '../consts'; * @ignore */ class Flip extends Component { - constructor(graphics) { - super(componentNames.FLIP, graphics); + constructor(graphics) { + super(componentNames.FLIP, graphics); + } + + /** + * Get current flip settings + * @returns {{flipX: Boolean, flipY: Boolean}} + */ + getCurrentSetting() { + const canvasImage = this.getCanvasImage(); + + return { + flipX: canvasImage.flipX, + flipY: canvasImage.flipY, + }; + } + + /** + * Set flipX, flipY + * @param {{flipX: Boolean, flipY: Boolean}} newSetting - Flip setting + * @returns {Promise} + */ + set(newSetting) { + const setting = this.getCurrentSetting(); + const isChangingFlipX = setting.flipX !== newSetting.flipX; + const isChangingFlipY = setting.flipY !== newSetting.flipY; + + if (!isChangingFlipX && !isChangingFlipY) { + return Promise.reject(rejectMessages.flip); } - /** - * Get current flip settings - * @returns {{flipX: Boolean, flipY: Boolean}} - */ - getCurrentSetting() { - const canvasImage = this.getCanvasImage(); - - return { - flipX: canvasImage.flipX, - flipY: canvasImage.flipY - }; + snippet.extend(setting, newSetting); + this.setImageProperties(setting, true); + this._invertAngle(isChangingFlipX, isChangingFlipY); + this._flipObjects(isChangingFlipX, isChangingFlipY); + + return Promise.resolve({ + flipX: setting.flipX, + flipY: setting.flipY, + angle: this.getCanvasImage().angle, + }); + } + + /** + * Invert image angle for flip + * @param {boolean} isChangingFlipX - Change flipX + * @param {boolean} isChangingFlipY - Change flipY + */ + _invertAngle(isChangingFlipX, isChangingFlipY) { + const canvasImage = this.getCanvasImage(); + let { angle } = canvasImage; + + if (isChangingFlipX) { + angle *= -1; } - - /** - * Set flipX, flipY - * @param {{flipX: Boolean, flipY: Boolean}} newSetting - Flip setting - * @returns {Promise} - */ - set(newSetting) { - const setting = this.getCurrentSetting(); - const isChangingFlipX = (setting.flipX !== newSetting.flipX); - const isChangingFlipY = (setting.flipY !== newSetting.flipY); - - if (!isChangingFlipX && !isChangingFlipY) { - return Promise.reject(rejectMessages.flip); - } - - snippet.extend(setting, newSetting); - this.setImageProperties(setting, true); - this._invertAngle(isChangingFlipX, isChangingFlipY); - this._flipObjects(isChangingFlipX, isChangingFlipY); - - return Promise.resolve({ - flipX: setting.flipX, - flipY: setting.flipY, - angle: this.getCanvasImage().angle - }); + if (isChangingFlipY) { + angle *= -1; } - - /** - * Invert image angle for flip - * @param {boolean} isChangingFlipX - Change flipX - * @param {boolean} isChangingFlipY - Change flipY - */ - _invertAngle(isChangingFlipX, isChangingFlipY) { - const canvasImage = this.getCanvasImage(); - let {angle} = canvasImage; - - if (isChangingFlipX) { - angle *= -1; - } - if (isChangingFlipY) { - angle *= -1; - } - canvasImage.rotate(parseFloat(angle)).setCoords();// parseFloat for -0 to 0 + canvasImage.rotate(parseFloat(angle)).setCoords(); // parseFloat for -0 to 0 + } + + /** + * Flip objects + * @param {boolean} isChangingFlipX - Change flipX + * @param {boolean} isChangingFlipY - Change flipY + * @private + */ + _flipObjects(isChangingFlipX, isChangingFlipY) { + const canvas = this.getCanvas(); + + if (isChangingFlipX) { + canvas.forEachObject((obj) => { + obj + .set({ + angle: parseFloat(obj.angle * -1), // parseFloat for -0 to 0 + flipX: !obj.flipX, + left: canvas.width - obj.left, + }) + .setCoords(); + }); } - - /** - * Flip objects - * @param {boolean} isChangingFlipX - Change flipX - * @param {boolean} isChangingFlipY - Change flipY - * @private - */ - _flipObjects(isChangingFlipX, isChangingFlipY) { - const canvas = this.getCanvas(); - - if (isChangingFlipX) { - canvas.forEachObject(obj => { - obj.set({ - angle: parseFloat(obj.angle * -1), // parseFloat for -0 to 0 - flipX: !obj.flipX, - left: canvas.width - obj.left - }).setCoords(); - }); - } - if (isChangingFlipY) { - canvas.forEachObject(obj => { - obj.set({ - angle: parseFloat(obj.angle * -1), // parseFloat for -0 to 0 - flipY: !obj.flipY, - top: canvas.height - obj.top - }).setCoords(); - }); - } - canvas.renderAll(); - } - - /** - * Reset flip settings - * @returns {Promise} - */ - reset() { - return this.set({ - flipX: false, - flipY: false - }); - } - - /** - * Flip x - * @returns {Promise} - */ - flipX() { - const current = this.getCurrentSetting(); - - return this.set({ - flipX: !current.flipX, - flipY: current.flipY - }); - } - - /** - * Flip y - * @returns {Promise} - */ - flipY() { - const current = this.getCurrentSetting(); - - return this.set({ - flipX: current.flipX, - flipY: !current.flipY - }); + if (isChangingFlipY) { + canvas.forEachObject((obj) => { + obj + .set({ + angle: parseFloat(obj.angle * -1), // parseFloat for -0 to 0 + flipY: !obj.flipY, + top: canvas.height - obj.top, + }) + .setCoords(); + }); } + canvas.renderAll(); + } + + /** + * Reset flip settings + * @returns {Promise} + */ + reset() { + return this.set({ + flipX: false, + flipY: false, + }); + } + + /** + * Flip x + * @returns {Promise} + */ + flipX() { + const current = this.getCurrentSetting(); + + return this.set({ + flipX: !current.flipX, + flipY: current.flipY, + }); + } + + /** + * Flip y + * @returns {Promise} + */ + flipY() { + const current = this.getCurrentSetting(); + + return this.set({ + flipX: current.flipX, + flipY: !current.flipY, + }); + } } export default Flip; diff --git a/src/js/component/freeDrawing.js b/src/js/component/freeDrawing.js index 5351ee5de..a393fa577 100644 --- a/src/js/component/freeDrawing.js +++ b/src/js/component/freeDrawing.js @@ -4,7 +4,7 @@ */ import fabric from 'fabric'; import Component from '../interface/component'; -import {componentNames} from '../consts'; +import { componentNames } from '../consts'; /** * FreeDrawing @@ -14,57 +14,57 @@ import {componentNames} from '../consts'; * @ignore */ class FreeDrawing extends Component { - constructor(graphics) { - super(componentNames.FREE_DRAWING, graphics); - - /** - * Brush width - * @type {number} - */ - this.width = 12; - - /** - * fabric.Color instance for brush color - * @type {fabric.Color} - */ - this.oColor = new fabric.Color('rgba(0, 0, 0, 0.5)'); - } + constructor(graphics) { + super(componentNames.FREE_DRAWING, graphics); /** - * Start free drawing mode - * @param {{width: ?number, color: ?string}} [setting] - Brush width & color + * Brush width + * @type {number} */ - start(setting) { - const canvas = this.getCanvas(); - - canvas.isDrawingMode = true; - this.setBrush(setting); - } + this.width = 12; /** - * Set brush - * @param {{width: ?number, color: ?string}} [setting] - Brush width & color + * fabric.Color instance for brush color + * @type {fabric.Color} */ - setBrush(setting) { - const brush = this.getCanvas().freeDrawingBrush; + this.oColor = new fabric.Color('rgba(0, 0, 0, 0.5)'); + } - setting = setting || {}; - this.width = setting.width || this.width; - if (setting.color) { - this.oColor = new fabric.Color(setting.color); - } - brush.width = this.width; - brush.color = this.oColor.toRgba(); - } + /** + * Start free drawing mode + * @param {{width: ?number, color: ?string}} [setting] - Brush width & color + */ + start(setting) { + const canvas = this.getCanvas(); - /** - * End free drawing mode - */ - end() { - const canvas = this.getCanvas(); + canvas.isDrawingMode = true; + this.setBrush(setting); + } + + /** + * Set brush + * @param {{width: ?number, color: ?string}} [setting] - Brush width & color + */ + setBrush(setting) { + const brush = this.getCanvas().freeDrawingBrush; - canvas.isDrawingMode = false; + setting = setting || {}; + this.width = setting.width || this.width; + if (setting.color) { + this.oColor = new fabric.Color(setting.color); } + brush.width = this.width; + brush.color = this.oColor.toRgba(); + } + + /** + * End free drawing mode + */ + end() { + const canvas = this.getCanvas(); + + canvas.isDrawingMode = false; + } } export default FreeDrawing; diff --git a/src/js/component/icon.js b/src/js/component/icon.js index 3f2b9e11b..2364c6f09 100644 --- a/src/js/component/icon.js +++ b/src/js/component/icon.js @@ -4,14 +4,15 @@ */ import fabric from 'fabric'; import snippet from 'tui-code-snippet'; -import {Promise} from '../util'; +import { Promise } from '../util'; import Component from '../interface/component'; -import {eventNames as events, rejectMessages, componentNames, fObjectOptions} from '../consts'; +import { eventNames as events, rejectMessages, componentNames, fObjectOptions } from '../consts'; const pathMap = { - arrow: 'M 0 90 H 105 V 120 L 160 60 L 105 0 V 30 H 0 Z', - cancel: 'M 0 30 L 30 60 L 0 90 L 30 120 L 60 90 L 90 120 L 120 90 ' + - 'L 90 60 L 120 30 L 90 0 L 60 30 L 30 0 Z' + arrow: 'M 0 90 H 105 V 120 L 160 60 L 105 0 V 30 H 0 Z', + cancel: + 'M 0 30 L 30 60 L 0 90 L 30 120 L 60 90 L 90 120 L 120 90 ' + + 'L 90 60 L 120 30 L 90 0 L 60 30 L 30 0 Z', }; /** @@ -22,217 +23,228 @@ const pathMap = { * @ignore */ class Icon extends Component { - constructor(graphics) { - super(componentNames.ICON, graphics); - - /** - * Default icon color - * @type {string} - */ - this._oColor = '#000000'; - - /** - * Path value of each icon type - * @type {Object} - */ - this._pathMap = pathMap; - - /** - * Type of the drawing icon - * @type {string} - * @private - */ - this._type = null; - - /** - * Color of the drawing icon - * @type {string} - * @private - */ - this._iconColor = null; - - /** - * Event handler list - * @type {Object} - * @private - */ - this._handlers = { - mousedown: this._onFabricMouseDown.bind(this), - mousemove: this._onFabricMouseMove.bind(this), - mouseup: this._onFabricMouseUp.bind(this) - }; - } - - /** - * Set states of the current drawing shape - * @ignore - * @param {string} type - Icon type ('arrow', 'cancel', custom icon name) - * @param {string} iconColor - Icon foreground color - */ - setStates(type, iconColor) { - this._type = type; - this._iconColor = iconColor; - } - - /** - * Start to draw the icon on canvas - * @ignore - */ - start() { - const canvas = this.getCanvas(); - canvas.selection = false; - canvas.on('mouse:down', this._handlers.mousedown); - } - - /** - * End to draw the icon on canvas - * @ignore - */ - end() { - const canvas = this.getCanvas(); - - canvas.selection = true; - canvas.off({ - 'mouse:down': this._handlers.mousedown - }); - } - - /** - * Add icon - * @param {string} type - Icon type - * @param {Object} options - Icon options - * @param {string} [options.fill] - Icon foreground color - * @param {string} [options.left] - Icon x position - * @param {string} [options.top] - Icon y position - * @returns {Promise} - */ - add(type, options) { - return new Promise((resolve, reject) => { - const canvas = this.getCanvas(); - const path = this._pathMap[type]; - const selectionStyle = fObjectOptions.SELECTION_STYLE; - const icon = path ? this._createIcon(path) : null; - this._icon = icon; - - if (!icon) { - reject(rejectMessages.invalidParameters); - } - - icon.set(snippet.extend({ - type: 'icon', - fill: this._oColor - }, selectionStyle, options, this.graphics.controlStyle)); - - canvas.add(icon).setActiveObject(icon); - - resolve(this.graphics.createObjectProperties(icon)); - }); - } + constructor(graphics) { + super(componentNames.ICON, graphics); /** - * Register icon paths - * @param {{key: string, value: string}} pathInfos - Path infos + * Default icon color + * @type {string} */ - registerPaths(pathInfos) { - snippet.forEach(pathInfos, (path, type) => { - this._pathMap[type] = path; - }, this); - } + this._oColor = '#000000'; /** - * Set icon object color - * @param {string} color - Color to set - * @param {fabric.Path}[obj] - Current activated path object + * Path value of each icon type + * @type {Object} */ - setColor(color, obj) { - this._oColor = color; - - if (obj && obj.get('type') === 'icon') { - obj.set({fill: this._oColor}); - this.getCanvas().renderAll(); - } - } + this._pathMap = pathMap; /** - * Get icon color - * @param {fabric.Path}[obj] - Current activated path object - * @returns {string} color - */ - getColor(obj) { - return obj.fill; - } - - /** - * Create icon object - * @param {string} path - Path value to create icon - * @returns {fabric.Path} Path object + * Type of the drawing icon + * @type {string} + * @private */ - _createIcon(path) { - return new fabric.Path(path); - } + this._type = null; /** - * MouseDown event handler on canvas - * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object + * Color of the drawing icon + * @type {string} * @private */ - _onFabricMouseDown(fEvent) { - const canvas = this.getCanvas(); - - this._startPoint = canvas.getPointer(fEvent.e); - const {x: left, y: top} = this._startPoint; - - this.add(this._type, { - left, - top, - fill: this._iconColor - }).then(() => { - this.fire(events.ADD_OBJECT, this.graphics.createObjectProperties(this._icon)); - canvas.on('mouse:move', this._handlers.mousemove); - canvas.on('mouse:up', this._handlers.mouseup); - }); - } + this._iconColor = null; /** - * MouseMove event handler on canvas - * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object + * Event handler list + * @type {Object} * @private */ - _onFabricMouseMove(fEvent) { - const canvas = this.getCanvas(); + this._handlers = { + mousedown: this._onFabricMouseDown.bind(this), + mousemove: this._onFabricMouseMove.bind(this), + mouseup: this._onFabricMouseUp.bind(this), + }; + } + + /** + * Set states of the current drawing shape + * @ignore + * @param {string} type - Icon type ('arrow', 'cancel', custom icon name) + * @param {string} iconColor - Icon foreground color + */ + setStates(type, iconColor) { + this._type = type; + this._iconColor = iconColor; + } + + /** + * Start to draw the icon on canvas + * @ignore + */ + start() { + const canvas = this.getCanvas(); + canvas.selection = false; + canvas.on('mouse:down', this._handlers.mousedown); + } + + /** + * End to draw the icon on canvas + * @ignore + */ + end() { + const canvas = this.getCanvas(); + + canvas.selection = true; + canvas.off({ + 'mouse:down': this._handlers.mousedown, + }); + } + + /** + * Add icon + * @param {string} type - Icon type + * @param {Object} options - Icon options + * @param {string} [options.fill] - Icon foreground color + * @param {string} [options.left] - Icon x position + * @param {string} [options.top] - Icon y position + * @returns {Promise} + */ + add(type, options) { + return new Promise((resolve, reject) => { + const canvas = this.getCanvas(); + const path = this._pathMap[type]; + const selectionStyle = fObjectOptions.SELECTION_STYLE; + const icon = path ? this._createIcon(path) : null; + this._icon = icon; + + if (!icon) { + reject(rejectMessages.invalidParameters); + } + + icon.set( + snippet.extend( + { + type: 'icon', + fill: this._oColor, + }, + selectionStyle, + options, + this.graphics.controlStyle + ) + ); + + canvas.add(icon).setActiveObject(icon); + + resolve(this.graphics.createObjectProperties(icon)); + }); + } + + /** + * Register icon paths + * @param {{key: string, value: string}} pathInfos - Path infos + */ + registerPaths(pathInfos) { + snippet.forEach( + pathInfos, + (path, type) => { + this._pathMap[type] = path; + }, + this + ); + } + + /** + * Set icon object color + * @param {string} color - Color to set + * @param {fabric.Path}[obj] - Current activated path object + */ + setColor(color, obj) { + this._oColor = color; + + if (obj && obj.get('type') === 'icon') { + obj.set({ fill: this._oColor }); + this.getCanvas().renderAll(); + } + } + + /** + * Get icon color + * @param {fabric.Path}[obj] - Current activated path object + * @returns {string} color + */ + getColor(obj) { + return obj.fill; + } + + /** + * Create icon object + * @param {string} path - Path value to create icon + * @returns {fabric.Path} Path object + */ + _createIcon(path) { + return new fabric.Path(path); + } + + /** + * MouseDown event handler on canvas + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object + * @private + */ + _onFabricMouseDown(fEvent) { + const canvas = this.getCanvas(); + + this._startPoint = canvas.getPointer(fEvent.e); + const { x: left, y: top } = this._startPoint; + + this.add(this._type, { + left, + top, + fill: this._iconColor, + }).then(() => { + this.fire(events.ADD_OBJECT, this.graphics.createObjectProperties(this._icon)); + canvas.on('mouse:move', this._handlers.mousemove); + canvas.on('mouse:up', this._handlers.mouseup); + }); + } + + /** + * MouseMove event handler on canvas + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object + * @private + */ + _onFabricMouseMove(fEvent) { + const canvas = this.getCanvas(); + + if (!this._icon) { + return; + } + const moveOriginPointer = canvas.getPointer(fEvent.e); - if (!this._icon) { - return; - } - const moveOriginPointer = canvas.getPointer(fEvent.e); + const scaleX = (moveOriginPointer.x - this._startPoint.x) / this._icon.width; + const scaleY = (moveOriginPointer.y - this._startPoint.y) / this._icon.height; - const scaleX = (moveOriginPointer.x - this._startPoint.x) / this._icon.width; - const scaleY = (moveOriginPointer.y - this._startPoint.y) / this._icon.height; + this._icon.set({ + scaleX: Math.abs(scaleX * 2), + scaleY: Math.abs(scaleY * 2), + }); - this._icon.set({ - scaleX: Math.abs(scaleX * 2), - scaleY: Math.abs(scaleY * 2) - }); + this._icon.setCoords(); + canvas.renderAll(); + } - this._icon.setCoords(); - canvas.renderAll(); - } + /** + * MouseUp event handler on canvas + * @private + */ + _onFabricMouseUp() { + const canvas = this.getCanvas(); - /** - * MouseUp event handler on canvas - * @private - */ - _onFabricMouseUp() { - const canvas = this.getCanvas(); + this.fire(events.OBJECT_ADDED, this.graphics.createObjectProperties(this._icon)); - this.fire(events.OBJECT_ADDED, this.graphics.createObjectProperties(this._icon)); + this._icon = null; - this._icon = null; - - canvas.off('mouse:down', this._handlers.mousedown); - canvas.off('mouse:move', this._handlers.mousemove); - canvas.off('mouse:up', this._handlers.mouseup); - } + canvas.off('mouse:down', this._handlers.mousedown); + canvas.off('mouse:move', this._handlers.mousemove); + canvas.off('mouse:up', this._handlers.mouseup); + } } export default Icon; diff --git a/src/js/component/imageLoader.js b/src/js/component/imageLoader.js index 5a365541f..c52fddd81 100644 --- a/src/js/component/imageLoader.js +++ b/src/js/component/imageLoader.js @@ -3,12 +3,12 @@ * @fileoverview Image loader */ import Component from '../interface/component'; -import {componentNames, rejectMessages} from '../consts'; -import {Promise} from '../util'; +import { componentNames, rejectMessages } from '../consts'; +import { Promise } from '../util'; const imageOption = { - padding: 0, - crossOrigin: 'Anonymous' + padding: 0, + crossOrigin: 'Anonymous', }; /** @@ -19,66 +19,71 @@ const imageOption = { * @ignore */ class ImageLoader extends Component { - constructor(graphics) { - super(componentNames.IMAGE_LOADER, graphics); - } - - /** - * Load image from url - * @param {?string} imageName - File name - * @param {?(fabric.Image|string)} img - fabric.Image instance or URL of an image - * @returns {Promise} - */ - load(imageName, img) { - let promise; + constructor(graphics) { + super(componentNames.IMAGE_LOADER, graphics); + } - if (!imageName && !img) { // Back to the initial state, not error. - const canvas = this.getCanvas(); + /** + * Load image from url + * @param {?string} imageName - File name + * @param {?(fabric.Image|string)} img - fabric.Image instance or URL of an image + * @returns {Promise} + */ + load(imageName, img) { + let promise; - canvas.backgroundImage = null; - canvas.renderAll(); + if (!imageName && !img) { + // Back to the initial state, not error. + const canvas = this.getCanvas(); - promise = new Promise(resolve => { - this.setCanvasImage('', null); - resolve(); - }); - } else { - promise = this._setBackgroundImage(img).then(oImage => { - this.setCanvasImage(imageName, oImage); - this.adjustCanvasDimension(); + canvas.backgroundImage = null; + canvas.renderAll(); - return oImage; - }); - } + promise = new Promise((resolve) => { + this.setCanvasImage('', null); + resolve(); + }); + } else { + promise = this._setBackgroundImage(img).then((oImage) => { + this.setCanvasImage(imageName, oImage); + this.adjustCanvasDimension(); - return promise; + return oImage; + }); } - /** - * Set background image - * @param {?(fabric.Image|String)} img fabric.Image instance or URL of an image to set background to - * @returns {Promise} - * @private - */ - _setBackgroundImage(img) { - if (!img) { - return Promise.reject(rejectMessages.loadImage); - } + return promise; + } - return new Promise((resolve, reject) => { - const canvas = this.getCanvas(); + /** + * Set background image + * @param {?(fabric.Image|String)} img fabric.Image instance or URL of an image to set background to + * @returns {Promise} + * @private + */ + _setBackgroundImage(img) { + if (!img) { + return Promise.reject(rejectMessages.loadImage); + } - canvas.setBackgroundImage(img, () => { - const oImage = canvas.backgroundImage; + return new Promise((resolve, reject) => { + const canvas = this.getCanvas(); - if (oImage && oImage.getElement()) { - resolve(oImage); - } else { - reject(rejectMessages.loadingImageFailed); - } - }, imageOption); - }); - } + canvas.setBackgroundImage( + img, + () => { + const oImage = canvas.backgroundImage; + + if (oImage && oImage.getElement()) { + resolve(oImage); + } else { + reject(rejectMessages.loadingImageFailed); + } + }, + imageOption + ); + }); + } } export default ImageLoader; diff --git a/src/js/component/line.js b/src/js/component/line.js index 51a8aa741..078a5784a 100644 --- a/src/js/component/line.js +++ b/src/js/component/line.js @@ -6,7 +6,7 @@ import fabric from 'fabric'; import snippet from 'tui-code-snippet'; import Component from '../interface/component'; import ArrowLine from '../extension/arrowLine'; -import {eventNames, componentNames, fObjectOptions} from '../consts'; +import { eventNames, componentNames, fObjectOptions } from '../consts'; /** * Line @@ -16,195 +16,195 @@ import {eventNames, componentNames, fObjectOptions} from '../consts'; * @ignore */ class Line extends Component { - constructor(graphics) { - super(componentNames.LINE, graphics); - - /** - * Brush width - * @type {number} - * @private - */ - this._width = 12; - - /** - * fabric.Color instance for brush color - * @type {fabric.Color} - * @private - */ - this._oColor = new fabric.Color('rgba(0, 0, 0, 0.5)'); - - /** - * Listeners - * @type {object.} - * @private - */ - this._listeners = { - mousedown: this._onFabricMouseDown.bind(this), - mousemove: this._onFabricMouseMove.bind(this), - mouseup: this._onFabricMouseUp.bind(this) - }; - } - - /** - * Start drawing line mode - * @param {{width: ?number, color: ?string}} [setting] - Brush width & color - */ - setHeadOption(setting) { - const { - arrowType = { - head: null, - tail: null - } - } = setting; - - this._arrowType = arrowType; - } - - /** - * Start drawing line mode - * @param {{width: ?number, color: ?string}} [setting] - Brush width & color - */ - start(setting = {}) { - const canvas = this.getCanvas(); - - canvas.defaultCursor = 'crosshair'; - canvas.selection = false; - - this.setHeadOption(setting); - this.setBrush(setting); - - canvas.forEachObject(obj => { - obj.set({ - evented: false - }); - }); - - canvas.on({ - 'mouse:down': this._listeners.mousedown - }); - } - - /** - * Set brush - * @param {{width: ?number, color: ?string}} [setting] - Brush width & color - */ - setBrush(setting) { - const brush = this.getCanvas().freeDrawingBrush; - - setting = setting || {}; - this._width = setting.width || this._width; - - if (setting.color) { - this._oColor = new fabric.Color(setting.color); - } - brush.width = this._width; - brush.color = this._oColor.toRgba(); - } - - /** - * End drawing line mode - */ - end() { - const canvas = this.getCanvas(); - - canvas.defaultCursor = 'default'; - canvas.selection = true; - - canvas.forEachObject(obj => { - obj.set({ - evented: true - }); - }); - - canvas.off('mouse:down', this._listeners.mousedown); - } - - /** - * Mousedown event handler in fabric canvas - * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object - * @private - */ - _onFabricMouseDown(fEvent) { - const canvas = this.getCanvas(); - const {x, y} = canvas.getPointer(fEvent.e); - const points = [x, y, x, y]; - - this._line = new ArrowLine(points, { - stroke: this._oColor.toRgba(), - strokeWidth: this._width, - arrowType: this._arrowType, - evented: false - }); - - this._line.set(fObjectOptions.SELECTION_STYLE); - - canvas.add(this._line); - - canvas.on({ - 'mouse:move': this._listeners.mousemove, - 'mouse:up': this._listeners.mouseup - }); - - this.fire(eventNames.ADD_OBJECT, this._createLineEventObjectProperties()); - } + constructor(graphics) { + super(componentNames.LINE, graphics); /** - * Mousemove event handler in fabric canvas - * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object + * Brush width + * @type {number} * @private */ - _onFabricMouseMove(fEvent) { - const canvas = this.getCanvas(); - const pointer = canvas.getPointer(fEvent.e); - - this._line.set({ - x2: pointer.x, - y2: pointer.y - }); - - this._line.setCoords(); - - canvas.renderAll(); - } + this._width = 12; /** - * Mouseup event handler in fabric canvas - * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object + * fabric.Color instance for brush color + * @type {fabric.Color} * @private */ - _onFabricMouseUp() { - const canvas = this.getCanvas(); - - this.fire(eventNames.OBJECT_ADDED, this._createLineEventObjectProperties()); - - this._line = null; - - canvas.off({ - 'mouse:move': this._listeners.mousemove, - 'mouse:up': this._listeners.mouseup - }); - } + this._oColor = new fabric.Color('rgba(0, 0, 0, 0.5)'); /** - * create line event object properties - * @returns {Object} properties line object + * Listeners + * @type {object.} * @private */ - _createLineEventObjectProperties() { - const params = this.graphics.createObjectProperties(this._line); - const {x1, x2, y1, y2} = this._line; - - return snippet.extend({}, params, { - startPosition: { - x: x1, - y: y1 - }, - endPosition: { - x: x2, - y: y2 - } - }); + this._listeners = { + mousedown: this._onFabricMouseDown.bind(this), + mousemove: this._onFabricMouseMove.bind(this), + mouseup: this._onFabricMouseUp.bind(this), + }; + } + + /** + * Start drawing line mode + * @param {{width: ?number, color: ?string}} [setting] - Brush width & color + */ + setHeadOption(setting) { + const { + arrowType = { + head: null, + tail: null, + }, + } = setting; + + this._arrowType = arrowType; + } + + /** + * Start drawing line mode + * @param {{width: ?number, color: ?string}} [setting] - Brush width & color + */ + start(setting = {}) { + const canvas = this.getCanvas(); + + canvas.defaultCursor = 'crosshair'; + canvas.selection = false; + + this.setHeadOption(setting); + this.setBrush(setting); + + canvas.forEachObject((obj) => { + obj.set({ + evented: false, + }); + }); + + canvas.on({ + 'mouse:down': this._listeners.mousedown, + }); + } + + /** + * Set brush + * @param {{width: ?number, color: ?string}} [setting] - Brush width & color + */ + setBrush(setting) { + const brush = this.getCanvas().freeDrawingBrush; + + setting = setting || {}; + this._width = setting.width || this._width; + + if (setting.color) { + this._oColor = new fabric.Color(setting.color); } + brush.width = this._width; + brush.color = this._oColor.toRgba(); + } + + /** + * End drawing line mode + */ + end() { + const canvas = this.getCanvas(); + + canvas.defaultCursor = 'default'; + canvas.selection = true; + + canvas.forEachObject((obj) => { + obj.set({ + evented: true, + }); + }); + + canvas.off('mouse:down', this._listeners.mousedown); + } + + /** + * Mousedown event handler in fabric canvas + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object + * @private + */ + _onFabricMouseDown(fEvent) { + const canvas = this.getCanvas(); + const { x, y } = canvas.getPointer(fEvent.e); + const points = [x, y, x, y]; + + this._line = new ArrowLine(points, { + stroke: this._oColor.toRgba(), + strokeWidth: this._width, + arrowType: this._arrowType, + evented: false, + }); + + this._line.set(fObjectOptions.SELECTION_STYLE); + + canvas.add(this._line); + + canvas.on({ + 'mouse:move': this._listeners.mousemove, + 'mouse:up': this._listeners.mouseup, + }); + + this.fire(eventNames.ADD_OBJECT, this._createLineEventObjectProperties()); + } + + /** + * Mousemove event handler in fabric canvas + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object + * @private + */ + _onFabricMouseMove(fEvent) { + const canvas = this.getCanvas(); + const pointer = canvas.getPointer(fEvent.e); + + this._line.set({ + x2: pointer.x, + y2: pointer.y, + }); + + this._line.setCoords(); + + canvas.renderAll(); + } + + /** + * Mouseup event handler in fabric canvas + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object + * @private + */ + _onFabricMouseUp() { + const canvas = this.getCanvas(); + + this.fire(eventNames.OBJECT_ADDED, this._createLineEventObjectProperties()); + + this._line = null; + + canvas.off({ + 'mouse:move': this._listeners.mousemove, + 'mouse:up': this._listeners.mouseup, + }); + } + + /** + * create line event object properties + * @returns {Object} properties line object + * @private + */ + _createLineEventObjectProperties() { + const params = this.graphics.createObjectProperties(this._line); + const { x1, x2, y1, y2 } = this._line; + + return snippet.extend({}, params, { + startPosition: { + x: x1, + y: y1, + }, + endPosition: { + x: x2, + y: y2, + }, + }); + } } export default Line; diff --git a/src/js/component/rotation.js b/src/js/component/rotation.js index 166eb4db3..38f0899ff 100644 --- a/src/js/component/rotation.js +++ b/src/js/component/rotation.js @@ -3,9 +3,9 @@ * @fileoverview Image rotation module */ import fabric from 'fabric'; -import {Promise} from '../util'; +import { Promise } from '../util'; import Component from '../interface/component'; -import {componentNames} from '../consts'; +import { componentNames } from '../consts'; /** * Image Rotation component @@ -15,82 +15,82 @@ import {componentNames} from '../consts'; * @ignore */ class Rotation extends Component { - constructor(graphics) { - super(componentNames.ROTATION, graphics); - } + constructor(graphics) { + super(componentNames.ROTATION, graphics); + } - /** - * Get current angle - * @returns {Number} - */ - getCurrentAngle() { - return this.getCanvasImage().angle; - } + /** + * Get current angle + * @returns {Number} + */ + getCurrentAngle() { + return this.getCanvasImage().angle; + } - /** - * Set angle of the image - * - * Do not call "this.setImageProperties" for setting angle directly. - * Before setting angle, The originX,Y of image should be set to center. - * See "http://fabricjs.com/docs/fabric.Object.html#setAngle" - * - * @param {number} angle - Angle value - * @returns {Promise} - */ - setAngle(angle) { - const oldAngle = this.getCurrentAngle() % 360; // The angle is lower than 2*PI(===360 degrees) + /** + * Set angle of the image + * + * Do not call "this.setImageProperties" for setting angle directly. + * Before setting angle, The originX,Y of image should be set to center. + * See "http://fabricjs.com/docs/fabric.Object.html#setAngle" + * + * @param {number} angle - Angle value + * @returns {Promise} + */ + setAngle(angle) { + const oldAngle = this.getCurrentAngle() % 360; // The angle is lower than 2*PI(===360 degrees) - angle %= 360; + angle %= 360; - const canvasImage = this.getCanvasImage(); - const oldImageCenter = canvasImage.getCenterPoint(); - canvasImage.set({angle}).setCoords(); - this.adjustCanvasDimension(); - const newImageCenter = canvasImage.getCenterPoint(); - this._rotateForEachObject(oldImageCenter, newImageCenter, angle - oldAngle); + const canvasImage = this.getCanvasImage(); + const oldImageCenter = canvasImage.getCenterPoint(); + canvasImage.set({ angle }).setCoords(); + this.adjustCanvasDimension(); + const newImageCenter = canvasImage.getCenterPoint(); + this._rotateForEachObject(oldImageCenter, newImageCenter, angle - oldAngle); - return Promise.resolve(angle); - } + return Promise.resolve(angle); + } - /** - * Rotate for each object - * @param {fabric.Point} oldImageCenter - Image center point before rotation - * @param {fabric.Point} newImageCenter - Image center point after rotation - * @param {number} angleDiff - Image angle difference after rotation - * @private - */ - _rotateForEachObject(oldImageCenter, newImageCenter, angleDiff) { - const canvas = this.getCanvas(); - const centerDiff = { - x: oldImageCenter.x - newImageCenter.x, - y: oldImageCenter.y - newImageCenter.y - }; + /** + * Rotate for each object + * @param {fabric.Point} oldImageCenter - Image center point before rotation + * @param {fabric.Point} newImageCenter - Image center point after rotation + * @param {number} angleDiff - Image angle difference after rotation + * @private + */ + _rotateForEachObject(oldImageCenter, newImageCenter, angleDiff) { + const canvas = this.getCanvas(); + const centerDiff = { + x: oldImageCenter.x - newImageCenter.x, + y: oldImageCenter.y - newImageCenter.y, + }; - canvas.forEachObject(obj => { - const objCenter = obj.getCenterPoint(); - const radian = fabric.util.degreesToRadians(angleDiff); - const newObjCenter = fabric.util.rotatePoint(objCenter, oldImageCenter, radian); + canvas.forEachObject((obj) => { + const objCenter = obj.getCenterPoint(); + const radian = fabric.util.degreesToRadians(angleDiff); + const newObjCenter = fabric.util.rotatePoint(objCenter, oldImageCenter, radian); - obj.set({ - left: newObjCenter.x - centerDiff.x, - top: newObjCenter.y - centerDiff.y, - angle: (obj.angle + angleDiff) % 360 - }); - obj.setCoords(); - }); - canvas.renderAll(); - } + obj.set({ + left: newObjCenter.x - centerDiff.x, + top: newObjCenter.y - centerDiff.y, + angle: (obj.angle + angleDiff) % 360, + }); + obj.setCoords(); + }); + canvas.renderAll(); + } - /** - * Rotate the image - * @param {number} additionalAngle - Additional angle - * @returns {Promise} - */ - rotate(additionalAngle) { - const current = this.getCurrentAngle(); + /** + * Rotate the image + * @param {number} additionalAngle - Additional angle + * @returns {Promise} + */ + rotate(additionalAngle) { + const current = this.getCurrentAngle(); - return this.setAngle(current + additionalAngle); - } + return this.setAngle(current + additionalAngle); + } } export default Rotation; diff --git a/src/js/component/shape.js b/src/js/component/shape.js index e8ee9303c..ee1291c75 100644 --- a/src/js/component/shape.js +++ b/src/js/component/shape.js @@ -5,35 +5,45 @@ import fabric from 'fabric'; import Component from '../interface/component'; import { - rejectMessages, - eventNames, - keyCodes as KEY_CODES, - componentNames, - fObjectOptions, - SHAPE_DEFAULT_OPTIONS, - SHAPE_FILL_TYPE + rejectMessages, + eventNames, + keyCodes as KEY_CODES, + componentNames, + fObjectOptions, + SHAPE_DEFAULT_OPTIONS, + SHAPE_FILL_TYPE, } from '../consts'; import resizeHelper from '../helper/shapeResizeHelper'; import { - getFillImageFromShape, - rePositionFilterTypeFillImage, - reMakePatternImageSource, - makeFillPatternForFilter, - makeFilterOptionFromFabricImage, - resetFillPatternCanvas + getFillImageFromShape, + rePositionFilterTypeFillImage, + reMakePatternImageSource, + makeFillPatternForFilter, + makeFilterOptionFromFabricImage, + resetFillPatternCanvas, } from '../helper/shapeFilterFillHelper'; -import {Promise, changeOrigin, getCustomProperty, getFillTypeFromOption, getFillTypeFromObject, isShape} from '../util'; -import {extend} from 'tui-code-snippet'; - -const SHAPE_INIT_OPTIONS = extend({ +import { + Promise, + changeOrigin, + getCustomProperty, + getFillTypeFromOption, + getFillTypeFromObject, + isShape, +} from '../util'; +import { extend } from 'tui-code-snippet'; + +const SHAPE_INIT_OPTIONS = extend( + { strokeWidth: 1, stroke: '#000000', fill: '#ffffff', width: 1, height: 1, rx: 0, - ry: 0 -}, SHAPE_DEFAULT_OPTIONS); + ry: 0, + }, + SHAPE_DEFAULT_OPTIONS +); const DEFAULT_TYPE = 'rect'; const DEFAULT_WIDTH = 20; @@ -48,23 +58,23 @@ const DEFAULT_HEIGHT = 20; * @private */ function makeFabricFillOption(options, canvasImage, createStaticCanvas) { - const fillOption = options.fill; - const fillType = getFillTypeFromOption(options.fill); - let fill = fillOption; - - if (fillOption.color) { - fill = fillOption.color; - } - - let extOption = null; - if (fillType === 'filter') { - const newStaticCanvas = createStaticCanvas(); - extOption = makeFillPatternForFilter(canvasImage, fillOption.filter, newStaticCanvas); - } else { - extOption = {fill}; - } - - return extend({}, options, extOption); + const fillOption = options.fill; + const fillType = getFillTypeFromOption(options.fill); + let fill = fillOption; + + if (fillOption.color) { + fill = fillOption.color; + } + + let extOption = null; + if (fillType === 'filter') { + const newStaticCanvas = createStaticCanvas(); + extOption = makeFillPatternForFilter(canvasImage, fillOption.filter, newStaticCanvas); + } else { + extOption = { fill }; + } + + return extend({}, options, extOption); } /** @@ -75,514 +85,523 @@ function makeFabricFillOption(options, canvasImage, createStaticCanvas) { * @ignore */ export default class Shape extends Component { - constructor(graphics) { - super(componentNames.SHAPE, graphics); - - /** - * Object of The drawing shape - * @type {fabric.Object} - * @private - */ - this._shapeObj = null; - - /** - * Type of the drawing shape - * @type {string} - * @private - */ - this._type = DEFAULT_TYPE; - - /** - * Options to draw the shape - * @type {Object} - * @private - */ - this._options = extend({}, SHAPE_INIT_OPTIONS); - - /** - * Whether the shape object is selected or not - * @type {boolean} - * @private - */ - this._isSelected = false; - - /** - * Pointer for drawing shape (x, y) - * @type {Object} - * @private - */ - this._startPoint = {}; - - /** - * Using shortcut on drawing shape - * @type {boolean} - * @private - */ - this._withShiftKey = false; - - /** - * Event handler list - * @type {Object} - * @private - */ - this._handlers = { - mousedown: this._onFabricMouseDown.bind(this), - mousemove: this._onFabricMouseMove.bind(this), - mouseup: this._onFabricMouseUp.bind(this), - keydown: this._onKeyDown.bind(this), - keyup: this._onKeyUp.bind(this) - }; - } - - /** - * Start to draw the shape on canvas - * @ignore - */ - start() { - const canvas = this.getCanvas(); - - this._isSelected = false; - - canvas.defaultCursor = 'crosshair'; - canvas.selection = false; - canvas.uniformScaling = true; - canvas.on({ - 'mouse:down': this._handlers.mousedown - }); - - fabric.util.addListener(document, 'keydown', this._handlers.keydown); - fabric.util.addListener(document, 'keyup', this._handlers.keyup); - } - - /** - * End to draw the shape on canvas - * @ignore - */ - end() { - const canvas = this.getCanvas(); - - this._isSelected = false; - - canvas.defaultCursor = 'default'; - - canvas.selection = true; - canvas.uniformScaling = false; - canvas.off({ - 'mouse:down': this._handlers.mousedown - }); - - fabric.util.removeListener(document, 'keydown', this._handlers.keydown); - fabric.util.removeListener(document, 'keyup', this._handlers.keyup); - } - - /** - * Set states of the current drawing shape - * @ignore - * @param {string} type - Shape type (ex: 'rect', 'circle') - * @param {Object} [options] - Shape options - * @param {(ShapeFillOption | string)} [options.fill] - {@link ShapeFillOption} or - * Shape foreground color (ex: '#fff', 'transparent') - * @param {string} [options.stoke] - Shape outline color - * @param {number} [options.strokeWidth] - Shape outline width - * @param {number} [options.width] - Width value (When type option is 'rect', this options can use) - * @param {number} [options.height] - Height value (When type option is 'rect', this options can use) - * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use) - * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use) - */ - setStates(type, options) { - this._type = type; - - if (options) { - this._options = extend(this._options, options); - } - } - - /** - * Add the shape - * @ignore - * @param {string} type - Shape type (ex: 'rect', 'circle') - * @param {Object} options - Shape options - * @param {(ShapeFillOption | string)} [options.fill] - ShapeFillOption or Shape foreground color (ex: '#fff', 'transparent') or ShapeFillOption object - * @param {string} [options.stroke] - Shape outline color - * @param {number} [options.strokeWidth] - Shape outline width - * @param {number} [options.width] - Width value (When type option is 'rect', this options can use) - * @param {number} [options.height] - Height value (When type option is 'rect', this options can use) - * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use) - * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use) - * @param {number} [options.isRegular] - Whether scaling shape has 1:1 ratio or not - * @returns {Promise} - */ - add(type, options) { - return new Promise(resolve => { - const canvas = this.getCanvas(); - const extendOption = this._extendOptions(options); - - const shapeObj = this._createInstance(type, extendOption); - const objectProperties = this.graphics.createObjectProperties(shapeObj); - - this._bindEventOnShape(shapeObj); - - canvas.add(shapeObj).setActiveObject(shapeObj); - - this._resetPositionFillFilter(shapeObj); - - resolve(objectProperties); - }); - } - - /** - * Change the shape - * @ignore - * @param {fabric.Object} shapeObj - Selected shape object on canvas - * @param {Object} options - Shape options - * @param {(ShapeFillOption | string)} [options.fill] - {@link ShapeFillOption} or - * Shape foreground color (ex: '#fff', 'transparent') - * @param {string} [options.stroke] - Shape outline color - * @param {number} [options.strokeWidth] - Shape outline width - * @param {number} [options.width] - Width value (When type option is 'rect', this options can use) - * @param {number} [options.height] - Height value (When type option is 'rect', this options can use) - * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use) - * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use) - * @param {number} [options.isRegular] - Whether scaling shape has 1:1 ratio or not - * @returns {Promise} - */ - change(shapeObj, options) { - return new Promise((resolve, reject) => { - if (!isShape(shapeObj)) { - reject(rejectMessages.unsupportedType); - } - const hasFillOption = getFillTypeFromOption(options.fill) === 'filter'; - const {canvasImage, createStaticCanvas} = this.graphics; - - shapeObj.set(hasFillOption ? makeFabricFillOption(options, canvasImage, createStaticCanvas) : options); - - if (hasFillOption) { - this._resetPositionFillFilter(shapeObj); - } - - this.getCanvas().renderAll(); - resolve(); - }); - } - - /** - * make fill property for user event - * @param {fabric.Object} shapeObj - fabric object - * @returns {Object} - */ - makeFillPropertyForUserEvent(shapeObj) { - const fillType = getFillTypeFromObject(shapeObj); - const fillProp = {}; - - if (fillType === SHAPE_FILL_TYPE.FILTER) { - const fillImage = getFillImageFromShape(shapeObj); - const filterOption = makeFilterOptionFromFabricImage(fillImage); - - fillProp.type = fillType; - fillProp.filter = filterOption; - } else { - fillProp.type = SHAPE_FILL_TYPE.COLOR; - fillProp.color = shapeObj.fill || 'transparent'; - } - - return fillProp; - } - - /** - * Copy object handling. - * @param {fabric.Object} shapeObj - Shape object - * @param {fabric.Object} originalShapeObj - Shape object - */ - processForCopiedObject(shapeObj, originalShapeObj) { - this._bindEventOnShape(shapeObj); - - if (getFillTypeFromObject(shapeObj) === 'filter') { - const fillImage = getFillImageFromShape(originalShapeObj); - const filterOption = makeFilterOptionFromFabricImage(fillImage); - const newStaticCanvas = this.graphics.createStaticCanvas(); - - shapeObj.set(makeFillPatternForFilter(this.graphics.canvasImage, filterOption, newStaticCanvas)); - this._resetPositionFillFilter(shapeObj); - } - } + constructor(graphics) { + super(componentNames.SHAPE, graphics); /** - * Create the instance of shape - * @param {string} type - Shape type - * @param {Object} options - Options to creat the shape - * @returns {fabric.Object} Shape instance + * Object of The drawing shape + * @type {fabric.Object} * @private */ - _createInstance(type, options) { - let instance; - - switch (type) { - case 'rect': - instance = new fabric.Rect(options); - break; - case 'circle': - instance = new fabric.Ellipse(extend({ - type: 'circle' - }, options)); - break; - case 'triangle': - instance = new fabric.Triangle(options); - break; - default: - instance = {}; - } - - return instance; - } + this._shapeObj = null; /** - * Get the options to create the shape - * @param {Object} options - Options to creat the shape - * @returns {Object} Shape options + * Type of the drawing shape + * @type {string} * @private */ - _extendOptions(options) { - const selectionStyles = fObjectOptions.SELECTION_STYLE; - const {canvasImage, createStaticCanvas} = this.graphics; - - options = extend({}, SHAPE_INIT_OPTIONS, this._options, selectionStyles, options); - - return makeFabricFillOption(options, canvasImage, createStaticCanvas); - } + this._type = DEFAULT_TYPE; /** - * Bind fabric events on the creating shape object - * @param {fabric.Object} shapeObj - Shape object + * Options to draw the shape + * @type {Object} * @private */ - _bindEventOnShape(shapeObj) { - const self = this; - const canvas = this.getCanvas(); - - shapeObj.on({ - added() { - self._shapeObj = this; - resizeHelper.setOrigins(self._shapeObj); - }, - selected() { - self._isSelected = true; - self._shapeObj = this; - canvas.uniformScaling = true; - canvas.defaultCursor = 'default'; - resizeHelper.setOrigins(self._shapeObj); - }, - deselected() { - self._isSelected = false; - self._shapeObj = null; - canvas.defaultCursor = 'crosshair'; - canvas.uniformScaling = false; - }, - modified() { - const currentObj = self._shapeObj; - - resizeHelper.adjustOriginToCenter(currentObj); - resizeHelper.setOrigins(currentObj); - }, - modifiedInGroup(activeSelection) { - self._fillFilterRePositionInGroupSelection(shapeObj, activeSelection); - }, - moving() { - self._resetPositionFillFilter(this); - }, - rotating() { - self._resetPositionFillFilter(this); - }, - scaling(fEvent) { - const pointer = canvas.getPointer(fEvent.e); - const currentObj = self._shapeObj; - - canvas.setCursor('crosshair'); - resizeHelper.resize(currentObj, pointer, true); - - self._resetPositionFillFilter(this); - } - }); - } + this._options = extend({}, SHAPE_INIT_OPTIONS); /** - * MouseDown event handler on canvas - * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object + * Whether the shape object is selected or not + * @type {boolean} * @private */ - _onFabricMouseDown(fEvent) { - if (!fEvent.target) { - this._isSelected = false; - this._shapeObj = false; - } - - if (!this._isSelected && !this._shapeObj) { - const canvas = this.getCanvas(); - this._startPoint = canvas.getPointer(fEvent.e); - - canvas.on({ - 'mouse:move': this._handlers.mousemove, - 'mouse:up': this._handlers.mouseup - }); - } - } + this._isSelected = false; /** - * MouseDown event handler on canvas - * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object + * Pointer for drawing shape (x, y) + * @type {Object} * @private */ - _onFabricMouseMove(fEvent) { - const canvas = this.getCanvas(); - const pointer = canvas.getPointer(fEvent.e); - const startPointX = this._startPoint.x; - const startPointY = this._startPoint.y; - const width = startPointX - pointer.x; - const height = startPointY - pointer.y; - const shape = this._shapeObj; - - if (!shape) { - this.add(this._type, { - left: startPointX, - top: startPointY, - width, - height - }).then(objectProps => { - this.fire(eventNames.ADD_OBJECT, objectProps); - }); - } else { - this._shapeObj.set({ - isRegular: this._withShiftKey - }); - - resizeHelper.resize(shape, pointer); - canvas.renderAll(); - - this._resetPositionFillFilter(shape); - } - } + this._startPoint = {}; /** - * MouseUp event handler on canvas + * Using shortcut on drawing shape + * @type {boolean} * @private */ - _onFabricMouseUp() { - const canvas = this.getCanvas(); - const startPointX = this._startPoint.x; - const startPointY = this._startPoint.y; - const shape = this._shapeObj; - - if (!shape) { - this.add(this._type, { - left: startPointX, - top: startPointY, - width: DEFAULT_WIDTH, - height: DEFAULT_HEIGHT - }).then(objectProps => { - this.fire(eventNames.ADD_OBJECT, objectProps); - }); - } else if (shape) { - resizeHelper.adjustOriginToCenter(shape); - this.fire(eventNames.OBJECT_ADDED, this.graphics.createObjectProperties(shape)); - } - - canvas.off({ - 'mouse:move': this._handlers.mousemove, - 'mouse:up': this._handlers.mouseup - }); - } + this._withShiftKey = false; /** - * Keydown event handler on document - * @param {KeyboardEvent} e - Event object + * Event handler list + * @type {Object} * @private */ - _onKeyDown(e) { - if (e.keyCode === KEY_CODES.SHIFT) { - this._withShiftKey = true; - - if (this._shapeObj) { - this._shapeObj.isRegular = true; - } - } + this._handlers = { + mousedown: this._onFabricMouseDown.bind(this), + mousemove: this._onFabricMouseMove.bind(this), + mouseup: this._onFabricMouseUp.bind(this), + keydown: this._onKeyDown.bind(this), + keyup: this._onKeyUp.bind(this), + }; + } + + /** + * Start to draw the shape on canvas + * @ignore + */ + start() { + const canvas = this.getCanvas(); + + this._isSelected = false; + + canvas.defaultCursor = 'crosshair'; + canvas.selection = false; + canvas.uniformScaling = true; + canvas.on({ + 'mouse:down': this._handlers.mousedown, + }); + + fabric.util.addListener(document, 'keydown', this._handlers.keydown); + fabric.util.addListener(document, 'keyup', this._handlers.keyup); + } + + /** + * End to draw the shape on canvas + * @ignore + */ + end() { + const canvas = this.getCanvas(); + + this._isSelected = false; + + canvas.defaultCursor = 'default'; + + canvas.selection = true; + canvas.uniformScaling = false; + canvas.off({ + 'mouse:down': this._handlers.mousedown, + }); + + fabric.util.removeListener(document, 'keydown', this._handlers.keydown); + fabric.util.removeListener(document, 'keyup', this._handlers.keyup); + } + + /** + * Set states of the current drawing shape + * @ignore + * @param {string} type - Shape type (ex: 'rect', 'circle') + * @param {Object} [options] - Shape options + * @param {(ShapeFillOption | string)} [options.fill] - {@link ShapeFillOption} or + * Shape foreground color (ex: '#fff', 'transparent') + * @param {string} [options.stoke] - Shape outline color + * @param {number} [options.strokeWidth] - Shape outline width + * @param {number} [options.width] - Width value (When type option is 'rect', this options can use) + * @param {number} [options.height] - Height value (When type option is 'rect', this options can use) + * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use) + * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use) + */ + setStates(type, options) { + this._type = type; + + if (options) { + this._options = extend(this._options, options); + } + } + + /** + * Add the shape + * @ignore + * @param {string} type - Shape type (ex: 'rect', 'circle') + * @param {Object} options - Shape options + * @param {(ShapeFillOption | string)} [options.fill] - ShapeFillOption or Shape foreground color (ex: '#fff', 'transparent') or ShapeFillOption object + * @param {string} [options.stroke] - Shape outline color + * @param {number} [options.strokeWidth] - Shape outline width + * @param {number} [options.width] - Width value (When type option is 'rect', this options can use) + * @param {number} [options.height] - Height value (When type option is 'rect', this options can use) + * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use) + * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use) + * @param {number} [options.isRegular] - Whether scaling shape has 1:1 ratio or not + * @returns {Promise} + */ + add(type, options) { + return new Promise((resolve) => { + const canvas = this.getCanvas(); + const extendOption = this._extendOptions(options); + + const shapeObj = this._createInstance(type, extendOption); + const objectProperties = this.graphics.createObjectProperties(shapeObj); + + this._bindEventOnShape(shapeObj); + + canvas.add(shapeObj).setActiveObject(shapeObj); + + this._resetPositionFillFilter(shapeObj); + + resolve(objectProperties); + }); + } + + /** + * Change the shape + * @ignore + * @param {fabric.Object} shapeObj - Selected shape object on canvas + * @param {Object} options - Shape options + * @param {(ShapeFillOption | string)} [options.fill] - {@link ShapeFillOption} or + * Shape foreground color (ex: '#fff', 'transparent') + * @param {string} [options.stroke] - Shape outline color + * @param {number} [options.strokeWidth] - Shape outline width + * @param {number} [options.width] - Width value (When type option is 'rect', this options can use) + * @param {number} [options.height] - Height value (When type option is 'rect', this options can use) + * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use) + * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use) + * @param {number} [options.isRegular] - Whether scaling shape has 1:1 ratio or not + * @returns {Promise} + */ + change(shapeObj, options) { + return new Promise((resolve, reject) => { + if (!isShape(shapeObj)) { + reject(rejectMessages.unsupportedType); + } + const hasFillOption = getFillTypeFromOption(options.fill) === 'filter'; + const { canvasImage, createStaticCanvas } = this.graphics; + + shapeObj.set( + hasFillOption ? makeFabricFillOption(options, canvasImage, createStaticCanvas) : options + ); + + if (hasFillOption) { + this._resetPositionFillFilter(shapeObj); + } + + this.getCanvas().renderAll(); + resolve(); + }); + } + + /** + * make fill property for user event + * @param {fabric.Object} shapeObj - fabric object + * @returns {Object} + */ + makeFillPropertyForUserEvent(shapeObj) { + const fillType = getFillTypeFromObject(shapeObj); + const fillProp = {}; + + if (fillType === SHAPE_FILL_TYPE.FILTER) { + const fillImage = getFillImageFromShape(shapeObj); + const filterOption = makeFilterOptionFromFabricImage(fillImage); + + fillProp.type = fillType; + fillProp.filter = filterOption; + } else { + fillProp.type = SHAPE_FILL_TYPE.COLOR; + fillProp.color = shapeObj.fill || 'transparent'; } - /** - * Keyup event handler on document - * @param {KeyboardEvent} e - Event object - * @private - */ - _onKeyUp(e) { - if (e.keyCode === KEY_CODES.SHIFT) { - this._withShiftKey = false; - - if (this._shapeObj) { - this._shapeObj.isRegular = false; - } - } + return fillProp; + } + + /** + * Copy object handling. + * @param {fabric.Object} shapeObj - Shape object + * @param {fabric.Object} originalShapeObj - Shape object + */ + processForCopiedObject(shapeObj, originalShapeObj) { + this._bindEventOnShape(shapeObj); + + if (getFillTypeFromObject(shapeObj) === 'filter') { + const fillImage = getFillImageFromShape(originalShapeObj); + const filterOption = makeFilterOptionFromFabricImage(fillImage); + const newStaticCanvas = this.graphics.createStaticCanvas(); + + shapeObj.set( + makeFillPatternForFilter(this.graphics.canvasImage, filterOption, newStaticCanvas) + ); + this._resetPositionFillFilter(shapeObj); + } + } + + /** + * Create the instance of shape + * @param {string} type - Shape type + * @param {Object} options - Options to creat the shape + * @returns {fabric.Object} Shape instance + * @private + */ + _createInstance(type, options) { + let instance; + + switch (type) { + case 'rect': + instance = new fabric.Rect(options); + break; + case 'circle': + instance = new fabric.Ellipse( + extend( + { + type: 'circle', + }, + options + ) + ); + break; + case 'triangle': + instance = new fabric.Triangle(options); + break; + default: + instance = {}; } - /** - * Reset shape position and internal proportions in the filter type fill area. - * @param {fabric.Object} shapeObj - Shape object - * @private - */ - _resetPositionFillFilter(shapeObj) { - if (getFillTypeFromObject(shapeObj) !== 'filter') { - return; - } + return instance; + } + + /** + * Get the options to create the shape + * @param {Object} options - Options to creat the shape + * @returns {Object} Shape options + * @private + */ + _extendOptions(options) { + const selectionStyles = fObjectOptions.SELECTION_STYLE; + const { canvasImage, createStaticCanvas } = this.graphics; + + options = extend({}, SHAPE_INIT_OPTIONS, this._options, selectionStyles, options); + + return makeFabricFillOption(options, canvasImage, createStaticCanvas); + } + + /** + * Bind fabric events on the creating shape object + * @param {fabric.Object} shapeObj - Shape object + * @private + */ + _bindEventOnShape(shapeObj) { + const self = this; + const canvas = this.getCanvas(); + + shapeObj.on({ + added() { + self._shapeObj = this; + resizeHelper.setOrigins(self._shapeObj); + }, + selected() { + self._isSelected = true; + self._shapeObj = this; + canvas.uniformScaling = true; + canvas.defaultCursor = 'default'; + resizeHelper.setOrigins(self._shapeObj); + }, + deselected() { + self._isSelected = false; + self._shapeObj = null; + canvas.defaultCursor = 'crosshair'; + canvas.uniformScaling = false; + }, + modified() { + const currentObj = self._shapeObj; + + resizeHelper.adjustOriginToCenter(currentObj); + resizeHelper.setOrigins(currentObj); + }, + modifiedInGroup(activeSelection) { + self._fillFilterRePositionInGroupSelection(shapeObj, activeSelection); + }, + moving() { + self._resetPositionFillFilter(this); + }, + rotating() { + self._resetPositionFillFilter(this); + }, + scaling(fEvent) { + const pointer = canvas.getPointer(fEvent.e); + const currentObj = self._shapeObj; + + canvas.setCursor('crosshair'); + resizeHelper.resize(currentObj, pointer, true); + + self._resetPositionFillFilter(this); + }, + }); + } + + /** + * MouseDown event handler on canvas + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object + * @private + */ + _onFabricMouseDown(fEvent) { + if (!fEvent.target) { + this._isSelected = false; + this._shapeObj = false; + } - const {patternSourceCanvas} = getCustomProperty(shapeObj, 'patternSourceCanvas'); + if (!this._isSelected && !this._shapeObj) { + const canvas = this.getCanvas(); + this._startPoint = canvas.getPointer(fEvent.e); - const fillImage = getFillImageFromShape(shapeObj); - const {originalAngle} = getCustomProperty(fillImage, 'originalAngle'); + canvas.on({ + 'mouse:move': this._handlers.mousemove, + 'mouse:up': this._handlers.mouseup, + }); + } + } + + /** + * MouseDown event handler on canvas + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object + * @private + */ + _onFabricMouseMove(fEvent) { + const canvas = this.getCanvas(); + const pointer = canvas.getPointer(fEvent.e); + const startPointX = this._startPoint.x; + const startPointY = this._startPoint.y; + const width = startPointX - pointer.x; + const height = startPointY - pointer.y; + const shape = this._shapeObj; + + if (!shape) { + this.add(this._type, { + left: startPointX, + top: startPointY, + width, + height, + }).then((objectProps) => { + this.fire(eventNames.ADD_OBJECT, objectProps); + }); + } else { + this._shapeObj.set({ + isRegular: this._withShiftKey, + }); - if (this.graphics.canvasImage.angle !== originalAngle) { - reMakePatternImageSource(shapeObj, this.graphics.canvasImage); - } - const {originX, originY} = shapeObj; + resizeHelper.resize(shape, pointer); + canvas.renderAll(); - resizeHelper.adjustOriginToCenter(shapeObj); + this._resetPositionFillFilter(shape); + } + } + + /** + * MouseUp event handler on canvas + * @private + */ + _onFabricMouseUp() { + const canvas = this.getCanvas(); + const startPointX = this._startPoint.x; + const startPointY = this._startPoint.y; + const shape = this._shapeObj; + + if (!shape) { + this.add(this._type, { + left: startPointX, + top: startPointY, + width: DEFAULT_WIDTH, + height: DEFAULT_HEIGHT, + }).then((objectProps) => { + this.fire(eventNames.ADD_OBJECT, objectProps); + }); + } else if (shape) { + resizeHelper.adjustOriginToCenter(shape); + this.fire(eventNames.OBJECT_ADDED, this.graphics.createObjectProperties(shape)); + } - shapeObj.width *= shapeObj.scaleX; - shapeObj.height *= shapeObj.scaleY; - shapeObj.rx *= shapeObj.scaleX; - shapeObj.ry *= shapeObj.scaleY; - shapeObj.scaleX = 1; - shapeObj.scaleY = 1; + canvas.off({ + 'mouse:move': this._handlers.mousemove, + 'mouse:up': this._handlers.mouseup, + }); + } + + /** + * Keydown event handler on document + * @param {KeyboardEvent} e - Event object + * @private + */ + _onKeyDown(e) { + if (e.keyCode === KEY_CODES.SHIFT) { + this._withShiftKey = true; + + if (this._shapeObj) { + this._shapeObj.isRegular = true; + } + } + } + + /** + * Keyup event handler on document + * @param {KeyboardEvent} e - Event object + * @private + */ + _onKeyUp(e) { + if (e.keyCode === KEY_CODES.SHIFT) { + this._withShiftKey = false; + + if (this._shapeObj) { + this._shapeObj.isRegular = false; + } + } + } + + /** + * Reset shape position and internal proportions in the filter type fill area. + * @param {fabric.Object} shapeObj - Shape object + * @private + */ + _resetPositionFillFilter(shapeObj) { + if (getFillTypeFromObject(shapeObj) !== 'filter') { + return; + } - rePositionFilterTypeFillImage(shapeObj); + const { patternSourceCanvas } = getCustomProperty(shapeObj, 'patternSourceCanvas'); - changeOrigin(shapeObj, { - originX, - originY - }); + const fillImage = getFillImageFromShape(shapeObj); + const { originalAngle } = getCustomProperty(fillImage, 'originalAngle'); - resetFillPatternCanvas(patternSourceCanvas); + if (this.graphics.canvasImage.angle !== originalAngle) { + reMakePatternImageSource(shapeObj, this.graphics.canvasImage); + } + const { originX, originY } = shapeObj; + + resizeHelper.adjustOriginToCenter(shapeObj); + + shapeObj.width *= shapeObj.scaleX; + shapeObj.height *= shapeObj.scaleY; + shapeObj.rx *= shapeObj.scaleX; + shapeObj.ry *= shapeObj.scaleY; + shapeObj.scaleX = 1; + shapeObj.scaleY = 1; + + rePositionFilterTypeFillImage(shapeObj); + + changeOrigin(shapeObj, { + originX, + originY, + }); + + resetFillPatternCanvas(patternSourceCanvas); + } + + /** + * Reset filter area position within group selection. + * @param {fabric.Object} shapeObj - Shape object + * @param {fabric.ActiveSelection} activeSelection - Shape object + * @private + */ + _fillFilterRePositionInGroupSelection(shapeObj, activeSelection) { + if (activeSelection.scaleX !== 1 || activeSelection.scaleY !== 1) { + // This is necessary because the group's scale transition state affects the relative size of the fill area. + // The only way to reset the object transformation scale state to neutral. + // {@link https://github.com/fabricjs/fabric.js/issues/5372} + activeSelection.addWithUpdate(); } - /** - * Reset filter area position within group selection. - * @param {fabric.Object} shapeObj - Shape object - * @param {fabric.ActiveSelection} activeSelection - Shape object - * @private - */ - _fillFilterRePositionInGroupSelection(shapeObj, activeSelection) { - if (activeSelection.scaleX !== 1 || activeSelection.scaleY !== 1) { - // This is necessary because the group's scale transition state affects the relative size of the fill area. - // The only way to reset the object transformation scale state to neutral. - // {@link https://github.com/fabricjs/fabric.js/issues/5372} - activeSelection.addWithUpdate(); - } - - const {angle, left, top} = shapeObj; + const { angle, left, top } = shapeObj; - activeSelection.realizeTransform(shapeObj); - this._resetPositionFillFilter(shapeObj); + activeSelection.realizeTransform(shapeObj); + this._resetPositionFillFilter(shapeObj); - shapeObj.set({ - angle, - left, - top - }); - } + shapeObj.set({ + angle, + left, + top, + }); + } } diff --git a/src/js/component/text.js b/src/js/component/text.js index d743cda56..0cdf22a5e 100644 --- a/src/js/component/text.js +++ b/src/js/component/text.js @@ -5,20 +5,20 @@ import fabric from 'fabric'; import snippet from 'tui-code-snippet'; import Component from '../interface/component'; -import {eventNames as events, componentNames, fObjectOptions} from '../consts'; -import {Promise} from '../util'; +import { eventNames as events, componentNames, fObjectOptions } from '../consts'; +import { Promise } from '../util'; const defaultStyles = { - fill: '#000000', - left: 0, - top: 0 + fill: '#000000', + left: 0, + top: 0, }; const resetStyles = { - fill: '#000000', - fontStyle: 'normal', - fontWeight: 'normal', - textAlign: 'left', - underline: false + fill: '#000000', + fontStyle: 'normal', + fontWeight: 'normal', + textAlign: 'left', + underline: false, }; const DBCLICK_TIME = 500; @@ -31,531 +31,535 @@ const DBCLICK_TIME = 500; * @ignore */ class Text extends Component { - constructor(graphics) { - super(componentNames.TEXT, graphics); - - /** - * Default text style - * @type {Object} - */ - this._defaultStyles = defaultStyles; - - /** - * Selected state - * @type {boolean} - */ - this._isSelected = false; - - /** - * Selected text object - * @type {Object} - */ - this._selectedObj = {}; - - /** - * Editing text object - * @type {Object} - */ - this._editingObj = {}; - - /** - * Listeners for fabric event - * @type {Object} - */ - this._listeners = { - mousedown: this._onFabricMouseDown.bind(this), - select: this._onFabricSelect.bind(this), - selectClear: this._onFabricSelectClear.bind(this), - scaling: this._onFabricScaling.bind(this) - }; - - /** - * Textarea element for editing - * @type {HTMLElement} - */ - this._textarea = null; - - /** - * Ratio of current canvas - * @type {number} - */ - this._ratio = 1; - - /** - * Last click time - * @type {Date} - */ - this._lastClickTime = (new Date()).getTime(); - - /** - * Text object infos before editing - * @type {Object} - */ - this._editingObjInfos = {}; - - /** - * Previous state of editing - * @type {boolean} - */ - this.isPrevEditing = false; - } + constructor(graphics) { + super(componentNames.TEXT, graphics); /** - * Start input text mode + * Default text style + * @type {Object} */ - start() { - const canvas = this.getCanvas(); - - canvas.selection = false; - canvas.defaultCursor = 'text'; - canvas.on({ - 'mouse:down': this._listeners.mousedown, - 'selection:created': this._listeners.select, - 'selection:updated': this._listeners.select, - 'before:selection:cleared': this._listeners.selectClear, - 'object:scaling': this._listeners.scaling, - 'text:editing': this._listeners.modify - }); - - canvas.forEachObject(obj => { - if (obj.type === 'i-text') { - this.adjustOriginPosition(obj, 'start'); - } - }); - - this.setCanvasRatio(); - } + this._defaultStyles = defaultStyles; /** - * End input text mode + * Selected state + * @type {boolean} */ - end() { - const canvas = this.getCanvas(); - - canvas.selection = true; - canvas.defaultCursor = 'default'; - - canvas.forEachObject(obj => { - if (obj.type === 'i-text') { - if (obj.text === '') { - canvas.remove(obj); - } else { - this.adjustOriginPosition(obj, 'end'); - } - } - }); - - canvas.off({ - 'mouse:down': this._listeners.mousedown, - 'object:selected': this._listeners.select, - 'before:selection:cleared': this._listeners.selectClear, - 'object:scaling': this._listeners.scaling, - 'text:editing': this._listeners.modify - }); - } + this._isSelected = false; /** - * Adjust the origin position - * @param {fabric.Object} text - text object - * @param {string} editStatus - 'start' or 'end' + * Selected text object + * @type {Object} */ - adjustOriginPosition(text, editStatus) { - let [originX, originY] = ['center', 'center']; - if (editStatus === 'start') { - [originX, originY] = ['left', 'top']; - } - - const {x: left, y: top} = text.getPointByOrigin(originX, originY); - text.set({ - left, - top, - originX, - originY - }); - text.setCoords(); - } + this._selectedObj = {}; /** - * Add new text on canvas image - * @param {string} text - Initial input text - * @param {Object} options - Options for generating text - * @param {Object} [options.styles] Initial styles - * @param {string} [options.styles.fill] Color - * @param {string} [options.styles.fontFamily] Font type for text - * @param {number} [options.styles.fontSize] Size - * @param {string} [options.styles.fontStyle] Type of inclination (normal / italic) - * @param {string} [options.styles.fontWeight] Type of thicker or thinner looking (normal / bold) - * @param {string} [options.styles.textAlign] Type of text align (left / center / right) - * @param {string} [options.styles.textDecoration] Type of line (underline / line-through / overline) - * @param {{x: number, y: number}} [options.position] - Initial position - * @returns {Promise} + * Editing text object + * @type {Object} */ - add(text, options) { - return new Promise(resolve => { - const canvas = this.getCanvas(); - let newText = null; - let selectionStyle = fObjectOptions.SELECTION_STYLE; - let styles = this._defaultStyles; - - this._setInitPos(options.position); - - if (options.styles) { - styles = snippet.extend(styles, options.styles); - } - - if (!snippet.isExisty(options.autofocus)) { - options.autofocus = true; - } - - newText = new fabric.IText(text, styles); - selectionStyle = snippet.extend({}, selectionStyle, { - originX: 'left', - originY: 'top' - }); - - newText.set(selectionStyle); - newText.on({ - mouseup: this._onFabricMouseUp.bind(this) - }); - - canvas.add(newText); - - if (options.autofocus) { - newText.enterEditing(); - newText.selectAll(); - } - - if (!canvas.getActiveObject()) { - canvas.setActiveObject(newText); - } - - this.isPrevEditing = true; - resolve(this.graphics.createObjectProperties(newText)); - }); - } + this._editingObj = {}; /** - * Change text of activate object on canvas image - * @param {Object} activeObj - Current selected text object - * @param {string} text - Changed text - * @returns {Promise} + * Listeners for fabric event + * @type {Object} */ - change(activeObj, text) { - return new Promise(resolve => { - activeObj.set('text', text); - - this.getCanvas().renderAll(); - resolve(); - }); - } + this._listeners = { + mousedown: this._onFabricMouseDown.bind(this), + select: this._onFabricSelect.bind(this), + selectClear: this._onFabricSelectClear.bind(this), + scaling: this._onFabricScaling.bind(this), + }; /** - * Set style - * @param {Object} activeObj - Current selected text object - * @param {Object} styleObj - Initial styles - * @param {string} [styleObj.fill] Color - * @param {string} [styleObj.fontFamily] Font type for text - * @param {number} [styleObj.fontSize] Size - * @param {string} [styleObj.fontStyle] Type of inclination (normal / italic) - * @param {string} [styleObj.fontWeight] Type of thicker or thinner looking (normal / bold) - * @param {string} [styleObj.textAlign] Type of text align (left / center / right) - * @param {string} [styleObj.textDecoration] Type of line (underline / line-through / overline) - * @returns {Promise} + * Textarea element for editing + * @type {HTMLElement} */ - setStyle(activeObj, styleObj) { - return new Promise(resolve => { - snippet.forEach(styleObj, (val, key) => { - if (activeObj[key] === val && key !== 'fontSize') { - styleObj[key] = resetStyles[key] || ''; - } - }, this); - - if ('textDecoration' in styleObj) { - snippet.extend(styleObj, this._getTextDecorationAdaptObject(styleObj.textDecoration)); - } - - activeObj.set(styleObj); - - this.getCanvas().renderAll(); - resolve(); - }); - } + this._textarea = null; /** - * Get the text - * @param {Object} activeObj - Current selected text object - * @returns {String} text + * Ratio of current canvas + * @type {number} */ - getText(activeObj) { - return activeObj.text; - } + this._ratio = 1; /** - * Set infos of the current selected object - * @param {fabric.Text} obj - Current selected text object - * @param {boolean} state - State of selecting + * Last click time + * @type {Date} */ - setSelectedInfo(obj, state) { - this._selectedObj = obj; - this._isSelected = state; - } + this._lastClickTime = new Date().getTime(); /** - * Whether object is selected or not - * @returns {boolean} State of selecting + * Text object infos before editing + * @type {Object} */ - isSelected() { - return this._isSelected; - } + this._editingObjInfos = {}; /** - * Get current selected text object - * @returns {fabric.Text} Current selected text object + * Previous state of editing + * @type {boolean} */ - getSelectedObj() { - return this._selectedObj; + this.isPrevEditing = false; + } + + /** + * Start input text mode + */ + start() { + const canvas = this.getCanvas(); + + canvas.selection = false; + canvas.defaultCursor = 'text'; + canvas.on({ + 'mouse:down': this._listeners.mousedown, + 'selection:created': this._listeners.select, + 'selection:updated': this._listeners.select, + 'before:selection:cleared': this._listeners.selectClear, + 'object:scaling': this._listeners.scaling, + 'text:editing': this._listeners.modify, + }); + + canvas.forEachObject((obj) => { + if (obj.type === 'i-text') { + this.adjustOriginPosition(obj, 'start'); + } + }); + + this.setCanvasRatio(); + } + + /** + * End input text mode + */ + end() { + const canvas = this.getCanvas(); + + canvas.selection = true; + canvas.defaultCursor = 'default'; + + canvas.forEachObject((obj) => { + if (obj.type === 'i-text') { + if (obj.text === '') { + canvas.remove(obj); + } else { + this.adjustOriginPosition(obj, 'end'); + } + } + }); + + canvas.off({ + 'mouse:down': this._listeners.mousedown, + 'object:selected': this._listeners.select, + 'before:selection:cleared': this._listeners.selectClear, + 'object:scaling': this._listeners.scaling, + 'text:editing': this._listeners.modify, + }); + } + + /** + * Adjust the origin position + * @param {fabric.Object} text - text object + * @param {string} editStatus - 'start' or 'end' + */ + adjustOriginPosition(text, editStatus) { + let [originX, originY] = ['center', 'center']; + if (editStatus === 'start') { + [originX, originY] = ['left', 'top']; } - /** - * Set ratio value of canvas - */ - setCanvasRatio() { - const canvasElement = this.getCanvasElement(); - const cssWidth = parseInt(canvasElement.style.maxWidth, 10); - const originWidth = canvasElement.width; - const ratio = originWidth / cssWidth; - - this._ratio = ratio; + const { x: left, y: top } = text.getPointByOrigin(originX, originY); + text.set({ + left, + top, + originX, + originY, + }); + text.setCoords(); + } + + /** + * Add new text on canvas image + * @param {string} text - Initial input text + * @param {Object} options - Options for generating text + * @param {Object} [options.styles] Initial styles + * @param {string} [options.styles.fill] Color + * @param {string} [options.styles.fontFamily] Font type for text + * @param {number} [options.styles.fontSize] Size + * @param {string} [options.styles.fontStyle] Type of inclination (normal / italic) + * @param {string} [options.styles.fontWeight] Type of thicker or thinner looking (normal / bold) + * @param {string} [options.styles.textAlign] Type of text align (left / center / right) + * @param {string} [options.styles.textDecoration] Type of line (underline / line-through / overline) + * @param {{x: number, y: number}} [options.position] - Initial position + * @returns {Promise} + */ + add(text, options) { + return new Promise((resolve) => { + const canvas = this.getCanvas(); + let newText = null; + let selectionStyle = fObjectOptions.SELECTION_STYLE; + let styles = this._defaultStyles; + + this._setInitPos(options.position); + + if (options.styles) { + styles = snippet.extend(styles, options.styles); + } + + if (!snippet.isExisty(options.autofocus)) { + options.autofocus = true; + } + + newText = new fabric.IText(text, styles); + selectionStyle = snippet.extend({}, selectionStyle, { + originX: 'left', + originY: 'top', + }); + + newText.set(selectionStyle); + newText.on({ + mouseup: this._onFabricMouseUp.bind(this), + }); + + canvas.add(newText); + + if (options.autofocus) { + newText.enterEditing(); + newText.selectAll(); + } + + if (!canvas.getActiveObject()) { + canvas.setActiveObject(newText); + } + + this.isPrevEditing = true; + resolve(this.graphics.createObjectProperties(newText)); + }); + } + + /** + * Change text of activate object on canvas image + * @param {Object} activeObj - Current selected text object + * @param {string} text - Changed text + * @returns {Promise} + */ + change(activeObj, text) { + return new Promise((resolve) => { + activeObj.set('text', text); + + this.getCanvas().renderAll(); + resolve(); + }); + } + + /** + * Set style + * @param {Object} activeObj - Current selected text object + * @param {Object} styleObj - Initial styles + * @param {string} [styleObj.fill] Color + * @param {string} [styleObj.fontFamily] Font type for text + * @param {number} [styleObj.fontSize] Size + * @param {string} [styleObj.fontStyle] Type of inclination (normal / italic) + * @param {string} [styleObj.fontWeight] Type of thicker or thinner looking (normal / bold) + * @param {string} [styleObj.textAlign] Type of text align (left / center / right) + * @param {string} [styleObj.textDecoration] Type of line (underline / line-through / overline) + * @returns {Promise} + */ + setStyle(activeObj, styleObj) { + return new Promise((resolve) => { + snippet.forEach( + styleObj, + (val, key) => { + if (activeObj[key] === val && key !== 'fontSize') { + styleObj[key] = resetStyles[key] || ''; + } + }, + this + ); + + if ('textDecoration' in styleObj) { + snippet.extend(styleObj, this._getTextDecorationAdaptObject(styleObj.textDecoration)); + } + + activeObj.set(styleObj); + + this.getCanvas().renderAll(); + resolve(); + }); + } + + /** + * Get the text + * @param {Object} activeObj - Current selected text object + * @returns {String} text + */ + getText(activeObj) { + return activeObj.text; + } + + /** + * Set infos of the current selected object + * @param {fabric.Text} obj - Current selected text object + * @param {boolean} state - State of selecting + */ + setSelectedInfo(obj, state) { + this._selectedObj = obj; + this._isSelected = state; + } + + /** + * Whether object is selected or not + * @returns {boolean} State of selecting + */ + isSelected() { + return this._isSelected; + } + + /** + * Get current selected text object + * @returns {fabric.Text} Current selected text object + */ + getSelectedObj() { + return this._selectedObj; + } + + /** + * Set ratio value of canvas + */ + setCanvasRatio() { + const canvasElement = this.getCanvasElement(); + const cssWidth = parseInt(canvasElement.style.maxWidth, 10); + const originWidth = canvasElement.width; + const ratio = originWidth / cssWidth; + + this._ratio = ratio; + } + + /** + * Get ratio value of canvas + * @returns {number} Ratio value + */ + getCanvasRatio() { + return this._ratio; + } + + /** + * Get text decoration adapt object + * @param {string} textDecoration - text decoration option string + * @returns {object} adapt object for override + */ + _getTextDecorationAdaptObject(textDecoration) { + return { + underline: textDecoration === 'underline', + linethrough: textDecoration === 'line-through', + overline: textDecoration === 'overline', + }; + } + + /** + * Set initial position on canvas image + * @param {{x: number, y: number}} [position] - Selected position + * @private + */ + _setInitPos(position) { + position = position || this.getCanvasImage().getCenterPoint(); + + this._defaultStyles.left = position.x; + this._defaultStyles.top = position.y; + } + + /** + * Input event handler + * @private + */ + _onInput() { + const ratio = this.getCanvasRatio(); + const obj = this._editingObj; + const textareaStyle = this._textarea.style; + + textareaStyle.width = `${Math.ceil(obj.width / ratio)}px`; + textareaStyle.height = `${Math.ceil(obj.height / ratio)}px`; + } + + /** + * Keydown event handler + * @private + */ + _onKeyDown() { + const ratio = this.getCanvasRatio(); + const obj = this._editingObj; + const textareaStyle = this._textarea.style; + + setTimeout(() => { + obj.text(this._textarea.value); + + textareaStyle.width = `${Math.ceil(obj.width / ratio)}px`; + textareaStyle.height = `${Math.ceil(obj.height / ratio)}px`; + }, 0); + } + + /** + * Blur event handler + * @private + */ + _onBlur() { + const ratio = this.getCanvasRatio(); + const editingObj = this._editingObj; + const editingObjInfos = this._editingObjInfos; + const textContent = this._textarea.value; + let transWidth = editingObj.width / ratio - editingObjInfos.width / ratio; + let transHeight = editingObj.height / ratio - editingObjInfos.height / ratio; + + if (ratio === 1) { + transWidth /= 2; + transHeight /= 2; } - /** - * Get ratio value of canvas - * @returns {number} Ratio value - */ - getCanvasRatio() { - return this._ratio; - } + this._textarea.style.display = 'none'; - /** - * Get text decoration adapt object - * @param {string} textDecoration - text decoration option string - * @returns {object} adapt object for override - */ - _getTextDecorationAdaptObject(textDecoration) { - return { - underline: textDecoration === 'underline', - linethrough: textDecoration === 'line-through', - overline: textDecoration === 'overline' - }; - } + editingObj.set({ + left: editingObjInfos.left + transWidth, + top: editingObjInfos.top + transHeight, + }); - /** - * Set initial position on canvas image - * @param {{x: number, y: number}} [position] - Selected position - * @private - */ - _setInitPos(position) { - position = position || this.getCanvasImage().getCenterPoint(); + if (textContent.length) { + this.getCanvas().add(editingObj); - this._defaultStyles.left = position.x; - this._defaultStyles.top = position.y; - } + const params = { + id: snippet.stamp(editingObj), + type: editingObj.type, + text: textContent, + }; - /** - * Input event handler - * @private - */ - _onInput() { - const ratio = this.getCanvasRatio(); - const obj = this._editingObj; - const textareaStyle = this._textarea.style; - - textareaStyle.width = `${Math.ceil(obj.width / ratio)}px`; - textareaStyle.height = `${Math.ceil(obj.height / ratio)}px`; + this.fire(events.TEXT_CHANGED, params); } - - /** - * Keydown event handler - * @private - */ - _onKeyDown() { - const ratio = this.getCanvasRatio(); - const obj = this._editingObj; - const textareaStyle = this._textarea.style; - - setTimeout(() => { - obj.text(this._textarea.value); - - textareaStyle.width = `${Math.ceil(obj.width / ratio)}px`; - textareaStyle.height = `${Math.ceil(obj.height / ratio)}px`; - }, 0); + } + + /** + * Scroll event handler + * @private + */ + _onScroll() { + this._textarea.scrollLeft = 0; + this._textarea.scrollTop = 0; + } + + /** + * Fabric scaling event handler + * @param {fabric.Event} fEvent - Current scaling event on selected object + * @private + */ + _onFabricScaling(fEvent) { + const obj = fEvent.target; + const scalingSize = obj.fontSize * obj.scaleY; + + obj.fontSize = scalingSize; + obj.scaleX = 1; + obj.scaleY = 1; + } + + /** + * onSelectClear handler in fabric canvas + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event + * @private + */ + _onFabricSelectClear(fEvent) { + const obj = this.getSelectedObj(); + + this.isPrevEditing = true; + + this.setSelectedInfo(fEvent.target, false); + + if (obj) { + // obj is empty object at initial time, will be set fabric object + if (obj.text === '') { + this.getCanvas().remove(obj); + } } - - /** - * Blur event handler - * @private - */ - _onBlur() { - const ratio = this.getCanvasRatio(); - const editingObj = this._editingObj; - const editingObjInfos = this._editingObjInfos; - const textContent = this._textarea.value; - let transWidth = (editingObj.width / ratio) - (editingObjInfos.width / ratio); - let transHeight = (editingObj.height / ratio) - (editingObjInfos.height / ratio); - - if (ratio === 1) { - transWidth /= 2; - transHeight /= 2; - } - - this._textarea.style.display = 'none'; - - editingObj.set({ - left: editingObjInfos.left + transWidth, - top: editingObjInfos.top + transHeight - }); - - if (textContent.length) { - this.getCanvas().add(editingObj); - - const params = { - id: snippet.stamp(editingObj), - type: editingObj.type, - text: textContent - }; - - this.fire(events.TEXT_CHANGED, params); - } + } + + /** + * onSelect handler in fabric canvas + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event + * @private + */ + _onFabricSelect(fEvent) { + this.isPrevEditing = true; + + this.setSelectedInfo(fEvent.target, true); + } + + /** + * Fabric 'mousedown' event handler + * @param {fabric.Event} fEvent - Current mousedown event on selected object + * @private + */ + _onFabricMouseDown(fEvent) { + const obj = fEvent.target; + + if (obj && !obj.isType('text')) { + return; } - /** - * Scroll event handler - * @private - */ - _onScroll() { - this._textarea.scrollLeft = 0; - this._textarea.scrollTop = 0; - } + if (this.isPrevEditing) { + this.isPrevEditing = false; - /** - * Fabric scaling event handler - * @param {fabric.Event} fEvent - Current scaling event on selected object - * @private - */ - _onFabricScaling(fEvent) { - const obj = fEvent.target; - const scalingSize = obj.fontSize * obj.scaleY; - - obj.fontSize = scalingSize; - obj.scaleX = 1; - obj.scaleY = 1; + return; } - /** - * onSelectClear handler in fabric canvas - * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event - * @private - */ - _onFabricSelectClear(fEvent) { - const obj = this.getSelectedObj(); - - this.isPrevEditing = true; - - this.setSelectedInfo(fEvent.target, false); - - if (obj) { - // obj is empty object at initial time, will be set fabric object - if (obj.text === '') { - this.getCanvas().remove(obj); - } - } - } - - /** - * onSelect handler in fabric canvas - * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event - * @private - */ - _onFabricSelect(fEvent) { - this.isPrevEditing = true; - - this.setSelectedInfo(fEvent.target, true); - } - - /** - * Fabric 'mousedown' event handler - * @param {fabric.Event} fEvent - Current mousedown event on selected object - * @private - */ - _onFabricMouseDown(fEvent) { - const obj = fEvent.target; - - if (obj && !obj.isType('text')) { - return; - } - - if (this.isPrevEditing) { - this.isPrevEditing = false; - - return; - } - - this._fireAddText(fEvent); + this._fireAddText(fEvent); + } + + /** + * Fire 'addText' event if object is not selected. + * @param {fabric.Event} fEvent - Current mousedown event on selected object + * @private + */ + _fireAddText(fEvent) { + const obj = fEvent.target; + const e = fEvent.e || {}; + const originPointer = this.getCanvas().getPointer(e); + + if (!obj) { + this.fire(events.ADD_TEXT, { + originPosition: { + x: originPointer.x, + y: originPointer.y, + }, + clientPosition: { + x: e.clientX || 0, + y: e.clientY || 0, + }, + }); } - - /** - * Fire 'addText' event if object is not selected. - * @param {fabric.Event} fEvent - Current mousedown event on selected object - * @private - */ - _fireAddText(fEvent) { - const obj = fEvent.target; - const e = fEvent.e || {}; - const originPointer = this.getCanvas().getPointer(e); - - if (!obj) { - this.fire(events.ADD_TEXT, { - originPosition: { - x: originPointer.x, - y: originPointer.y - }, - clientPosition: { - x: e.clientX || 0, - y: e.clientY || 0 - } - }); - } + } + + /** + * Fabric mouseup event handler + * @param {fabric.Event} fEvent - Current mousedown event on selected object + * @private + */ + _onFabricMouseUp(fEvent) { + const { target } = fEvent; + const newClickTime = new Date().getTime(); + + if (this._isDoubleClick(newClickTime) && !target.isEditing) { + target.enterEditing(); } - /** - * Fabric mouseup event handler - * @param {fabric.Event} fEvent - Current mousedown event on selected object - * @private - */ - _onFabricMouseUp(fEvent) { - const {target} = fEvent; - const newClickTime = (new Date()).getTime(); - - if (this._isDoubleClick(newClickTime) && !target.isEditing) { - target.enterEditing(); - } - - if (target.isEditing) { - this.fire(events.TEXT_EDITING); // fire editing text event - } - - this._lastClickTime = newClickTime; + if (target.isEditing) { + this.fire(events.TEXT_EDITING); // fire editing text event } - /** - * Get state of firing double click event - * @param {Date} newClickTime - Current clicked time - * @returns {boolean} Whether double clicked or not - * @private - */ - _isDoubleClick(newClickTime) { - return (newClickTime - this._lastClickTime < DBCLICK_TIME); - } + this._lastClickTime = newClickTime; + } + + /** + * Get state of firing double click event + * @param {Date} newClickTime - Current clicked time + * @returns {boolean} Whether double clicked or not + * @private + */ + _isDoubleClick(newClickTime) { + return newClickTime - this._lastClickTime < DBCLICK_TIME; + } } export default Text; diff --git a/src/js/consts.js b/src/js/consts.js index 79fbe833b..4145e98db 100644 --- a/src/js/consts.js +++ b/src/js/consts.js @@ -2,7 +2,7 @@ * @author NHN Ent. FE Development Team * @fileoverview Constants */ -import {keyMirror} from './util'; +import { keyMirror } from './util'; /** * Editor help features @@ -15,8 +15,8 @@ export const HELP_MENUS = ['undo', 'redo', 'reset', 'delete', 'deleteAll']; * @type {Object.} */ export const FILTER_NAME_VALUE_MAP = { - blur: 'blur', - blocksize: 'pixelate' + blur: 'blur', + blocksize: 'pixelate', }; /** @@ -24,8 +24,8 @@ export const FILTER_NAME_VALUE_MAP = { * @type {Object.} */ export const SHAPE_FILL_TYPE = { - FILTER: 'filter', - COLOR: 'color' + FILTER: 'filter', + COLOR: 'color', }; /** @@ -39,16 +39,16 @@ export const SHAPE_TYPE = ['rect', 'circle', 'triangle']; * @type {Object.} */ export const componentNames = keyMirror( - 'IMAGE_LOADER', - 'CROPPER', - 'FLIP', - 'ROTATION', - 'FREE_DRAWING', - 'LINE', - 'TEXT', - 'ICON', - 'FILTER', - 'SHAPE' + 'IMAGE_LOADER', + 'CROPPER', + 'FLIP', + 'ROTATION', + 'FREE_DRAWING', + 'LINE', + 'TEXT', + 'ICON', + 'FILTER', + 'SHAPE' ); /** @@ -56,10 +56,10 @@ export const componentNames = keyMirror( * @type {Object} */ export const SHAPE_DEFAULT_OPTIONS = { - lockSkewingX: true, - lockSkewingY: true, - bringForward: true, - isRegular: false + lockSkewingX: true, + lockSkewingY: true, + bringForward: true, + isRegular: false, }; /** @@ -67,12 +67,12 @@ export const SHAPE_DEFAULT_OPTIONS = { * @type {Object} */ export const CROPZONE_DEFAULT_OPTIONS = { - hasRotatingPoint: false, - hasBorders: false, - lockScalingFlip: true, - lockRotation: true, - lockSkewingX: true, - lockSkewingY: true + hasRotatingPoint: false, + hasBorders: false, + lockScalingFlip: true, + lockRotation: true, + lockSkewingX: true, + lockSkewingY: true, }; /** @@ -80,26 +80,26 @@ export const CROPZONE_DEFAULT_OPTIONS = { * @type {Object.} */ export const commandNames = { - 'CLEAR_OBJECTS': 'clearObjects', - 'LOAD_IMAGE': 'loadImage', - 'FLIP_IMAGE': 'flip', - 'ROTATE_IMAGE': 'rotate', - 'ADD_OBJECT': 'addObject', - 'REMOVE_OBJECT': 'removeObject', - 'APPLY_FILTER': 'applyFilter', - 'REMOVE_FILTER': 'removeFilter', - 'ADD_ICON': 'addIcon', - 'CHANGE_ICON_COLOR': 'changeIconColor', - 'ADD_SHAPE': 'addShape', - 'CHANGE_SHAPE': 'changeShape', - 'ADD_TEXT': 'addText', - 'CHANGE_TEXT': 'changeText', - 'CHANGE_TEXT_STYLE': 'changeTextStyle', - 'ADD_IMAGE_OBJECT': 'addImageObject', - 'RESIZE_CANVAS_DIMENSION': 'resizeCanvasDimension', - 'SET_OBJECT_PROPERTIES': 'setObjectProperties', - 'SET_OBJECT_POSITION': 'setObjectPosition', - 'CHANGE_SELECTION': 'changeSelection' + CLEAR_OBJECTS: 'clearObjects', + LOAD_IMAGE: 'loadImage', + FLIP_IMAGE: 'flip', + ROTATE_IMAGE: 'rotate', + ADD_OBJECT: 'addObject', + REMOVE_OBJECT: 'removeObject', + APPLY_FILTER: 'applyFilter', + REMOVE_FILTER: 'removeFilter', + ADD_ICON: 'addIcon', + CHANGE_ICON_COLOR: 'changeIconColor', + ADD_SHAPE: 'addShape', + CHANGE_SHAPE: 'changeShape', + ADD_TEXT: 'addText', + CHANGE_TEXT: 'changeText', + CHANGE_TEXT_STYLE: 'changeTextStyle', + ADD_IMAGE_OBJECT: 'addImageObject', + RESIZE_CANVAS_DIMENSION: 'resizeCanvasDimension', + SET_OBJECT_PROPERTIES: 'setObjectProperties', + SET_OBJECT_POSITION: 'setObjectPosition', + CHANGE_SELECTION: 'changeSelection', }; /** @@ -107,28 +107,28 @@ export const commandNames = { * @type {Object.} */ export const eventNames = { - OBJECT_ACTIVATED: 'objectActivated', - OBJECT_MOVED: 'objectMoved', - OBJECT_SCALED: 'objectScaled', - OBJECT_CREATED: 'objectCreated', - OBJECT_ROTATED: 'objectRotated', - OBJECT_ADDED: 'objectAdded', - OBJECT_MODIFIED: 'objectModified', - TEXT_EDITING: 'textEditing', - TEXT_CHANGED: 'textChanged', - ICON_CREATE_RESIZE: 'iconCreateResize', - ICON_CREATE_END: 'iconCreateEnd', - ADD_TEXT: 'addText', - ADD_OBJECT: 'addObject', - ADD_OBJECT_AFTER: 'addObjectAfter', - MOUSE_DOWN: 'mousedown', - MOUSE_UP: 'mouseup', - MOUSE_MOVE: 'mousemove', - // UNDO/REDO Events - REDO_STACK_CHANGED: 'redoStackChanged', - UNDO_STACK_CHANGED: 'undoStackChanged', - SELECTION_CLEARED: 'selectionCleared', - SELECTION_CREATED: 'selectionCreated' + OBJECT_ACTIVATED: 'objectActivated', + OBJECT_MOVED: 'objectMoved', + OBJECT_SCALED: 'objectScaled', + OBJECT_CREATED: 'objectCreated', + OBJECT_ROTATED: 'objectRotated', + OBJECT_ADDED: 'objectAdded', + OBJECT_MODIFIED: 'objectModified', + TEXT_EDITING: 'textEditing', + TEXT_CHANGED: 'textChanged', + ICON_CREATE_RESIZE: 'iconCreateResize', + ICON_CREATE_END: 'iconCreateEnd', + ADD_TEXT: 'addText', + ADD_OBJECT: 'addObject', + ADD_OBJECT_AFTER: 'addObjectAfter', + MOUSE_DOWN: 'mousedown', + MOUSE_UP: 'mouseup', + MOUSE_MOVE: 'mousemove', + // UNDO/REDO Events + REDO_STACK_CHANGED: 'redoStackChanged', + UNDO_STACK_CHANGED: 'undoStackChanged', + SELECTION_CLEARED: 'selectionCleared', + SELECTION_CREATED: 'selectionCreated', }; /** @@ -136,13 +136,13 @@ export const eventNames = { * @type {Object.} */ export const drawingModes = keyMirror( - 'NORMAL', - 'CROPPER', - 'FREE_DRAWING', - 'LINE_DRAWING', - 'TEXT', - 'SHAPE', - 'ICON' + 'NORMAL', + 'CROPPER', + 'FREE_DRAWING', + 'LINE_DRAWING', + 'TEXT', + 'SHAPE', + 'ICON' ); /** @@ -150,15 +150,15 @@ export const drawingModes = keyMirror( * @type {Object.} */ export const keyCodes = { - Z: 90, - Y: 89, - C: 67, - V: 86, - SHIFT: 16, - BACKSPACE: 8, - DEL: 46, - ARROW_DOWN: 40, - ARROW_UP: 38 + Z: 90, + Y: 89, + C: 67, + V: 86, + SHIFT: 16, + BACKSPACE: 8, + DEL: 46, + ARROW_DOWN: 40, + ARROW_UP: 38, }; /** @@ -166,14 +166,14 @@ export const keyCodes = { * @type {Object.} */ export const fObjectOptions = { - SELECTION_STYLE: { - borderColor: 'red', - cornerColor: 'green', - cornerSize: 10, - originX: 'center', - originY: 'center', - transparentCorners: false - } + SELECTION_STYLE: { + borderColor: 'red', + cornerColor: 'green', + cornerSize: 10, + originX: 'center', + originY: 'center', + transparentCorners: false, + }, }; /** @@ -181,20 +181,20 @@ export const fObjectOptions = { * @type {Object.} */ export const rejectMessages = { - addedObject: 'The object is already added.', - flip: 'The flipX and flipY setting values are not changed.', - invalidDrawingMode: 'This operation is not supported in the drawing mode.', - invalidParameters: 'Invalid parameters.', - isLock: 'The executing command state is locked.', - loadImage: 'The background image is empty.', - loadingImageFailed: 'Invalid image loaded.', - noActiveObject: 'There is no active object.', - noObject: 'The object is not in canvas.', - redo: 'The promise of redo command is reject.', - rotation: 'The current angle is same the old angle.', - undo: 'The promise of undo command is reject.', - unsupportedOperation: 'Unsupported operation.', - unsupportedType: 'Unsupported object type.' + addedObject: 'The object is already added.', + flip: 'The flipX and flipY setting values are not changed.', + invalidDrawingMode: 'This operation is not supported in the drawing mode.', + invalidParameters: 'Invalid parameters.', + isLock: 'The executing command state is locked.', + loadImage: 'The background image is empty.', + loadingImageFailed: 'Invalid image loaded.', + noActiveObject: 'There is no active object.', + noObject: 'The object is not in canvas.', + redo: 'The promise of redo command is reject.', + rotation: 'The current angle is same the old angle.', + undo: 'The promise of undo command is reject.', + unsupportedOperation: 'Unsupported operation.', + unsupportedType: 'Unsupported object type.', }; /** @@ -202,86 +202,92 @@ export const rejectMessages = { * @type {Object.} */ export const defaultIconPath = { - 'icon-arrow': 'M40 12V0l24 24-24 24V36H0V12h40z', - 'icon-arrow-2': 'M49,32 H3 V22 h46 l-18,-18 h12 l23,23 L43,50 h-12 l18,-18 z ', - 'icon-arrow-3': 'M43.349998,27 L17.354,53 H1.949999 l25.996,-26 L1.949999,1 h15.404 L43.349998,27 z ', - 'icon-star': 'M35,54.557999 l-19.912001,10.468 l3.804,-22.172001 l-16.108,-15.7 l22.26,-3.236 L35,3.746 l9.956,20.172001 l22.26,3.236 l-16.108,15.7 l3.804,22.172001 z ', - 'icon-star-2': 'M17,31.212 l-7.194,4.08 l-4.728,-6.83 l-8.234,0.524 l-1.328,-8.226 l-7.644,-3.14 l2.338,-7.992 l-5.54,-6.18 l5.54,-6.176 l-2.338,-7.994 l7.644,-3.138 l1.328,-8.226 l8.234,0.522 l4.728,-6.83 L17,-24.312 l7.194,-4.08 l4.728,6.83 l8.234,-0.522 l1.328,8.226 l7.644,3.14 l-2.338,7.992 l5.54,6.178 l-5.54,6.178 l2.338,7.992 l-7.644,3.14 l-1.328,8.226 l-8.234,-0.524 l-4.728,6.83 z ', - 'icon-polygon': 'M3,31 L19,3 h32 l16,28 l-16,28 H19 z ', - 'icon-location': 'M24 62C8 45.503 0 32.837 0 24 0 10.745 10.745 0 24 0s24 10.745 24 24c0 8.837-8 21.503-24 38zm0-28c5.523 0 10-4.477 10-10s-4.477-10-10-10-10 4.477-10 10 4.477 10 10 10z', - 'icon-heart': 'M49.994999,91.349998 l-6.96,-6.333 C18.324001,62.606995 2.01,47.829002 2.01,29.690998 C2.01,14.912998 13.619999,3.299999 28.401001,3.299999 c8.349,0 16.362,5.859 21.594,12 c5.229,-6.141 13.242001,-12 21.591,-12 c14.778,0 26.390999,11.61 26.390999,26.390999 c0,18.138 -16.314001,32.916 -41.025002,55.374001 l-6.96,6.285 z ', - 'icon-bubble': 'M44 48L34 58V48H12C5.373 48 0 42.627 0 36V12C0 5.373 5.373 0 12 0h40c6.627 0 12 5.373 12 12v24c0 6.627-5.373 12-12 12h-8z' + 'icon-arrow': 'M40 12V0l24 24-24 24V36H0V12h40z', + 'icon-arrow-2': 'M49,32 H3 V22 h46 l-18,-18 h12 l23,23 L43,50 h-12 l18,-18 z ', + 'icon-arrow-3': + 'M43.349998,27 L17.354,53 H1.949999 l25.996,-26 L1.949999,1 h15.404 L43.349998,27 z ', + 'icon-star': + 'M35,54.557999 l-19.912001,10.468 l3.804,-22.172001 l-16.108,-15.7 l22.26,-3.236 L35,3.746 l9.956,20.172001 l22.26,3.236 l-16.108,15.7 l3.804,22.172001 z ', + 'icon-star-2': + 'M17,31.212 l-7.194,4.08 l-4.728,-6.83 l-8.234,0.524 l-1.328,-8.226 l-7.644,-3.14 l2.338,-7.992 l-5.54,-6.18 l5.54,-6.176 l-2.338,-7.994 l7.644,-3.138 l1.328,-8.226 l8.234,0.522 l4.728,-6.83 L17,-24.312 l7.194,-4.08 l4.728,6.83 l8.234,-0.522 l1.328,8.226 l7.644,3.14 l-2.338,7.992 l5.54,6.178 l-5.54,6.178 l2.338,7.992 l-7.644,3.14 l-1.328,8.226 l-8.234,-0.524 l-4.728,6.83 z ', + 'icon-polygon': 'M3,31 L19,3 h32 l16,28 l-16,28 H19 z ', + 'icon-location': + 'M24 62C8 45.503 0 32.837 0 24 0 10.745 10.745 0 24 0s24 10.745 24 24c0 8.837-8 21.503-24 38zm0-28c5.523 0 10-4.477 10-10s-4.477-10-10-10-10 4.477-10 10 4.477 10 10 10z', + 'icon-heart': + 'M49.994999,91.349998 l-6.96,-6.333 C18.324001,62.606995 2.01,47.829002 2.01,29.690998 C2.01,14.912998 13.619999,3.299999 28.401001,3.299999 c8.349,0 16.362,5.859 21.594,12 c5.229,-6.141 13.242001,-12 21.591,-12 c14.778,0 26.390999,11.61 26.390999,26.390999 c0,18.138 -16.314001,32.916 -41.025002,55.374001 l-6.96,6.285 z ', + 'icon-bubble': + 'M44 48L34 58V48H12C5.373 48 0 42.627 0 36V12C0 5.373 5.373 0 12 0h40c6.627 0 12 5.373 12 12v24c0 6.627-5.373 12-12 12h-8z', }; export const defaultRotateRangeValus = { - realTimeEvent: true, - min: -360, - max: 360, - value: 0 + realTimeEvent: true, + min: -360, + max: 360, + value: 0, }; export const defaultDrawRangeValus = { - min: 5, - max: 30, - value: 12 + min: 5, + max: 30, + value: 12, }; export const defaultShapeStrokeValus = { - realTimeEvent: true, - min: 2, - max: 300, - value: 3 + realTimeEvent: true, + min: 2, + max: 300, + value: 3, }; export const defaultTextRangeValus = { - realTimeEvent: true, - min: 10, - max: 100, - value: 50 + realTimeEvent: true, + min: 10, + max: 100, + value: 50, }; export const defaultFilterRangeValus = { - tintOpacityRange: { - realTimeEvent: true, - min: 0, - max: 1, - value: 0.7, - useDecimal: true - }, - removewhiteDistanceRange: { - realTimeEvent: true, - min: 0, - max: 1, - value: 0.2, - useDecimal: true - }, - brightnessRange: { - realTimeEvent: true, - min: -1, - max: 1, - value: 0, - useDecimal: true - }, - noiseRange: { - realTimeEvent: true, - min: 0, - max: 1000, - value: 100 - }, - pixelateRange: { - realTimeEvent: true, - min: 2, - max: 20, - value: 4 - }, - colorfilterThresholeRange: { - realTimeEvent: true, - min: 0, - max: 1, - value: 0.2, - useDecimal: true - }, - blurFilterRange: { - value: 0.1 - } + tintOpacityRange: { + realTimeEvent: true, + min: 0, + max: 1, + value: 0.7, + useDecimal: true, + }, + removewhiteDistanceRange: { + realTimeEvent: true, + min: 0, + max: 1, + value: 0.2, + useDecimal: true, + }, + brightnessRange: { + realTimeEvent: true, + min: -1, + max: 1, + value: 0, + useDecimal: true, + }, + noiseRange: { + realTimeEvent: true, + min: 0, + max: 1000, + value: 100, + }, + pixelateRange: { + realTimeEvent: true, + min: 2, + max: 20, + value: 4, + }, + colorfilterThresholeRange: { + realTimeEvent: true, + min: 0, + max: 1, + value: 0.2, + useDecimal: true, + }, + blurFilterRange: { + value: 0.1, + }, }; diff --git a/src/js/drawingMode/cropper.js b/src/js/drawingMode/cropper.js index 35726e919..39c0f686f 100644 --- a/src/js/drawingMode/cropper.js +++ b/src/js/drawingMode/cropper.js @@ -3,7 +3,7 @@ * @fileoverview CropperDrawingMode class */ import DrawingMode from '../interface/drawingMode'; -import {drawingModes, componentNames as components} from '../consts'; +import { drawingModes, componentNames as components } from '../consts'; /** * CropperDrawingMode class @@ -11,29 +11,29 @@ import {drawingModes, componentNames as components} from '../consts'; * @ignore */ class CropperDrawingMode extends DrawingMode { - constructor() { - super(drawingModes.CROPPER); - } + constructor() { + super(drawingModes.CROPPER); + } - /** - * start this drawing mode - * @param {Graphics} graphics - Graphics instance - * @override - */ - start(graphics) { - const cropper = graphics.getComponent(components.CROPPER); - cropper.start(); - } + /** + * start this drawing mode + * @param {Graphics} graphics - Graphics instance + * @override + */ + start(graphics) { + const cropper = graphics.getComponent(components.CROPPER); + cropper.start(); + } - /** - * stop this drawing mode - * @param {Graphics} graphics - Graphics instance - * @override - */ - end(graphics) { - const cropper = graphics.getComponent(components.CROPPER); - cropper.end(); - } + /** + * stop this drawing mode + * @param {Graphics} graphics - Graphics instance + * @override + */ + end(graphics) { + const cropper = graphics.getComponent(components.CROPPER); + cropper.end(); + } } export default CropperDrawingMode; diff --git a/src/js/drawingMode/freeDrawing.js b/src/js/drawingMode/freeDrawing.js index e0b2ae85f..1ef432893 100644 --- a/src/js/drawingMode/freeDrawing.js +++ b/src/js/drawingMode/freeDrawing.js @@ -3,7 +3,7 @@ * @fileoverview FreeDrawingMode class */ import DrawingMode from '../interface/drawingMode'; -import {drawingModes, componentNames as components} from '../consts'; +import { drawingModes, componentNames as components } from '../consts'; /** * FreeDrawingMode class @@ -11,30 +11,30 @@ import {drawingModes, componentNames as components} from '../consts'; * @ignore */ class FreeDrawingMode extends DrawingMode { - constructor() { - super(drawingModes.FREE_DRAWING); - } + constructor() { + super(drawingModes.FREE_DRAWING); + } - /** - * start this drawing mode - * @param {Graphics} graphics - Graphics instance - * @param {{width: ?number, color: ?string}} [options] - Brush width & color - * @override - */ - start(graphics, options) { - const freeDrawing = graphics.getComponent(components.FREE_DRAWING); - freeDrawing.start(options); - } + /** + * start this drawing mode + * @param {Graphics} graphics - Graphics instance + * @param {{width: ?number, color: ?string}} [options] - Brush width & color + * @override + */ + start(graphics, options) { + const freeDrawing = graphics.getComponent(components.FREE_DRAWING); + freeDrawing.start(options); + } - /** - * stop this drawing mode - * @param {Graphics} graphics - Graphics instance - * @override - */ - end(graphics) { - const freeDrawing = graphics.getComponent(components.FREE_DRAWING); - freeDrawing.end(); - } + /** + * stop this drawing mode + * @param {Graphics} graphics - Graphics instance + * @override + */ + end(graphics) { + const freeDrawing = graphics.getComponent(components.FREE_DRAWING); + freeDrawing.end(); + } } export default FreeDrawingMode; diff --git a/src/js/drawingMode/icon.js b/src/js/drawingMode/icon.js index f5e886512..d1c65b9d5 100644 --- a/src/js/drawingMode/icon.js +++ b/src/js/drawingMode/icon.js @@ -3,7 +3,7 @@ * @fileoverview IconDrawingMode class */ import DrawingMode from '../interface/drawingMode'; -import {drawingModes, componentNames as components} from '../consts'; +import { drawingModes, componentNames as components } from '../consts'; /** * IconDrawingMode class @@ -11,29 +11,29 @@ import {drawingModes, componentNames as components} from '../consts'; * @ignore */ class IconDrawingMode extends DrawingMode { - constructor() { - super(drawingModes.ICON); - } + constructor() { + super(drawingModes.ICON); + } - /** - * start this drawing mode - * @param {Graphics} graphics - Graphics instance - * @override - */ - start(graphics) { - const icon = graphics.getComponent(components.ICON); - icon.start(); - } + /** + * start this drawing mode + * @param {Graphics} graphics - Graphics instance + * @override + */ + start(graphics) { + const icon = graphics.getComponent(components.ICON); + icon.start(); + } - /** - * stop this drawing mode - * @param {Graphics} graphics - Graphics instance - * @override - */ - end(graphics) { - const icon = graphics.getComponent(components.ICON); - icon.end(); - } + /** + * stop this drawing mode + * @param {Graphics} graphics - Graphics instance + * @override + */ + end(graphics) { + const icon = graphics.getComponent(components.ICON); + icon.end(); + } } export default IconDrawingMode; diff --git a/src/js/drawingMode/lineDrawing.js b/src/js/drawingMode/lineDrawing.js index e824a9b5a..9d8365848 100644 --- a/src/js/drawingMode/lineDrawing.js +++ b/src/js/drawingMode/lineDrawing.js @@ -3,7 +3,7 @@ * @fileoverview LineDrawingMode class */ import DrawingMode from '../interface/drawingMode'; -import {drawingModes, componentNames as components} from '../consts'; +import { drawingModes, componentNames as components } from '../consts'; /** * LineDrawingMode class @@ -11,30 +11,30 @@ import {drawingModes, componentNames as components} from '../consts'; * @ignore */ class LineDrawingMode extends DrawingMode { - constructor() { - super(drawingModes.LINE_DRAWING); - } + constructor() { + super(drawingModes.LINE_DRAWING); + } - /** - * start this drawing mode - * @param {Graphics} graphics - Graphics instance - * @param {{width: ?number, color: ?string}} [options] - Brush width & color - * @override - */ - start(graphics, options) { - const lineDrawing = graphics.getComponent(components.LINE); - lineDrawing.start(options); - } + /** + * start this drawing mode + * @param {Graphics} graphics - Graphics instance + * @param {{width: ?number, color: ?string}} [options] - Brush width & color + * @override + */ + start(graphics, options) { + const lineDrawing = graphics.getComponent(components.LINE); + lineDrawing.start(options); + } - /** - * stop this drawing mode - * @param {Graphics} graphics - Graphics instance - * @override - */ - end(graphics) { - const lineDrawing = graphics.getComponent(components.LINE); - lineDrawing.end(); - } + /** + * stop this drawing mode + * @param {Graphics} graphics - Graphics instance + * @override + */ + end(graphics) { + const lineDrawing = graphics.getComponent(components.LINE); + lineDrawing.end(); + } } export default LineDrawingMode; diff --git a/src/js/drawingMode/shape.js b/src/js/drawingMode/shape.js index 2f571f668..581589719 100644 --- a/src/js/drawingMode/shape.js +++ b/src/js/drawingMode/shape.js @@ -3,7 +3,7 @@ * @fileoverview ShapeDrawingMode class */ import DrawingMode from '../interface/drawingMode'; -import {drawingModes, componentNames as components} from '../consts'; +import { drawingModes, componentNames as components } from '../consts'; /** * ShapeDrawingMode class @@ -11,29 +11,29 @@ import {drawingModes, componentNames as components} from '../consts'; * @ignore */ class ShapeDrawingMode extends DrawingMode { - constructor() { - super(drawingModes.SHAPE); - } + constructor() { + super(drawingModes.SHAPE); + } - /** - * start this drawing mode - * @param {Graphics} graphics - Graphics instance - * @override - */ - start(graphics) { - const shape = graphics.getComponent(components.SHAPE); - shape.start(); - } + /** + * start this drawing mode + * @param {Graphics} graphics - Graphics instance + * @override + */ + start(graphics) { + const shape = graphics.getComponent(components.SHAPE); + shape.start(); + } - /** - * stop this drawing mode - * @param {Graphics} graphics - Graphics instance - * @override - */ - end(graphics) { - const shape = graphics.getComponent(components.SHAPE); - shape.end(); - } + /** + * stop this drawing mode + * @param {Graphics} graphics - Graphics instance + * @override + */ + end(graphics) { + const shape = graphics.getComponent(components.SHAPE); + shape.end(); + } } export default ShapeDrawingMode; diff --git a/src/js/drawingMode/text.js b/src/js/drawingMode/text.js index c110b05df..dc98fa236 100644 --- a/src/js/drawingMode/text.js +++ b/src/js/drawingMode/text.js @@ -3,7 +3,7 @@ * @fileoverview TextDrawingMode class */ import DrawingMode from '../interface/drawingMode'; -import {drawingModes, componentNames as components} from '../consts'; +import { drawingModes, componentNames as components } from '../consts'; /** * TextDrawingMode class @@ -11,29 +11,29 @@ import {drawingModes, componentNames as components} from '../consts'; * @ignore */ class TextDrawingMode extends DrawingMode { - constructor() { - super(drawingModes.TEXT); - } + constructor() { + super(drawingModes.TEXT); + } - /** - * start this drawing mode - * @param {Graphics} graphics - Graphics instance - * @override - */ - start(graphics) { - const text = graphics.getComponent(components.TEXT); - text.start(); - } + /** + * start this drawing mode + * @param {Graphics} graphics - Graphics instance + * @override + */ + start(graphics) { + const text = graphics.getComponent(components.TEXT); + text.start(); + } - /** - * stop this drawing mode - * @param {Graphics} graphics - Graphics instance - * @override - */ - end(graphics) { - const text = graphics.getComponent(components.TEXT); - text.end(); - } + /** + * stop this drawing mode + * @param {Graphics} graphics - Graphics instance + * @override + */ + end(graphics) { + const text = graphics.getComponent(components.TEXT); + text.end(); + } } export default TextDrawingMode; diff --git a/src/js/extension/arrowLine.js b/src/js/extension/arrowLine.js index 539d84c49..10943fd14 100644 --- a/src/js/extension/arrowLine.js +++ b/src/js/extension/arrowLine.js @@ -9,8 +9,9 @@ const CHEVRON_SIZE_RATIO = 2.7; const TRIANGLE_SIZE_RATIO = 1.7; const RADIAN_CONVERSION_VALUE = 180; -const ArrowLine = fabric.util.createClass(fabric.Line, /** @lends Convolute.prototype */{ - +const ArrowLine = fabric.util.createClass( + fabric.Line, + /** @lends Convolute.prototype */ { /** * Line type * @param {String} type @@ -25,9 +26,9 @@ const ArrowLine = fabric.util.createClass(fabric.Line, /** @lends Convolute.prot * @override */ initialize(points, options = {}) { - this.callSuper('initialize', points, options); + this.callSuper('initialize', points, options); - this.arrowType = options.arrowType; + this.arrowType = options.arrowType; }, /** @@ -36,20 +37,20 @@ const ArrowLine = fabric.util.createClass(fabric.Line, /** @lends Convolute.prot * @override */ _render(ctx) { - const {x1: fromX, y1: fromY, x2: toX, y2: toY} = this.calcLinePoints(); - const linePosition = { - fromX, - fromY, - toX, - toY - }; - this.ctx = ctx; - ctx.lineWidth = this.strokeWidth; - - this._renderBasicLinePath(linePosition); - this._drawDecoratorPath(linePosition); - - this._renderStroke(ctx); + const { x1: fromX, y1: fromY, x2: toX, y2: toY } = this.calcLinePoints(); + const linePosition = { + fromX, + fromY, + toX, + toY, + }; + this.ctx = ctx; + ctx.lineWidth = this.strokeWidth; + + this._renderBasicLinePath(linePosition); + this._drawDecoratorPath(linePosition); + + this._renderStroke(ctx); }, /** @@ -61,10 +62,10 @@ const ArrowLine = fabric.util.createClass(fabric.Line, /** @lends Convolute.prot * @param {number} option.toY - line end position y * @private */ - _renderBasicLinePath({fromX, fromY, toX, toY}) { - this.ctx.beginPath(); - this.ctx.moveTo(fromX, fromY); - this.ctx.lineTo(toX, toY); + _renderBasicLinePath({ fromX, fromY, toX, toY }) { + this.ctx.beginPath(); + this.ctx.moveTo(fromX, fromY); + this.ctx.lineTo(toX, toY); }, /** @@ -77,8 +78,8 @@ const ArrowLine = fabric.util.createClass(fabric.Line, /** @lends Convolute.prot * @private */ _drawDecoratorPath(linePosition) { - this._drawDecoratorPathType('head', linePosition); - this._drawDecoratorPathType('tail', linePosition); + this._drawDecoratorPathType('head', linePosition); + this._drawDecoratorPathType('tail', linePosition); }, /** @@ -92,16 +93,16 @@ const ArrowLine = fabric.util.createClass(fabric.Line, /** @lends Convolute.prot * @private */ _drawDecoratorPathType(type, linePosition) { - switch (this.arrowType[type]) { - case 'triangle': - this._drawTrianglePath(type, linePosition); - break; - case 'chevron': - this._drawChevronPath(type, linePosition); - break; - default: - break; - } + switch (this.arrowType[type]) { + case 'triangle': + this._drawTrianglePath(type, linePosition); + break; + case 'chevron': + this._drawChevronPath(type, linePosition); + break; + default: + break; + } }, /** @@ -115,10 +116,10 @@ const ArrowLine = fabric.util.createClass(fabric.Line, /** @lends Convolute.prot * @private */ _drawTrianglePath(type, linePosition) { - const decorateSize = this.ctx.lineWidth * TRIANGLE_SIZE_RATIO; + const decorateSize = this.ctx.lineWidth * TRIANGLE_SIZE_RATIO; - this._drawChevronPath(type, linePosition, decorateSize); - this.ctx.closePath(); + this._drawChevronPath(type, linePosition, decorateSize); + this.ctx.closePath(); }, /** @@ -132,24 +133,27 @@ const ArrowLine = fabric.util.createClass(fabric.Line, /** @lends Convolute.prot * @param {number} decorateSize - decorate size * @private */ - _drawChevronPath(type, {fromX, fromY, toX, toY}, decorateSize) { - const {ctx} = this; - if (!decorateSize) { - decorateSize = this.ctx.lineWidth * CHEVRON_SIZE_RATIO; - } - - const [standardX, standardY] = type === 'head' ? [fromX, fromY] : [toX, toY]; - const [compareX, compareY] = type === 'head' ? [toX, toY] : [fromX, fromY]; - - const angle = Math.atan2(compareY - standardY, compareX - standardX) * RADIAN_CONVERSION_VALUE / Math.PI; - const rotatedPosition = changeAngle => this.getRotatePosition(decorateSize, changeAngle, { - x: standardX, - y: standardY + _drawChevronPath(type, { fromX, fromY, toX, toY }, decorateSize) { + const { ctx } = this; + if (!decorateSize) { + decorateSize = this.ctx.lineWidth * CHEVRON_SIZE_RATIO; + } + + const [standardX, standardY] = type === 'head' ? [fromX, fromY] : [toX, toY]; + const [compareX, compareY] = type === 'head' ? [toX, toY] : [fromX, fromY]; + + const angle = + (Math.atan2(compareY - standardY, compareX - standardX) * RADIAN_CONVERSION_VALUE) / + Math.PI; + const rotatedPosition = (changeAngle) => + this.getRotatePosition(decorateSize, changeAngle, { + x: standardX, + y: standardY, }); - ctx.moveTo(...rotatedPosition(angle + ARROW_ANGLE)); - ctx.lineTo(standardX, standardY); - ctx.lineTo(...rotatedPosition(angle - ARROW_ANGLE)); + ctx.moveTo(...rotatedPosition(angle + ARROW_ANGLE)); + ctx.lineTo(standardX, standardY); + ctx.lineTo(...rotatedPosition(angle - ARROW_ANGLE)); }, /** @@ -161,14 +165,12 @@ const ArrowLine = fabric.util.createClass(fabric.Line, /** @lends Convolute.prot * @private */ getRotatePosition(distance, angle, referencePosition) { - const radian = angle * Math.PI / RADIAN_CONVERSION_VALUE; - const {x, y} = referencePosition; - - return [ - (distance * Math.cos(radian)) + x, - (distance * Math.sin(radian)) + y - ]; - } -}); + const radian = (angle * Math.PI) / RADIAN_CONVERSION_VALUE; + const { x, y } = referencePosition; + + return [distance * Math.cos(radian) + x, distance * Math.sin(radian) + y]; + }, + } +); export default ArrowLine; diff --git a/src/js/extension/blur.js b/src/js/extension/blur.js index a591d0b25..0207e51dc 100644 --- a/src/js/extension/blur.js +++ b/src/js/extension/blur.js @@ -10,7 +10,9 @@ import fabric from 'fabric'; * @extends {fabric.Image.filters.Convolute} * @ignore */ -const Blur = fabric.util.createClass(fabric.Image.filters.Convolute, /** @lends Convolute.prototype */{ +const Blur = fabric.util.createClass( + fabric.Image.filters.Convolute, + /** @lends Convolute.prototype */ { /** * Filter type * @param {String} type @@ -23,12 +25,9 @@ const Blur = fabric.util.createClass(fabric.Image.filters.Convolute, /** @lends * @override */ initialize() { - this.matrix = [ - 1 / 9, 1 / 9, 1 / 9, - 1 / 9, 1 / 9, 1 / 9, - 1 / 9, 1 / 9, 1 / 9 - ]; - } -}); + this.matrix = [1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9]; + }, + } +); export default Blur; diff --git a/src/js/extension/colorFilter.js b/src/js/extension/colorFilter.js index 5d999aaa0..447d9157e 100644 --- a/src/js/extension/colorFilter.js +++ b/src/js/extension/colorFilter.js @@ -10,7 +10,9 @@ import fabric from 'fabric'; * @extends {fabric.Image.filters.BaseFilter} * @ignore */ -const ColorFilter = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends BaseFilter.prototype */{ +const ColorFilter = fabric.util.createClass( + fabric.Image.filters.BaseFilter, + /** @lends BaseFilter.prototype */ { /** * Filter type * @param {String} type @@ -27,41 +29,44 @@ const ColorFilter = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** * @override */ initialize(options) { - if (!options) { - options = {}; - } - this.color = options.color || '#FFFFFF'; - this.threshold = options.threshold || 45; - this.x = options.x || null; - this.y = options.y || null; + if (!options) { + options = {}; + } + this.color = options.color || '#FFFFFF'; + this.threshold = options.threshold || 45; + this.x = options.x || null; + this.y = options.y || null; }, /** * Applies filter to canvas element * @param {Object} canvas Canvas object passed by fabric */ - applyTo(canvas) { // eslint-disable-line - const {canvasEl} = canvas; - const context = canvasEl.getContext('2d'); - const imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height); - const {data} = imageData; - const {threshold} = this; - let filterColor = fabric.Color.sourceFromHex(this.color); - let i, len; + applyTo(canvas) { + // eslint-disable-line + const { canvasEl } = canvas; + const context = canvasEl.getContext('2d'); + const imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height); + const { data } = imageData; + const { threshold } = this; + let filterColor = fabric.Color.sourceFromHex(this.color); + let i, len; - if (this.x && this.y) { - filterColor = this._getColor(imageData, this.x, this.y); - } + if (this.x && this.y) { + filterColor = this._getColor(imageData, this.x, this.y); + } - for (i = 0, len = data.length; i < len; i += 4) { - if (this._isOutsideThreshold(data[i], filterColor[0], threshold) - || this._isOutsideThreshold(data[i + 1], filterColor[1], threshold) - || this._isOutsideThreshold(data[i + 2], filterColor[2], threshold)) { - continue; - } - data[i] = data[i + 1] = data[i + 2] = data[i + 3] = 0; + for (i = 0, len = data.length; i < len; i += 4) { + if ( + this._isOutsideThreshold(data[i], filterColor[0], threshold) || + this._isOutsideThreshold(data[i + 1], filterColor[1], threshold) || + this._isOutsideThreshold(data[i + 2], filterColor[2], threshold) + ) { + continue; } - context.putImageData(imageData, 0, 0); + data[i] = data[i + 1] = data[i + 2] = data[i + 3] = 0; + } + context.putImageData(imageData, 0, 0); }, /** @@ -72,9 +77,9 @@ const ColorFilter = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** * @returns {boolean} true if within threshold or false */ _isOutsideThreshold(color1, color2, threshold) { - const diff = color1 - color2; + const diff = color1 - color2; - return Math.abs(diff) > threshold; + return Math.abs(diff) > threshold; }, /** @@ -85,18 +90,19 @@ const ColorFilter = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** * @returns {Array} color array */ _getColor(imageData, x, y) { - const color = [0, 0, 0, 0]; - const {data, width} = imageData; - const bytes = 4; - const position = ((width * y) + x) * bytes; + const color = [0, 0, 0, 0]; + const { data, width } = imageData; + const bytes = 4; + const position = (width * y + x) * bytes; - color[0] = data[position]; - color[1] = data[position + 1]; - color[2] = data[position + 2]; - color[3] = data[position + 3]; + color[0] = data[position]; + color[1] = data[position + 1]; + color[2] = data[position + 2]; + color[3] = data[position + 3]; - return color; - } -}); + return color; + }, + } +); export default ColorFilter; diff --git a/src/js/extension/cropzone.js b/src/js/extension/cropzone.js index bb1010ef8..e51bc2d30 100644 --- a/src/js/extension/cropzone.js +++ b/src/js/extension/cropzone.js @@ -4,8 +4,8 @@ */ import snippet from 'tui-code-snippet'; import fabric from 'fabric'; -import {clamp} from '../util'; -import {eventNames as events} from '../consts'; +import { clamp } from '../util'; +import { eventNames as events } from '../consts'; const CORNER_TYPE_TOP_LEFT = 'tl'; const CORNER_TYPE_TOP_RIGHT = 'tr'; @@ -16,14 +16,14 @@ const CORNER_TYPE_MIDDLE_BOTTOM = 'mb'; const CORNER_TYPE_BOTTOM_LEFT = 'bl'; const CORNER_TYPE_BOTTOM_RIGHT = 'br'; const CORNER_TYPE_LIST = [ - CORNER_TYPE_TOP_LEFT, - CORNER_TYPE_TOP_RIGHT, - CORNER_TYPE_MIDDLE_TOP, - CORNER_TYPE_MIDDLE_LEFT, - CORNER_TYPE_MIDDLE_RIGHT, - CORNER_TYPE_MIDDLE_BOTTOM, - CORNER_TYPE_BOTTOM_LEFT, - CORNER_TYPE_BOTTOM_RIGHT + CORNER_TYPE_TOP_LEFT, + CORNER_TYPE_TOP_RIGHT, + CORNER_TYPE_MIDDLE_TOP, + CORNER_TYPE_MIDDLE_LEFT, + CORNER_TYPE_MIDDLE_RIGHT, + CORNER_TYPE_MIDDLE_BOTTOM, + CORNER_TYPE_BOTTOM_LEFT, + CORNER_TYPE_BOTTOM_RIGHT, ]; const NOOP_FUNCTION = () => {}; @@ -34,7 +34,7 @@ const NOOP_FUNCTION = () => {}; * @private */ function cornerTypeValid(selectedCorner) { - return CORNER_TYPE_LIST.indexOf(selectedCorner) >= 0; + return CORNER_TYPE_LIST.indexOf(selectedCorner) >= 0; } /** @@ -45,7 +45,7 @@ function cornerTypeValid(selectedCorner) { * @private */ function getScaleBasis(diffX, diffY) { - return diffX > diffY ? 'width' : 'height'; + return diffX > diffY ? 'width' : 'height'; } /** @@ -56,84 +56,86 @@ function getScaleBasis(diffX, diffY) { * @extends {fabric.Rect} * @ignore */ -const Cropzone = fabric.util.createClass(fabric.Rect, /** @lends Cropzone.prototype */{ +const Cropzone = fabric.util.createClass( + fabric.Rect, + /** @lends Cropzone.prototype */ { /** * Constructor * @param {Object} canvas canvas * @param {Object} options Options object - * @param {Object} extendsOptions object for extends "options" + * @param {Object} extendsOptions object for extends "options" * @override */ initialize(canvas, options, extendsOptions) { - options = snippet.extend(options, extendsOptions); - options.type = 'cropzone'; + options = snippet.extend(options, extendsOptions); + options.type = 'cropzone'; - this.callSuper('initialize', options); - this._addEventHandler(); + this.callSuper('initialize', options); + this._addEventHandler(); - this.canvas = canvas; - this.options = options; + this.canvas = canvas; + this.options = options; }, canvasEventDelegation(eventName) { - let delegationState = 'unregisted'; - const isRegisted = this.canvasEventTrigger[eventName] !== NOOP_FUNCTION; - if (isRegisted) { - delegationState = 'registed'; - } else if ([events.OBJECT_MOVED, events.OBJECT_SCALED].indexOf(eventName) < 0) { - delegationState = 'none'; - } - - return delegationState; + let delegationState = 'unregisted'; + const isRegisted = this.canvasEventTrigger[eventName] !== NOOP_FUNCTION; + if (isRegisted) { + delegationState = 'registed'; + } else if ([events.OBJECT_MOVED, events.OBJECT_SCALED].indexOf(eventName) < 0) { + delegationState = 'none'; + } + + return delegationState; }, canvasEventRegister(eventName, eventTrigger) { - this.canvasEventTrigger[eventName] = eventTrigger; + this.canvasEventTrigger[eventName] = eventTrigger; }, _addEventHandler() { - this.canvasEventTrigger = { - [events.OBJECT_MOVED]: NOOP_FUNCTION, - [events.OBJECT_SCALED]: NOOP_FUNCTION - }; - this.on({ - 'moving': this._onMoving.bind(this), - 'scaling': this._onScaling.bind(this) - }); + this.canvasEventTrigger = { + [events.OBJECT_MOVED]: NOOP_FUNCTION, + [events.OBJECT_SCALED]: NOOP_FUNCTION, + }; + this.on({ + moving: this._onMoving.bind(this), + scaling: this._onScaling.bind(this), + }); }, _renderCropzone(ctx) { - const cropzoneDashLineWidth = 7; - const cropzoneDashLineOffset = 7; - - // Calc original scale - const originalFlipX = this.flipX ? -1 : 1; - const originalFlipY = this.flipY ? -1 : 1; - const originalScaleX = originalFlipX / this.scaleX; - const originalScaleY = originalFlipY / this.scaleY; - - // Set original scale - ctx.scale(originalScaleX, originalScaleY); - - // Render outer rect - this._fillOuterRect(ctx, 'rgba(0, 0, 0, 0.5)'); - - if (this.options.lineWidth) { - this._fillInnerRect(ctx); - this._strokeBorder(ctx, 'rgb(255, 255, 255)', { - lineWidth: this.options.lineWidth - }); - } else { - // Black dash line - this._strokeBorder(ctx, 'rgb(0, 0, 0)', { - lineDashWidth: cropzoneDashLineWidth - }); - - // White dash line - this._strokeBorder(ctx, 'rgb(255, 255, 255)', { - lineDashWidth: cropzoneDashLineWidth, - lineDashOffset: cropzoneDashLineOffset - }); - } - - // Reset scale - ctx.scale(1 / originalScaleX, 1 / originalScaleY); + const cropzoneDashLineWidth = 7; + const cropzoneDashLineOffset = 7; + + // Calc original scale + const originalFlipX = this.flipX ? -1 : 1; + const originalFlipY = this.flipY ? -1 : 1; + const originalScaleX = originalFlipX / this.scaleX; + const originalScaleY = originalFlipY / this.scaleY; + + // Set original scale + ctx.scale(originalScaleX, originalScaleY); + + // Render outer rect + this._fillOuterRect(ctx, 'rgba(0, 0, 0, 0.5)'); + + if (this.options.lineWidth) { + this._fillInnerRect(ctx); + this._strokeBorder(ctx, 'rgb(255, 255, 255)', { + lineWidth: this.options.lineWidth, + }); + } else { + // Black dash line + this._strokeBorder(ctx, 'rgb(0, 0, 0)', { + lineDashWidth: cropzoneDashLineWidth, + }); + + // White dash line + this._strokeBorder(ctx, 'rgb(255, 255, 255)', { + lineDashWidth: cropzoneDashLineWidth, + lineDashOffset: cropzoneDashLineOffset, + }); + } + + // Reset scale + ctx.scale(1 / originalScaleX, 1 / originalScaleY); }, /** @@ -142,9 +144,9 @@ const Cropzone = fabric.util.createClass(fabric.Rect, /** @lends Cropzone.protot * @override */ _render(ctx) { - this.callSuper('_render', ctx); + this.callSuper('_render', ctx); - this._renderCropzone(ctx); + this._renderCropzone(ctx); }, /** @@ -173,31 +175,31 @@ const Cropzone = fabric.util.createClass(fabric.Rect, /** @lends Cropzone.protot * @private */ _fillOuterRect(ctx, fillStyle) { - const {x, y} = this._getCoordinates(); - - ctx.save(); - ctx.fillStyle = fillStyle; - ctx.beginPath(); - - // Outer rectangle - // Numbers are +/-1 so that overlay edges don't get blurry. - ctx.moveTo(x[0] - 1, y[0] - 1); - ctx.lineTo(x[3] + 1, y[0] - 1); - ctx.lineTo(x[3] + 1, y[3] + 1); - ctx.lineTo(x[0] - 1, y[3] + 1); - ctx.lineTo(x[0] - 1, y[0] - 1); - ctx.closePath(); - - // Inner rectangle - ctx.moveTo(x[1], y[1]); - ctx.lineTo(x[1], y[2]); - ctx.lineTo(x[2], y[2]); - ctx.lineTo(x[2], y[1]); - ctx.lineTo(x[1], y[1]); - ctx.closePath(); - - ctx.fill(); - ctx.restore(); + const { x, y } = this._getCoordinates(); + + ctx.save(); + ctx.fillStyle = fillStyle; + ctx.beginPath(); + + // Outer rectangle + // Numbers are +/-1 so that overlay edges don't get blurry. + ctx.moveTo(x[0] - 1, y[0] - 1); + ctx.lineTo(x[3] + 1, y[0] - 1); + ctx.lineTo(x[3] + 1, y[3] + 1); + ctx.lineTo(x[0] - 1, y[3] + 1); + ctx.lineTo(x[0] - 1, y[0] - 1); + ctx.closePath(); + + // Inner rectangle + ctx.moveTo(x[1], y[1]); + ctx.lineTo(x[1], y[2]); + ctx.lineTo(x[2], y[2]); + ctx.lineTo(x[2], y[1]); + ctx.lineTo(x[1], y[1]); + ctx.closePath(); + + ctx.fill(); + ctx.restore(); }, /** @@ -206,30 +208,30 @@ const Cropzone = fabric.util.createClass(fabric.Rect, /** @lends Cropzone.protot * @private */ _fillInnerRect(ctx) { - const {x: outerX, y: outerY} = this._getCoordinates(); - const x = this._caculateInnerPosition(outerX, (outerX[2] - outerX[1]) / 3); - const y = this._caculateInnerPosition(outerY, (outerY[2] - outerY[1]) / 3); + const { x: outerX, y: outerY } = this._getCoordinates(); + const x = this._caculateInnerPosition(outerX, (outerX[2] - outerX[1]) / 3); + const y = this._caculateInnerPosition(outerY, (outerY[2] - outerY[1]) / 3); - ctx.save(); - ctx.strokeStyle = 'rgba(255, 255, 255, 0.7)'; - ctx.lineWidth = this.options.lineWidth; - ctx.beginPath(); + ctx.save(); + ctx.strokeStyle = 'rgba(255, 255, 255, 0.7)'; + ctx.lineWidth = this.options.lineWidth; + ctx.beginPath(); - ctx.moveTo(x[0], y[1]); - ctx.lineTo(x[3], y[1]); + ctx.moveTo(x[0], y[1]); + ctx.lineTo(x[3], y[1]); - ctx.moveTo(x[0], y[2]); - ctx.lineTo(x[3], y[2]); + ctx.moveTo(x[0], y[2]); + ctx.lineTo(x[3], y[2]); - ctx.moveTo(x[1], y[0]); - ctx.lineTo(x[1], y[3]); + ctx.moveTo(x[1], y[0]); + ctx.lineTo(x[1], y[3]); - ctx.moveTo(x[2], y[0]); - ctx.lineTo(x[2], y[3]); - ctx.stroke(); - ctx.closePath(); + ctx.moveTo(x[2], y[0]); + ctx.lineTo(x[2], y[3]); + ctx.stroke(); + ctx.closePath(); - ctx.restore(); + ctx.restore(); }, /** @@ -240,13 +242,13 @@ const Cropzone = fabric.util.createClass(fabric.Rect, /** @lends Cropzone.protot * @private */ _caculateInnerPosition(outer, size) { - const position = []; - position[0] = outer[1]; - position[1] = outer[1] + size; - position[2] = outer[1] + (size * 2); - position[3] = outer[2]; + const position = []; + position[0] = outer[1]; + position[1] = outer[1] + size; + position[2] = outer[1] + size * 2; + position[3] = outer[2]; - return position; + return position; }, /** @@ -255,26 +257,32 @@ const Cropzone = fabric.util.createClass(fabric.Rect, /** @lends Cropzone.protot * @private */ _getCoordinates() { - const {canvas, width, height, left, top} = this; - const halfWidth = width / 2; - const halfHeight = height / 2; - const canvasHeight = canvas.getHeight(); // fabric object - const canvasWidth = canvas.getWidth(); // fabric object - - return { - x: snippet.map([ - -(halfWidth + left), // x0 - -(halfWidth), // x1 - halfWidth, // x2 - halfWidth + (canvasWidth - left - width) // x3 - ], Math.ceil), - y: snippet.map([ - -(halfHeight + top), // y0 - -(halfHeight), // y1 - halfHeight, // y2 - halfHeight + (canvasHeight - top - height) // y3 - ], Math.ceil) - }; + const { canvas, width, height, left, top } = this; + const halfWidth = width / 2; + const halfHeight = height / 2; + const canvasHeight = canvas.getHeight(); // fabric object + const canvasWidth = canvas.getWidth(); // fabric object + + return { + x: snippet.map( + [ + -(halfWidth + left), // x0 + -halfWidth, // x1 + halfWidth, // x2 + halfWidth + (canvasWidth - left - width), // x3 + ], + Math.ceil + ), + y: snippet.map( + [ + -(halfHeight + top), // y0 + -halfHeight, // y1 + halfHeight, // y2 + halfHeight + (canvasHeight - top - height), // y3 + ], + Math.ceil + ), + }; }, /** @@ -286,32 +294,32 @@ const Cropzone = fabric.util.createClass(fabric.Rect, /** @lends Cropzone.protot * @param {number} [lineWidth] - line width * @private */ - _strokeBorder(ctx, strokeStyle, {lineDashWidth, lineDashOffset, lineWidth}) { - const halfWidth = this.width / 2; - const halfHeight = this.height / 2; - - ctx.save(); - ctx.strokeStyle = strokeStyle; - - if (ctx.setLineDash) { - ctx.setLineDash([lineDashWidth, lineDashWidth]); - } - if (lineDashOffset) { - ctx.lineDashOffset = lineDashOffset; - } - if (lineWidth) { - ctx.lineWidth = lineWidth; - } - - ctx.beginPath(); - ctx.moveTo(-halfWidth, -halfHeight); - ctx.lineTo(halfWidth, -halfHeight); - ctx.lineTo(halfWidth, halfHeight); - ctx.lineTo(-halfWidth, halfHeight); - ctx.lineTo(-halfWidth, -halfHeight); - ctx.stroke(); - - ctx.restore(); + _strokeBorder(ctx, strokeStyle, { lineDashWidth, lineDashOffset, lineWidth }) { + const halfWidth = this.width / 2; + const halfHeight = this.height / 2; + + ctx.save(); + ctx.strokeStyle = strokeStyle; + + if (ctx.setLineDash) { + ctx.setLineDash([lineDashWidth, lineDashWidth]); + } + if (lineDashOffset) { + ctx.lineDashOffset = lineDashOffset; + } + if (lineWidth) { + ctx.lineWidth = lineWidth; + } + + ctx.beginPath(); + ctx.moveTo(-halfWidth, -halfHeight); + ctx.lineTo(halfWidth, -halfHeight); + ctx.lineTo(halfWidth, halfHeight); + ctx.lineTo(-halfWidth, halfHeight); + ctx.lineTo(-halfWidth, -halfHeight); + ctx.stroke(); + + ctx.restore(); }, /** @@ -319,14 +327,14 @@ const Cropzone = fabric.util.createClass(fabric.Rect, /** @lends Cropzone.protot * @private */ _onMoving() { - const {height, width, left, top} = this; - const maxLeft = this.canvas.getWidth() - width; - const maxTop = this.canvas.getHeight() - height; + const { height, width, left, top } = this; + const maxLeft = this.canvas.getWidth() - width; + const maxTop = this.canvas.getHeight() - height; - this.left = clamp(left, 0, maxLeft); - this.top = clamp(top, 0, maxTop); + this.left = clamp(left, 0, maxLeft); + this.top = clamp(top, 0, maxTop); - this.canvasEventTrigger[events.OBJECT_MOVED](this); + this.canvasEventTrigger[events.OBJECT_MOVED](this); }, /** @@ -335,15 +343,15 @@ const Cropzone = fabric.util.createClass(fabric.Rect, /** @lends Cropzone.protot * @private */ _onScaling(fEvent) { - const selectedCorner = fEvent.transform.corner; - const pointer = this.canvas.getPointer(fEvent.e); - const settings = this._calcScalingSizeFromPointer(pointer, selectedCorner); + const selectedCorner = fEvent.transform.corner; + const pointer = this.canvas.getPointer(fEvent.e); + const settings = this._calcScalingSizeFromPointer(pointer, selectedCorner); - // On scaling cropzone, - // change real width and height and fix scaleFactor to 1 - this.scale(1).set(settings); + // On scaling cropzone, + // change real width and height and fix scaleFactor to 1 + this.scale(1).set(settings); - this.canvasEventTrigger[events.OBJECT_SCALED](this); + this.canvasEventTrigger[events.OBJECT_SCALED](this); }, /** @@ -354,9 +362,9 @@ const Cropzone = fabric.util.createClass(fabric.Rect, /** @lends Cropzone.protot * @private */ _calcScalingSizeFromPointer(pointer, selectedCorner) { - const isCornerTypeValid = cornerTypeValid(selectedCorner); + const isCornerTypeValid = cornerTypeValid(selectedCorner); - return isCornerTypeValid && this._resizeCropZone(pointer, selectedCorner); + return isCornerTypeValid && this._resizeCropZone(pointer, selectedCorner); }, /** @@ -369,36 +377,36 @@ const Cropzone = fabric.util.createClass(fabric.Rect, /** @lends Cropzone.protot * @returns {{width: number, height: number}} * @private */ - adjustRatioCropzoneSize({width, height, leftMaker, topMaker, maxWidth, maxHeight, scaleTo}) { - width = maxWidth ? clamp(width, 1, maxWidth) : width; - height = maxHeight ? clamp(height, 1, maxHeight) : height; - - if (!this.presetRatio) { - return { - width, - height, - left: leftMaker(width), - top: topMaker(height) - }; - } - - if (scaleTo === 'width') { - height = width / this.presetRatio; - } else { - width = height * this.presetRatio; - } - - const maxScaleFactor = Math.min(maxWidth / width, maxHeight / height); - if (maxScaleFactor <= 1) { - [width, height] = [width, height].map(v => v * maxScaleFactor); - } + adjustRatioCropzoneSize({ width, height, leftMaker, topMaker, maxWidth, maxHeight, scaleTo }) { + width = maxWidth ? clamp(width, 1, maxWidth) : width; + height = maxHeight ? clamp(height, 1, maxHeight) : height; + if (!this.presetRatio) { return { - width, - height, - left: leftMaker(width), - top: topMaker(height) + width, + height, + left: leftMaker(width), + top: topMaker(height), }; + } + + if (scaleTo === 'width') { + height = width / this.presetRatio; + } else { + width = height * this.presetRatio; + } + + const maxScaleFactor = Math.min(maxWidth / width, maxHeight / height); + if (maxScaleFactor <= 1) { + [width, height] = [width, height].map((v) => v * maxScaleFactor); + } + + return { + width, + height, + left: leftMaker(width), + top: topMaker(height), + }; }, /** @@ -407,24 +415,24 @@ const Cropzone = fabric.util.createClass(fabric.Rect, /** @lends Cropzone.protot * @private */ _getCropzoneRectInfo() { - const {width: canvasWidth, height: canvasHeight} = this.canvas; - const { - top: rectTop, - left: rectLeft, - width: rectWidth, - height: rectHeight - } = this.getBoundingRect(false, true); - - return { - rectTop, - rectLeft, - rectWidth, - rectHeight, - rectRight: rectLeft + rectWidth, - rectBottom: rectTop + rectHeight, - canvasWidth, - canvasHeight - }; + const { width: canvasWidth, height: canvasHeight } = this.canvas; + const { + top: rectTop, + left: rectLeft, + width: rectWidth, + height: rectHeight, + } = this.getBoundingRect(false, true); + + return { + rectTop, + rectLeft, + rectWidth, + rectHeight, + rectRight: rectLeft + rectWidth, + rectBottom: rectTop + rectHeight, + canvasWidth, + canvasHeight, + }; }, /** @@ -434,92 +442,94 @@ const Cropzone = fabric.util.createClass(fabric.Rect, /** @lends Cropzone.protot * @returns {{left: number, top: number, width: number, height: number}} * @private */ - _resizeCropZone({x, y}, corner) { - const {rectWidth, - rectHeight, - rectTop, - rectLeft, - rectBottom, - rectRight, - canvasWidth, - canvasHeight} = this._getCropzoneRectInfo(); - - const resizeInfoMap = { - tl: { - width: rectRight - x, - height: rectBottom - y, - leftMaker: newWidth => rectRight - newWidth, - topMaker: newHeight => rectBottom - newHeight, - maxWidth: rectRight, - maxHeight: rectBottom, - scaleTo: getScaleBasis(rectLeft - x, rectTop - y) - }, - tr: { - width: x - rectLeft, - height: rectBottom - y, - leftMaker: () => rectLeft, - topMaker: newHeight => rectBottom - newHeight, - maxWidth: canvasWidth - rectLeft, - maxHeight: rectBottom, - scaleTo: getScaleBasis(x - rectRight, rectTop - y) - }, - mt: { - width: rectWidth, - height: rectBottom - y, - leftMaker: () => rectLeft, - topMaker: newHeight => rectBottom - newHeight, - maxWidth: canvasWidth - rectLeft, - maxHeight: rectBottom, - scaleTo: 'height' - }, - ml: { - width: rectRight - x, - height: rectHeight, - leftMaker: newWidth => rectRight - newWidth, - topMaker: () => rectTop, - maxWidth: rectRight, - maxHeight: canvasHeight - rectTop, - scaleTo: 'width' - }, - mr: { - width: x - rectLeft, - height: rectHeight, - leftMaker: () => rectLeft, - topMaker: () => rectTop, - maxWidth: canvasWidth - rectLeft, - maxHeight: canvasHeight - rectTop, - scaleTo: 'width' - }, - mb: { - width: rectWidth, - height: y - rectTop, - leftMaker: () => rectLeft, - topMaker: () => rectTop, - maxWidth: canvasWidth - rectLeft, - maxHeight: canvasHeight - rectTop, - scaleTo: 'height' - }, - bl: { - width: rectRight - x, - height: y - rectTop, - leftMaker: newWidth => rectRight - newWidth, - topMaker: () => rectTop, - maxWidth: rectRight, - maxHeight: canvasHeight - rectTop, - scaleTo: getScaleBasis(rectLeft - x, y - rectBottom) - }, - br: { - width: x - rectLeft, - height: y - rectTop, - leftMaker: () => rectLeft, - topMaker: () => rectTop, - maxWidth: canvasWidth - rectLeft, - maxHeight: canvasHeight - rectTop, - scaleTo: getScaleBasis(x - rectRight, y - rectBottom) - } - }; - - return this.adjustRatioCropzoneSize(resizeInfoMap[corner]); + _resizeCropZone({ x, y }, corner) { + const { + rectWidth, + rectHeight, + rectTop, + rectLeft, + rectBottom, + rectRight, + canvasWidth, + canvasHeight, + } = this._getCropzoneRectInfo(); + + const resizeInfoMap = { + tl: { + width: rectRight - x, + height: rectBottom - y, + leftMaker: (newWidth) => rectRight - newWidth, + topMaker: (newHeight) => rectBottom - newHeight, + maxWidth: rectRight, + maxHeight: rectBottom, + scaleTo: getScaleBasis(rectLeft - x, rectTop - y), + }, + tr: { + width: x - rectLeft, + height: rectBottom - y, + leftMaker: () => rectLeft, + topMaker: (newHeight) => rectBottom - newHeight, + maxWidth: canvasWidth - rectLeft, + maxHeight: rectBottom, + scaleTo: getScaleBasis(x - rectRight, rectTop - y), + }, + mt: { + width: rectWidth, + height: rectBottom - y, + leftMaker: () => rectLeft, + topMaker: (newHeight) => rectBottom - newHeight, + maxWidth: canvasWidth - rectLeft, + maxHeight: rectBottom, + scaleTo: 'height', + }, + ml: { + width: rectRight - x, + height: rectHeight, + leftMaker: (newWidth) => rectRight - newWidth, + topMaker: () => rectTop, + maxWidth: rectRight, + maxHeight: canvasHeight - rectTop, + scaleTo: 'width', + }, + mr: { + width: x - rectLeft, + height: rectHeight, + leftMaker: () => rectLeft, + topMaker: () => rectTop, + maxWidth: canvasWidth - rectLeft, + maxHeight: canvasHeight - rectTop, + scaleTo: 'width', + }, + mb: { + width: rectWidth, + height: y - rectTop, + leftMaker: () => rectLeft, + topMaker: () => rectTop, + maxWidth: canvasWidth - rectLeft, + maxHeight: canvasHeight - rectTop, + scaleTo: 'height', + }, + bl: { + width: rectRight - x, + height: y - rectTop, + leftMaker: (newWidth) => rectRight - newWidth, + topMaker: () => rectTop, + maxWidth: rectRight, + maxHeight: canvasHeight - rectTop, + scaleTo: getScaleBasis(rectLeft - x, y - rectBottom), + }, + br: { + width: x - rectLeft, + height: y - rectTop, + leftMaker: () => rectLeft, + topMaker: () => rectTop, + maxWidth: canvasWidth - rectLeft, + maxHeight: canvasHeight - rectTop, + scaleTo: getScaleBasis(x - rectRight, y - rectBottom), + }, + }; + + return this.adjustRatioCropzoneSize(resizeInfoMap[corner]); }, /** @@ -527,13 +537,9 @@ const Cropzone = fabric.util.createClass(fabric.Rect, /** @lends Cropzone.protot * @returns {boolean} */ isValid() { - return ( - this.left >= 0 && - this.top >= 0 && - this.width > 0 && - this.height > 0 - ); - } -}); + return this.left >= 0 && this.top >= 0 && this.width > 0 && this.height > 0; + }, + } +); export default Cropzone; diff --git a/src/js/extension/emboss.js b/src/js/extension/emboss.js index 24e51a4a7..7d3ee0d6e 100644 --- a/src/js/extension/emboss.js +++ b/src/js/extension/emboss.js @@ -10,7 +10,9 @@ import fabric from 'fabric'; * @extends {fabric.Image.filters.Convolute} * @ignore */ -const Emboss = fabric.util.createClass(fabric.Image.filters.Convolute, /** @lends Convolute.prototype */{ +const Emboss = fabric.util.createClass( + fabric.Image.filters.Convolute, + /** @lends Convolute.prototype */ { /** * Filter type * @param {String} type @@ -23,13 +25,10 @@ const Emboss = fabric.util.createClass(fabric.Image.filters.Convolute, /** @lend * @override */ initialize() { - const matrix = [ - 1, 1, 1, - 1, 0.7, -1, - -1, -1, -1 - ]; - this.matrix = matrix; - } -}); + const matrix = [1, 1, 1, 1, 0.7, -1, -1, -1, -1]; + this.matrix = matrix; + }, + } +); export default Emboss; diff --git a/src/js/extension/mask.js b/src/js/extension/mask.js index 0623fb7f5..1ce80b15f 100644 --- a/src/js/extension/mask.js +++ b/src/js/extension/mask.js @@ -10,28 +10,30 @@ import fabric from 'fabric'; * @extends {fabric.Image.filters.BlendImage} * @ignore */ -const Mask = fabric.util.createClass(fabric.Image.filters.BlendImage, /** @lends Mask.prototype */{ +const Mask = fabric.util.createClass( + fabric.Image.filters.BlendImage, + /** @lends Mask.prototype */ { /** * Apply filter to canvas element * @param {Object} pipelineState - Canvas element to apply filter * @override */ applyTo(pipelineState) { - if (!this.mask) { - return; - } + if (!this.mask) { + return; + } - const canvas = pipelineState.canvasEl; - const {width, height} = canvas; - const maskCanvasEl = this._createCanvasOfMask(width, height); - const ctx = canvas.getContext('2d'); - const maskCtx = maskCanvasEl.getContext('2d'); - const imageData = ctx.getImageData(0, 0, width, height); + const canvas = pipelineState.canvasEl; + const { width, height } = canvas; + const maskCanvasEl = this._createCanvasOfMask(width, height); + const ctx = canvas.getContext('2d'); + const maskCtx = maskCanvasEl.getContext('2d'); + const imageData = ctx.getImageData(0, 0, width, height); - this._drawMask(maskCtx, canvas, ctx); - this._mapData(maskCtx, imageData, width, height); + this._drawMask(maskCtx, canvas, ctx); + this._mapData(maskCtx, imageData, width, height); - pipelineState.imageData = imageData; + pipelineState.imageData = imageData; }, /** @@ -42,12 +44,12 @@ const Mask = fabric.util.createClass(fabric.Image.filters.BlendImage, /** @lends * @private */ _createCanvasOfMask(width, height) { - const maskCanvasEl = fabric.util.createCanvasElement(); + const maskCanvasEl = fabric.util.createCanvasElement(); - maskCanvasEl.width = width; - maskCanvasEl.height = height; + maskCanvasEl.width = width; + maskCanvasEl.height = height; - return maskCanvasEl; + return maskCanvasEl; }, /** @@ -56,16 +58,16 @@ const Mask = fabric.util.createClass(fabric.Image.filters.BlendImage, /** @lends * @private */ _drawMask(maskCtx) { - const {mask} = this; - const maskImg = mask.getElement(); - const {angle, left, scaleX, scaleY, top} = mask; + const { mask } = this; + const maskImg = mask.getElement(); + const { angle, left, scaleX, scaleY, top } = mask; - maskCtx.save(); - maskCtx.translate(left, top); - maskCtx.rotate(angle * Math.PI / 180); - maskCtx.scale(scaleX, scaleY); - maskCtx.drawImage(maskImg, -maskImg.width / 2, -maskImg.height / 2); - maskCtx.restore(); + maskCtx.save(); + maskCtx.translate(left, top); + maskCtx.rotate((angle * Math.PI) / 180); + maskCtx.scale(scaleX, scaleY); + maskCtx.drawImage(maskImg, -maskImg.width / 2, -maskImg.height / 2); + maskCtx.restore(); }, /** @@ -77,15 +79,16 @@ const Mask = fabric.util.createClass(fabric.Image.filters.BlendImage, /** @lends * @private */ _mapData(maskCtx, imageData, width, height) { - const {data, height: imgHeight, width: imgWidth} = imageData; - const sourceData = data; - const len = imgWidth * imgHeight * 4; - const maskData = maskCtx.getImageData(0, 0, width, height).data; + const { data, height: imgHeight, width: imgWidth } = imageData; + const sourceData = data; + const len = imgWidth * imgHeight * 4; + const maskData = maskCtx.getImageData(0, 0, width, height).data; - for (let i = 0; i < len; i += 4) { - sourceData[i + 3] = maskData[i]; // adjust value of alpha data - } - } -}); + for (let i = 0; i < len; i += 4) { + sourceData[i + 3] = maskData[i]; // adjust value of alpha data + } + }, + } +); export default Mask; diff --git a/src/js/extension/sharpen.js b/src/js/extension/sharpen.js index e6d3598cd..eb07af313 100644 --- a/src/js/extension/sharpen.js +++ b/src/js/extension/sharpen.js @@ -10,7 +10,9 @@ import fabric from 'fabric'; * @extends {fabric.Image.filters.Convolute} * @ignore */ -const Sharpen = fabric.util.createClass(fabric.Image.filters.Convolute, /** @lends Convolute.prototype */{ +const Sharpen = fabric.util.createClass( + fabric.Image.filters.Convolute, + /** @lends Convolute.prototype */ { /** * Filter type * @param {String} type @@ -23,13 +25,10 @@ const Sharpen = fabric.util.createClass(fabric.Image.filters.Convolute, /** @len * @override */ initialize() { - const matrix = [ - 0, -1, 0, - -1, 5, -1, - 0, -1, 0 - ]; - this.matrix = matrix; - } -}); + const matrix = [0, -1, 0, -1, 5, -1, 0, -1, 0]; + this.matrix = matrix; + }, + } +); export default Sharpen; diff --git a/src/js/factory/command.js b/src/js/factory/command.js index 6023adfe7..e7c77cc2f 100644 --- a/src/js/factory/command.js +++ b/src/js/factory/command.js @@ -14,12 +14,12 @@ const commands = {}; * @ignore */ function create(name, ...args) { - const actions = commands[name]; - if (actions) { - return new Command(actions, args); - } + const actions = commands[name]; + if (actions) { + return new Command(actions, args); + } - return null; + return null; } /** @@ -31,10 +31,10 @@ function create(name, ...args) { * @ignore */ function register(command) { - commands[command.name] = command; + commands[command.name] = command; } export default { - create, - register + create, + register, }; diff --git a/src/js/factory/errorMessage.js b/src/js/factory/errorMessage.js index bbf188a9f..8be092098 100644 --- a/src/js/factory/errorMessage.js +++ b/src/js/factory/errorMessage.js @@ -3,32 +3,29 @@ * @fileoverview Error-message factory */ import snippet from 'tui-code-snippet'; -import {keyMirror} from '../util'; +import { keyMirror } from '../util'; -const types = keyMirror( - 'UN_IMPLEMENTATION', - 'NO_COMPONENT_NAME' -); +const types = keyMirror('UN_IMPLEMENTATION', 'NO_COMPONENT_NAME'); const messages = { - UN_IMPLEMENTATION: 'Should implement a method: ', - NO_COMPONENT_NAME: 'Should set a component name' + UN_IMPLEMENTATION: 'Should implement a method: ', + NO_COMPONENT_NAME: 'Should set a component name', }; const map = { - UN_IMPLEMENTATION(methodName) { - return messages.UN_IMPLEMENTATION + methodName; - }, - NO_COMPONENT_NAME() { - return messages.NO_COMPONENT_NAME; - } + UN_IMPLEMENTATION(methodName) { + return messages.UN_IMPLEMENTATION + methodName; + }, + NO_COMPONENT_NAME() { + return messages.NO_COMPONENT_NAME; + }, }; export default { - types: snippet.extend({}, types), + types: snippet.extend({}, types), - create(type, ...args) { - type = type.toLowerCase(); - const func = map[type]; + create(type, ...args) { + type = type.toLowerCase(); + const func = map[type]; - return func(...args); - } + return func(...args); + }, }; diff --git a/src/js/graphics.js b/src/js/graphics.js index c425c0f3e..9a80257f0 100644 --- a/src/js/graphics.js +++ b/src/js/graphics.js @@ -20,24 +20,37 @@ import LineDrawingMode from './drawingMode/lineDrawing'; import ShapeDrawingMode from './drawingMode/shape'; import TextDrawingMode from './drawingMode/text'; import IconDrawingMode from './drawingMode/icon'; -import {getProperties, includes, isShape, Promise} from './util'; -import {componentNames as components, eventNames as events, drawingModes, fObjectOptions} from './consts'; +import { getProperties, includes, isShape, Promise } from './util'; import { - makeSelectionUndoData, - makeSelectionUndoDatum, - setCachedUndoDataForDimension + componentNames as components, + eventNames as events, + drawingModes, + fObjectOptions, +} from './consts'; +import { + makeSelectionUndoData, + makeSelectionUndoDatum, + setCachedUndoDataForDimension, } from './helper/selectionModifyHelper'; -const {extend, stamp, isArray, isString, forEachArray, forEachOwnProperties, CustomEvents} = snippet; +const { + extend, + stamp, + isArray, + isString, + forEachArray, + forEachOwnProperties, + CustomEvents, +} = snippet; const DEFAULT_CSS_MAX_WIDTH = 1000; const DEFAULT_CSS_MAX_HEIGHT = 800; const EXTRA_PX_FOR_PASTE = 10; const cssOnly = { - cssOnly: true + cssOnly: true, }; const backstoreOnly = { - backstoreOnly: true + backstoreOnly: true, }; /** @@ -50,1311 +63,1335 @@ const backstoreOnly = { * @ignore */ class Graphics { - constructor(element, { - cssMaxWidth, - cssMaxHeight - } = {}) { - /** - * Fabric image instance - * @type {fabric.Image} - */ - this.canvasImage = null; - - /** - * Max width of canvas elements - * @type {number} - */ - this.cssMaxWidth = cssMaxWidth || DEFAULT_CSS_MAX_WIDTH; - - /** - * Max height of canvas elements - * @type {number} - */ - this.cssMaxHeight = cssMaxHeight || DEFAULT_CSS_MAX_HEIGHT; - - /** - * cropper Selection Style - * @type {Object} - */ - this.cropSelectionStyle = {}; - - /** - * target fabric object for copy paste feature - * @type {fabric.Object} - * @private - */ - this.targetObjectForCopyPaste = null; - - /** - * Image name - * @type {string} - */ - this.imageName = ''; - - /** - * Object Map - * @type {Object} - * @private - */ - this._objects = {}; - - /** - * Fabric-Canvas instance - * @type {fabric.Canvas} - * @private - */ - this._canvas = null; - - /** - * Drawing mode - * @type {string} - * @private - */ - this._drawingMode = drawingModes.NORMAL; - - /** - * DrawingMode map - * @type {Object.} - * @private - */ - this._drawingModeMap = {}; - - /** - * Component map - * @type {Object.} - * @private - */ - this._componentMap = {}; - - /** - * fabric event handlers - * @type {Object.} - * @private - */ - this._handler = { - onMouseDown: this._onMouseDown.bind(this), - onObjectAdded: this._onObjectAdded.bind(this), - onObjectRemoved: this._onObjectRemoved.bind(this), - onObjectMoved: this._onObjectMoved.bind(this), - onObjectScaled: this._onObjectScaled.bind(this), - onObjectModified: this._onObjectModified.bind(this), - onObjectRotated: this._onObjectRotated.bind(this), - onObjectSelected: this._onObjectSelected.bind(this), - onPathCreated: this._onPathCreated.bind(this), - onSelectionCleared: this._onSelectionCleared.bind(this), - onSelectionCreated: this._onSelectionCreated.bind(this) - }; - - this._setObjectCachingToFalse(); - this._setCanvasElement(element); - this._createDrawingModeInstances(); - this._createComponents(); - this._attachCanvasEvents(); - } - - /** - * Destroy canvas element - */ - destroy() { - const {wrapperEl} = this._canvas; - - this._canvas.clear(); - - wrapperEl.parentNode.removeChild(wrapperEl); - } - - /** - * Deactivates all objects on canvas - * @returns {Graphics} this - */ - deactivateAll() { - this._canvas.discardActiveObject(); - - return this; - } - - /** - * Renders all objects on canvas - * @returns {Graphics} this - */ - renderAll() { - this._canvas.renderAll(); - - return this; - } - - /** - * Adds objects on canvas - * @param {Object|Array} objects - objects - */ - add(objects) { - let theArgs = []; - if (isArray(objects)) { - theArgs = objects; - } else { - theArgs.push(objects); - } - - this._canvas.add(...theArgs); - } - - /** - * Removes the object or group - * @param {Object} target - graphics object or group - * @returns {boolean} true if contains or false - */ - contains(target) { - return this._canvas.contains(target); - } - - /** - * Gets all objects or group - * @returns {Array} all objects, shallow copy - */ - getObjects() { - return this._canvas.getObjects().slice(); - } - - /** - * Get an object by id - * @param {number} id - object id - * @returns {fabric.Object} object corresponding id - */ - getObject(id) { - return this._objects[id]; - } - - /** - * Removes the object or group - * @param {Object} target - graphics object or group - */ - remove(target) { - this._canvas.remove(target); - } - + constructor(element, { cssMaxWidth, cssMaxHeight } = {}) { /** - * Removes all object or group - * @param {boolean} includesBackground - remove the background image or not - * @returns {Array} all objects array which is removed + * Fabric image instance + * @type {fabric.Image} */ - removeAll(includesBackground) { - const canvas = this._canvas; - const objects = canvas.getObjects().slice(); - canvas.remove(...this._canvas.getObjects()); - - if (includesBackground) { - canvas.clear(); - } - - return objects; - } + this.canvasImage = null; /** - * Removes an object or group by id - * @param {number} id - object id - * @returns {Array} removed objects + * Max width of canvas elements + * @type {number} */ - removeObjectById(id) { - const objects = []; - const canvas = this._canvas; - const target = this.getObject(id); - const isValidGroup = target && target.isType('group') && !target.isEmpty(); - - if (isValidGroup) { - canvas.discardActiveObject(); // restore states for each objects - target.forEachObject(obj => { - objects.push(obj); - canvas.remove(obj); - }); - } else if (canvas.contains(target)) { - objects.push(target); - canvas.remove(target); - } - - return objects; - } + this.cssMaxWidth = cssMaxWidth || DEFAULT_CSS_MAX_WIDTH; /** - * Get an id by object instance - * @param {fabric.Object} object object - * @returns {number} object id if it exists or null + * Max height of canvas elements + * @type {number} */ - getObjectId(object) { - let key = null; - for (key in this._objects) { - if (this._objects.hasOwnProperty(key)) { - if (object === this._objects[key]) { - return key; - } - } - } - - return null; - } + this.cssMaxHeight = cssMaxHeight || DEFAULT_CSS_MAX_HEIGHT; /** - * Gets an active object or group - * @returns {Object} active object or group instance + * cropper Selection Style + * @type {Object} */ - getActiveObject() { - return this._canvas._activeObject; - } + this.cropSelectionStyle = {}; /** - * Returns the object ID to delete the object. - * @returns {number} object id for remove - */ - getActiveObjectIdForRemove() { - const activeObject = this.getActiveObject(); - const {type, left, top} = activeObject; - const isSelection = type === 'activeSelection'; - - if (isSelection) { - const group = new fabric.Group([...activeObject.getObjects()], { - left, - top - }); - - return this._addFabricObject(group); - } - - return this.getObjectId(activeObject); - } - - /** - * Verify that you are ready to erase the object. - * @returns {boolean} ready for object remove - */ - isReadyRemoveObject() { - const activeObject = this.getActiveObject(); - - return activeObject && !activeObject.isEditing; - } - - /** - * Gets an active group object - * @returns {Object} active group object instance - */ - getActiveObjects() { - const activeObject = this._canvas._activeObject; - - return activeObject && activeObject.type === 'activeSelection' ? activeObject : null; - } - - /** - * Get Active object Selection from object ids - * @param {Array.} objects - fabric objects - * @returns {Object} target - target object group - */ - getActiveSelectionFromObjects(objects) { - const canvas = this.getCanvas(); - - return new fabric.ActiveSelection(objects, {canvas}); - } - - /** - * Activates an object or group - * @param {Object} target - target object or group - */ - setActiveObject(target) { - this._canvas.setActiveObject(target); - } - - /** - * Set Crop selection style - * @param {Object} style - Selection styles - */ - setCropSelectionStyle(style) { - this.cropSelectionStyle = style; - } - - /** - * Get component - * @param {string} name - Component name - * @returns {Component} - */ - getComponent(name) { - return this._componentMap[name]; - } - - /** - * Get current drawing mode - * @returns {string} - */ - getDrawingMode() { - return this._drawingMode; - } - - /** - * Start a drawing mode. If the current mode is not 'NORMAL', 'stopDrawingMode()' will be called first. - * @param {String} mode Can be one of 'CROPPER', 'FREE_DRAWING', 'LINE', 'TEXT', 'SHAPE' - * @param {Object} [option] parameters of drawing mode, it's available with 'FREE_DRAWING', 'LINE_DRAWING' - * @param {Number} [option.width] brush width - * @param {String} [option.color] brush color - * @returns {boolean} true if success or false - */ - startDrawingMode(mode, option) { - if (this._isSameDrawingMode(mode)) { - return true; - } - - // If the current mode is not 'NORMAL', 'stopDrawingMode()' will be called first. - this.stopDrawingMode(); - - const drawingModeInstance = this._getDrawingModeInstance(mode); - if (drawingModeInstance && drawingModeInstance.start) { - drawingModeInstance.start(this, option); - - this._drawingMode = mode; - } - - return !!drawingModeInstance; - } - - /** - * Stop the current drawing mode and back to the 'NORMAL' mode - */ - stopDrawingMode() { - if (this._isSameDrawingMode(drawingModes.NORMAL)) { - return; - } - - const drawingModeInstance = this._getDrawingModeInstance(this.getDrawingMode()); - if (drawingModeInstance && drawingModeInstance.end) { - drawingModeInstance.end(this); - } - this._drawingMode = drawingModes.NORMAL; - } - - /** - * To data url from canvas - * @param {Object} options - options for toDataURL - * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png" - * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg. - * @param {Number} [options.multiplier=1] Multiplier to scale by - * @param {Number} [options.left] Cropping left offset. Introduced in fabric v1.2.14 - * @param {Number} [options.top] Cropping top offset. Introduced in fabric v1.2.14 - * @param {Number} [options.width] Cropping width. Introduced in fabric v1.2.14 - * @param {Number} [options.height] Cropping height. Introduced in fabric v1.2.14 - * @returns {string} A DOMString containing the requested data URI. - */ - toDataURL(options) { - const cropper = this.getComponent(components.CROPPER); - cropper.changeVisibility(false); - - const dataUrl = this._canvas && this._canvas.toDataURL(options); - cropper.changeVisibility(true); - - return dataUrl; - } - - /** - * Save image(background) of canvas - * @param {string} name - Name of image - * @param {?fabric.Image} canvasImage - Fabric image instance - */ - setCanvasImage(name, canvasImage) { - if (canvasImage) { - stamp(canvasImage); - } - this.imageName = name; - this.canvasImage = canvasImage; - } - - /** - * Set css max dimension - * @param {{width: number, height: number}} maxDimension - Max width & Max height - */ - setCssMaxDimension(maxDimension) { - this.cssMaxWidth = maxDimension.width || this.cssMaxWidth; - this.cssMaxHeight = maxDimension.height || this.cssMaxHeight; - } - - /** - * Adjust canvas dimension with scaling image - */ - adjustCanvasDimension() { - const canvasImage = this.canvasImage.scale(1); - const {width, height} = canvasImage.getBoundingRect(); - const maxDimension = this._calcMaxDimension(width, height); - - this.setCanvasCssDimension({ - width: '100%', - height: '100%', // Set height '' for IE9 - 'max-width': `${maxDimension.width}px`, - 'max-height': `${maxDimension.height}px` - }); - - this.setCanvasBackstoreDimension({ - width, - height - }); - this._canvas.centerObject(canvasImage); - } - - /** - * Set canvas dimension - css only - * {@link http://fabricjs.com/docs/fabric.Canvas.html#setDimensions} - * @param {Object} dimension - Canvas css dimension - */ - setCanvasCssDimension(dimension) { - this._canvas.setDimensions(dimension, cssOnly); - } - - /** - * Set canvas dimension - backstore only - * {@link http://fabricjs.com/docs/fabric.Canvas.html#setDimensions} - * @param {Object} dimension - Canvas backstore dimension - */ - setCanvasBackstoreDimension(dimension) { - this._canvas.setDimensions(dimension, backstoreOnly); - } - - /** - * Set image properties - * {@link http://fabricjs.com/docs/fabric.Image.html#set} - * @param {Object} setting - Image properties - * @param {boolean} [withRendering] - If true, The changed image will be reflected in the canvas - */ - setImageProperties(setting, withRendering) { - const {canvasImage} = this; - - if (!canvasImage) { - return; - } - - canvasImage.set(setting).setCoords(); - if (withRendering) { - this._canvas.renderAll(); - } - } - - /** - * Returns canvas element of fabric.Canvas[[lower-canvas]] - * @returns {HTMLCanvasElement} - */ - getCanvasElement() { - return this._canvas.getElement(); - } - - /** - * Get fabric.Canvas instance - * @returns {fabric.Canvas} + * target fabric object for copy paste feature + * @type {fabric.Object} * @private */ - getCanvas() { - return this._canvas; - } - - /** - * Get canvasImage (fabric.Image instance) - * @returns {fabric.Image} - */ - getCanvasImage() { - return this.canvasImage; - } - - /** - * Get image name - * @returns {string} - */ - getImageName() { - return this.imageName; - } - - /** - * Add image object on canvas - * @param {string} imgUrl - Image url to make object - * @returns {Promise} - */ - addImageObject(imgUrl) { - const callback = this._callbackAfterLoadingImageObject.bind(this); - - return new Promise(resolve => { - fabric.Image.fromURL(imgUrl, image => { - callback(image); - resolve(this.createObjectProperties(image)); - }, { - crossOrigin: 'Anonymous' - } - ); - }); - } - - /** - * Get center position of canvas - * @returns {Object} {left, top} - */ - getCenter() { - return this._canvas.getCenter(); - } - - /** - * Get cropped rect - * @returns {Object} rect - */ - getCropzoneRect() { - return this.getComponent(components.CROPPER).getCropzoneRect(); - } - - /** - * Get cropped rect - * @param {number} [mode] cropzone rect mode - */ - setCropzoneRect(mode) { - this.getComponent(components.CROPPER).setCropzoneRect(mode); - } + this.targetObjectForCopyPaste = null; /** - * Get cropped image data - * @param {Object} cropRect cropzone rect - * @param {Number} cropRect.left left position - * @param {Number} cropRect.top top position - * @param {Number} cropRect.width width - * @param {Number} cropRect.height height - * @returns {?{imageName: string, url: string}} cropped Image data + * Image name + * @type {string} */ - getCroppedImageData(cropRect) { - return this.getComponent(components.CROPPER).getCroppedImageData(cropRect); - } + this.imageName = ''; /** - * Set brush option - * @param {Object} option brush option - * @param {Number} option.width width - * @param {String} option.color color like 'FFFFFF', 'rgba(0, 0, 0, 0.5)' - */ - setBrush(option) { - const drawingMode = this._drawingMode; - let compName = components.FREE_DRAWING; - - if (drawingMode === drawingModes.LINE_DRAWING) { - compName = components.LINE; - } - - this.getComponent(compName).setBrush(option); - } - - /** - * Set states of current drawing shape - * @param {string} type - Shape type (ex: 'rect', 'circle', 'triangle') - * @param {Object} [options] - Shape options - * @param {(ShapeFillOption | string)} [options.fill] - {@link ShapeFillOption} or - * Shape foreground color (ex: '#fff', 'transparent') - * @param {string} [options.stoke] - Shape outline color - * @param {number} [options.strokeWidth] - Shape outline width - * @param {number} [options.width] - Width value (When type option is 'rect', this options can use) - * @param {number} [options.height] - Height value (When type option is 'rect', this options can use) - * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use) - * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use) - * @param {number} [options.isRegular] - Whether resizing shape has 1:1 ratio or not - */ - setDrawingShape(type, options) { - this.getComponent(components.SHAPE).setStates(type, options); - } - - /** - * Set style of current drawing icon - * @param {string} type - icon type (ex: 'icon-arrow', 'icon-star') - * @param {Object} [iconColor] - Icon color - */ - setIconStyle(type, iconColor) { - this.getComponent(components.ICON).setStates(type, iconColor); - } - - /** - * Register icon paths - * @param {Object} pathInfos - Path infos - * @param {string} pathInfos.key - key - * @param {string} pathInfos.value - value - */ - registerPaths(pathInfos) { - this.getComponent(components.ICON).registerPaths(pathInfos); - } - - /** - * Change cursor style - * @param {string} cursorType - cursor type - */ - changeCursor(cursorType) { - const canvas = this.getCanvas(); - canvas.defaultCursor = cursorType; - canvas.renderAll(); - } - - /** - * Whether it has the filter or not - * @param {string} type - Filter type - * @returns {boolean} true if it has the filter - */ - hasFilter(type) { - return this.getComponent(components.FILTER).hasFilter(type); - } - - /** - * Set selection style of fabric object by init option - * @param {Object} styles - Selection styles - */ - setSelectionStyle(styles) { - extend(fObjectOptions.SELECTION_STYLE, styles); - } - - /** - * Set object properties - * @param {number} id - object id - * @param {Object} props - props - * @param {string} [props.fill] Color - * @param {string} [props.fontFamily] Font type for text - * @param {number} [props.fontSize] Size - * @param {string} [props.fontStyle] Type of inclination (normal / italic) - * @param {string} [props.fontWeight] Type of thicker or thinner looking (normal / bold) - * @param {string} [props.textAlign] Type of text align (left / center / right) - * @param {string} [props.textDecoration] Type of line (underline / line-through / overline) - * @returns {Object} applied properties - */ - setObjectProperties(id, props) { - const object = this.getObject(id); - const clone = extend({}, props); - - object.set(clone); - - object.setCoords(); - - this.getCanvas().renderAll(); - - return clone; - } - - /** - * Get object properties corresponding key - * @param {number} id - object id - * @param {Array|ObjectProps|string} keys - property's key - * @returns {Object} properties - */ - getObjectProperties(id, keys) { - const object = this.getObject(id); - const props = {}; - - if (isString(keys)) { - props[keys] = object[keys]; - } else if (isArray(keys)) { - forEachArray(keys, value => { - props[value] = object[value]; - }); - } else { - forEachOwnProperties(keys, (value, key) => { - props[key] = object[key]; - }); - } - - return props; - } - - /** - * Get object position by originX, originY - * @param {number} id - object id - * @param {string} originX - can be 'left', 'center', 'right' - * @param {string} originY - can be 'top', 'center', 'bottom' - * @returns {Object} {{x:number, y: number}} position by origin if id is valid, or null - */ - getObjectPosition(id, originX, originY) { - const targetObj = this.getObject(id); - if (!targetObj) { - return null; - } - - return targetObj.getPointByOrigin(originX, originY); - } - - /** - * Set object position by originX, originY - * @param {number} id - object id - * @param {Object} posInfo - position object - * @param {number} posInfo.x - x position - * @param {number} posInfo.y - y position - * @param {string} posInfo.originX - can be 'left', 'center', 'right' - * @param {string} posInfo.originY - can be 'top', 'center', 'bottom' - * @returns {boolean} true if target id is valid or false - */ - setObjectPosition(id, posInfo) { - const targetObj = this.getObject(id); - const {x, y, originX, originY} = posInfo; - if (!targetObj) { - return false; - } - - const targetOrigin = targetObj.getPointByOrigin(originX, originY); - const centerOrigin = targetObj.getPointByOrigin('center', 'center'); - const diffX = centerOrigin.x - targetOrigin.x; - const diffY = centerOrigin.y - targetOrigin.y; - - targetObj.set({ - left: x + diffX, - top: y + diffY - }); - - targetObj.setCoords(); - - return true; - } - - /** - * Get the canvas size - * @returns {Object} {{width: number, height: number}} image size - */ - getCanvasSize() { - const image = this.getCanvasImage(); - - return { - width: image ? image.width : 0, - height: image ? image.height : 0 - }; - } - - /** - * Create fabric static canvas - * @returns {Object} {{width: number, height: number}} image size - */ - createStaticCanvas() { - const staticCanvas = new fabric.StaticCanvas(); - - staticCanvas.set({ - enableRetinaScaling: false - }); - - return staticCanvas; - } - - /** - * Get a DrawingMode instance - * @param {string} modeName - DrawingMode Class Name - * @returns {DrawingMode} DrawingMode instance - * @private - */ - _getDrawingModeInstance(modeName) { - return this._drawingModeMap[modeName]; - } - - /** - * Set object caching to false. This brought many bugs when draw Shape & cropzone - * @see http://fabricjs.com/fabric-object-caching - * @private - */ - _setObjectCachingToFalse() { - fabric.Object.prototype.objectCaching = false; - } - - /** - * Set canvas element to fabric.Canvas - * @param {Element|string} element - Wrapper or canvas element or selector - * @private - */ - _setCanvasElement(element) { - let selectedElement; - let canvasElement; - - if (element.nodeType) { - selectedElement = element; - } else { - selectedElement = document.querySelector(element); - } - - if (selectedElement.nodeName.toUpperCase() !== 'CANVAS') { - canvasElement = document.createElement('canvas'); - selectedElement.appendChild(canvasElement); - } - - this._canvas = new fabric.Canvas(canvasElement, { - containerClass: 'tui-image-editor-canvas-container', - enableRetinaScaling: false - }); - } - - /** - * Creates DrawingMode instances - * @private - */ - _createDrawingModeInstances() { - this._register(this._drawingModeMap, new CropperDrawingMode()); - this._register(this._drawingModeMap, new FreeDrawingMode()); - this._register(this._drawingModeMap, new LineDrawingMode()); - this._register(this._drawingModeMap, new ShapeDrawingMode()); - this._register(this._drawingModeMap, new TextDrawingMode()); - this._register(this._drawingModeMap, new IconDrawingMode()); - } - - /** - * Create components - * @private - */ - _createComponents() { - this._register(this._componentMap, new ImageLoader(this)); - this._register(this._componentMap, new Cropper(this)); - this._register(this._componentMap, new Flip(this)); - this._register(this._componentMap, new Rotation(this)); - this._register(this._componentMap, new FreeDrawing(this)); - this._register(this._componentMap, new Line(this)); - this._register(this._componentMap, new Text(this)); - this._register(this._componentMap, new Icon(this)); - this._register(this._componentMap, new Filter(this)); - this._register(this._componentMap, new Shape(this)); - } - - /** - * Register component - * @param {Object} map - map object - * @param {Object} module - module which has getName method - * @private - */ - _register(map, module) { - map[module.getName()] = module; - } - - /** - * Get the current drawing mode is same with given mode - * @param {string} mode drawing mode - * @returns {boolean} true if same or false - */ - _isSameDrawingMode(mode) { - return this.getDrawingMode() === mode; - } - - /** - * Calculate max dimension of canvas - * The css-max dimension is dynamically decided with maintaining image ratio - * The css-max dimension is lower than canvas dimension (attribute of canvas, not css) - * @param {number} width - Canvas width - * @param {number} height - Canvas height - * @returns {{width: number, height: number}} - Max width & Max height - * @private - */ - _calcMaxDimension(width, height) { - const wScaleFactor = this.cssMaxWidth / width; - const hScaleFactor = this.cssMaxHeight / height; - let cssMaxWidth = Math.min(width, this.cssMaxWidth); - let cssMaxHeight = Math.min(height, this.cssMaxHeight); - - if (wScaleFactor < 1 && wScaleFactor < hScaleFactor) { - cssMaxWidth = width * wScaleFactor; - cssMaxHeight = height * wScaleFactor; - } else if (hScaleFactor < 1 && hScaleFactor < wScaleFactor) { - cssMaxWidth = width * hScaleFactor; - cssMaxHeight = height * hScaleFactor; - } - - return { - width: Math.floor(cssMaxWidth), - height: Math.floor(cssMaxHeight) - }; - } - - /** - * Callback function after loading image - * @param {fabric.Image} obj - Fabric image object - * @private - */ - _callbackAfterLoadingImageObject(obj) { - const centerPos = this.getCanvasImage().getCenterPoint(); - - obj.set(fObjectOptions.SELECTION_STYLE); - obj.set({ - left: centerPos.x, - top: centerPos.y, - crossOrigin: 'Anonymous' - }); - - this.getCanvas().add(obj).setActiveObject(obj); - } - - /** - * Attach canvas's events - */ - _attachCanvasEvents() { - const canvas = this._canvas; - const handler = this._handler; - canvas.on({ - 'mouse:down': handler.onMouseDown, - 'object:added': handler.onObjectAdded, - 'object:removed': handler.onObjectRemoved, - 'object:moving': handler.onObjectMoved, - 'object:scaling': handler.onObjectScaled, - 'object:modified': handler.onObjectModified, - 'object:rotating': handler.onObjectRotated, - 'path:created': handler.onPathCreated, - 'selection:cleared': handler.onSelectionCleared, - 'selection:created': handler.onSelectionCreated, - 'selection:updated': handler.onObjectSelected - }); - } - - /** - * "mouse:down" canvas event handler - * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event - * @private - */ - _onMouseDown(fEvent) { - const {e: event, target} = fEvent; - const originPointer = this._canvas.getPointer(event); - - if (target) { - const {type} = target; - const undoData = makeSelectionUndoData(target, - item => makeSelectionUndoDatum(this.getObjectId(item), item, type === 'activeSelection')); - - setCachedUndoDataForDimension(undoData); - } - - this.fire(events.MOUSE_DOWN, event, originPointer); - } - - /** - * "object:added" canvas event handler - * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event + * Object Map + * @type {Object} * @private */ - _onObjectAdded(fEvent) { - const obj = fEvent.target; - if (obj.isType('cropzone')) { - return; - } - - this._addFabricObject(obj); - } - - /** - * "object:removed" canvas event handler - * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event - * @private - */ - _onObjectRemoved(fEvent) { - const obj = fEvent.target; - - this._removeFabricObject(stamp(obj)); - } - - /** - * "object:moving" canvas event handler - * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event - * @private - */ - _onObjectMoved(fEvent) { - this._lazyFire(events.OBJECT_MOVED, object => this.createObjectProperties(object), fEvent.target); - } + this._objects = {}; /** - * "object:scaling" canvas event handler - * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event + * Fabric-Canvas instance + * @type {fabric.Canvas} * @private */ - _onObjectScaled(fEvent) { - this._lazyFire(events.OBJECT_SCALED, object => this.createObjectProperties(object), fEvent.target); - } + this._canvas = null; /** - * "object:modified" canvas event handler - * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event + * Drawing mode + * @type {string} * @private */ - _onObjectModified(fEvent) { - const {target} = fEvent; - if (target.type === 'activeSelection') { - const items = target.getObjects(); - - items.forEach(item => item.fire('modifiedInGroup', target)); - } - - this.fire(events.OBJECT_MODIFIED, target, this.getObjectId(target)); - } + this._drawingMode = drawingModes.NORMAL; /** - * "object:rotating" canvas event handler - * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event + * DrawingMode map + * @type {Object.} * @private */ - _onObjectRotated(fEvent) { - this._lazyFire(events.OBJECT_ROTATED, object => this.createObjectProperties(object), fEvent.target); - } + this._drawingModeMap = {}; /** - * Lazy event emitter - * @param {string} eventName - event name - * @param {Function} paramsMaker - make param function - * @param {Object} [target] - Object of the event owner. + * Component map + * @type {Object.} * @private */ - _lazyFire(eventName, paramsMaker, target) { - const existEventDelegation = target && target.canvasEventDelegation; - const delegationState = existEventDelegation ? target.canvasEventDelegation(eventName) : 'none'; - - if (delegationState === 'unregisted') { - target.canvasEventRegister(eventName, object => { - this.fire(eventName, paramsMaker(object)); - }); - } - - if (delegationState === 'none') { - this.fire(eventName, paramsMaker(target)); - } - } + this._componentMap = {}; /** - * "object:selected" canvas event handler - * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event + * fabric event handlers + * @type {Object.} * @private */ - _onObjectSelected(fEvent) { - const {target} = fEvent; - const params = this.createObjectProperties(target); - - this.fire(events.OBJECT_ACTIVATED, params); - } - - /** - * "path:created" canvas event handler - * @param {{path: fabric.Path}} obj - Path object - * @private - */ - _onPathCreated(obj) { - const {x: left, y: top} = obj.path.getCenterPoint(); - obj.path.set(extend({ - left, - top - }, fObjectOptions.SELECTION_STYLE)); - - const params = this.createObjectProperties(obj.path); - - this.fire(events.ADD_OBJECT, params); - } - - /** - * "selction:cleared" canvas event handler - * @private - */ - _onSelectionCleared() { - this.fire(events.SELECTION_CLEARED); - } - - /** - * "selction:created" canvas event handler - * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event - * @private - */ - _onSelectionCreated(fEvent) { - const {target} = fEvent; - const params = this.createObjectProperties(target); - - this.fire(events.OBJECT_ACTIVATED, params); - this.fire(events.SELECTION_CREATED, fEvent.target); - } - - /** - * Canvas discard selection all - */ - discardSelection() { - this._canvas.discardActiveObject(); - this._canvas.renderAll(); - } - - /** - * Canvas Selectable status change - * @param {boolean} selectable - expect status - */ - changeSelectableAll(selectable) { - this._canvas.forEachObject(obj => { - obj.selectable = selectable; - obj.hoverCursor = selectable ? 'move' : 'crosshair'; - }); - } - - /** - * Return object's properties - * @param {fabric.Object} obj - fabric object - * @returns {Object} properties object - */ - createObjectProperties(obj) { - const predefinedKeys = [ - 'left', - 'top', - 'width', - 'height', - 'fill', - 'stroke', - 'strokeWidth', - 'opacity', - 'angle' - ]; - const props = { - id: stamp(obj), - type: obj.type - }; - - extend(props, getProperties(obj, predefinedKeys)); - - if (includes(['i-text', 'text'], obj.type)) { - extend(props, this._createTextProperties(obj, props)); - } else if (includes(['rect', 'triangle', 'circle'], obj.type)) { - const shapeComp = this.getComponent(components.SHAPE); - extend(props, { - fill: shapeComp.makeFillPropertyForUserEvent(obj) - }); + this._handler = { + onMouseDown: this._onMouseDown.bind(this), + onObjectAdded: this._onObjectAdded.bind(this), + onObjectRemoved: this._onObjectRemoved.bind(this), + onObjectMoved: this._onObjectMoved.bind(this), + onObjectScaled: this._onObjectScaled.bind(this), + onObjectModified: this._onObjectModified.bind(this), + onObjectRotated: this._onObjectRotated.bind(this), + onObjectSelected: this._onObjectSelected.bind(this), + onPathCreated: this._onPathCreated.bind(this), + onSelectionCleared: this._onSelectionCleared.bind(this), + onSelectionCreated: this._onSelectionCreated.bind(this), + }; + + this._setObjectCachingToFalse(); + this._setCanvasElement(element); + this._createDrawingModeInstances(); + this._createComponents(); + this._attachCanvasEvents(); + } + + /** + * Destroy canvas element + */ + destroy() { + const { wrapperEl } = this._canvas; + + this._canvas.clear(); + + wrapperEl.parentNode.removeChild(wrapperEl); + } + + /** + * Deactivates all objects on canvas + * @returns {Graphics} this + */ + deactivateAll() { + this._canvas.discardActiveObject(); + + return this; + } + + /** + * Renders all objects on canvas + * @returns {Graphics} this + */ + renderAll() { + this._canvas.renderAll(); + + return this; + } + + /** + * Adds objects on canvas + * @param {Object|Array} objects - objects + */ + add(objects) { + let theArgs = []; + if (isArray(objects)) { + theArgs = objects; + } else { + theArgs.push(objects); + } + + this._canvas.add(...theArgs); + } + + /** + * Removes the object or group + * @param {Object} target - graphics object or group + * @returns {boolean} true if contains or false + */ + contains(target) { + return this._canvas.contains(target); + } + + /** + * Gets all objects or group + * @returns {Array} all objects, shallow copy + */ + getObjects() { + return this._canvas.getObjects().slice(); + } + + /** + * Get an object by id + * @param {number} id - object id + * @returns {fabric.Object} object corresponding id + */ + getObject(id) { + return this._objects[id]; + } + + /** + * Removes the object or group + * @param {Object} target - graphics object or group + */ + remove(target) { + this._canvas.remove(target); + } + + /** + * Removes all object or group + * @param {boolean} includesBackground - remove the background image or not + * @returns {Array} all objects array which is removed + */ + removeAll(includesBackground) { + const canvas = this._canvas; + const objects = canvas.getObjects().slice(); + canvas.remove(...this._canvas.getObjects()); + + if (includesBackground) { + canvas.clear(); + } + + return objects; + } + + /** + * Removes an object or group by id + * @param {number} id - object id + * @returns {Array} removed objects + */ + removeObjectById(id) { + const objects = []; + const canvas = this._canvas; + const target = this.getObject(id); + const isValidGroup = target && target.isType('group') && !target.isEmpty(); + + if (isValidGroup) { + canvas.discardActiveObject(); // restore states for each objects + target.forEachObject((obj) => { + objects.push(obj); + canvas.remove(obj); + }); + } else if (canvas.contains(target)) { + objects.push(target); + canvas.remove(target); + } + + return objects; + } + + /** + * Get an id by object instance + * @param {fabric.Object} object object + * @returns {number} object id if it exists or null + */ + getObjectId(object) { + let key = null; + for (key in this._objects) { + if (this._objects.hasOwnProperty(key)) { + if (object === this._objects[key]) { + return key; } - - return props; - } - - /** - * Get text object's properties - * @param {fabric.Object} obj - fabric text object - * @param {Object} props - properties - * @returns {Object} properties object - */ - _createTextProperties(obj) { - const predefinedKeys = [ - 'text', - 'fontFamily', - 'fontSize', - 'fontStyle', - 'textAlign', - 'textDecoration', - 'fontWeight' - ]; - const props = {}; - extend(props, getProperties(obj, predefinedKeys)); - - return props; - } - - /** - * Add object array by id - * @param {fabric.Object} obj - fabric object - * @returns {number} object id - */ - _addFabricObject(obj) { - const id = stamp(obj); - this._objects[id] = obj; - - return id; - } - - /** - * Remove an object in array yb id - * @param {number} id - object id - */ - _removeFabricObject(id) { - delete this._objects[id]; - } - - /** - * Reset targetObjectForCopyPaste value from activeObject - */ - resetTargetObjectForCopyPaste() { - const activeObject = this.getActiveObject(); - - if (activeObject) { - this.targetObjectForCopyPaste = activeObject; + } + } + + return null; + } + + /** + * Gets an active object or group + * @returns {Object} active object or group instance + */ + getActiveObject() { + return this._canvas._activeObject; + } + + /** + * Returns the object ID to delete the object. + * @returns {number} object id for remove + */ + getActiveObjectIdForRemove() { + const activeObject = this.getActiveObject(); + const { type, left, top } = activeObject; + const isSelection = type === 'activeSelection'; + + if (isSelection) { + const group = new fabric.Group([...activeObject.getObjects()], { + left, + top, + }); + + return this._addFabricObject(group); + } + + return this.getObjectId(activeObject); + } + + /** + * Verify that you are ready to erase the object. + * @returns {boolean} ready for object remove + */ + isReadyRemoveObject() { + const activeObject = this.getActiveObject(); + + return activeObject && !activeObject.isEditing; + } + + /** + * Gets an active group object + * @returns {Object} active group object instance + */ + getActiveObjects() { + const activeObject = this._canvas._activeObject; + + return activeObject && activeObject.type === 'activeSelection' ? activeObject : null; + } + + /** + * Get Active object Selection from object ids + * @param {Array.} objects - fabric objects + * @returns {Object} target - target object group + */ + getActiveSelectionFromObjects(objects) { + const canvas = this.getCanvas(); + + return new fabric.ActiveSelection(objects, { canvas }); + } + + /** + * Activates an object or group + * @param {Object} target - target object or group + */ + setActiveObject(target) { + this._canvas.setActiveObject(target); + } + + /** + * Set Crop selection style + * @param {Object} style - Selection styles + */ + setCropSelectionStyle(style) { + this.cropSelectionStyle = style; + } + + /** + * Get component + * @param {string} name - Component name + * @returns {Component} + */ + getComponent(name) { + return this._componentMap[name]; + } + + /** + * Get current drawing mode + * @returns {string} + */ + getDrawingMode() { + return this._drawingMode; + } + + /** + * Start a drawing mode. If the current mode is not 'NORMAL', 'stopDrawingMode()' will be called first. + * @param {String} mode Can be one of 'CROPPER', 'FREE_DRAWING', 'LINE', 'TEXT', 'SHAPE' + * @param {Object} [option] parameters of drawing mode, it's available with 'FREE_DRAWING', 'LINE_DRAWING' + * @param {Number} [option.width] brush width + * @param {String} [option.color] brush color + * @returns {boolean} true if success or false + */ + startDrawingMode(mode, option) { + if (this._isSameDrawingMode(mode)) { + return true; + } + + // If the current mode is not 'NORMAL', 'stopDrawingMode()' will be called first. + this.stopDrawingMode(); + + const drawingModeInstance = this._getDrawingModeInstance(mode); + if (drawingModeInstance && drawingModeInstance.start) { + drawingModeInstance.start(this, option); + + this._drawingMode = mode; + } + + return !!drawingModeInstance; + } + + /** + * Stop the current drawing mode and back to the 'NORMAL' mode + */ + stopDrawingMode() { + if (this._isSameDrawingMode(drawingModes.NORMAL)) { + return; + } + + const drawingModeInstance = this._getDrawingModeInstance(this.getDrawingMode()); + if (drawingModeInstance && drawingModeInstance.end) { + drawingModeInstance.end(this); + } + this._drawingMode = drawingModes.NORMAL; + } + + /** + * To data url from canvas + * @param {Object} options - options for toDataURL + * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png" + * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg. + * @param {Number} [options.multiplier=1] Multiplier to scale by + * @param {Number} [options.left] Cropping left offset. Introduced in fabric v1.2.14 + * @param {Number} [options.top] Cropping top offset. Introduced in fabric v1.2.14 + * @param {Number} [options.width] Cropping width. Introduced in fabric v1.2.14 + * @param {Number} [options.height] Cropping height. Introduced in fabric v1.2.14 + * @returns {string} A DOMString containing the requested data URI. + */ + toDataURL(options) { + const cropper = this.getComponent(components.CROPPER); + cropper.changeVisibility(false); + + const dataUrl = this._canvas && this._canvas.toDataURL(options); + cropper.changeVisibility(true); + + return dataUrl; + } + + /** + * Save image(background) of canvas + * @param {string} name - Name of image + * @param {?fabric.Image} canvasImage - Fabric image instance + */ + setCanvasImage(name, canvasImage) { + if (canvasImage) { + stamp(canvasImage); + } + this.imageName = name; + this.canvasImage = canvasImage; + } + + /** + * Set css max dimension + * @param {{width: number, height: number}} maxDimension - Max width & Max height + */ + setCssMaxDimension(maxDimension) { + this.cssMaxWidth = maxDimension.width || this.cssMaxWidth; + this.cssMaxHeight = maxDimension.height || this.cssMaxHeight; + } + + /** + * Adjust canvas dimension with scaling image + */ + adjustCanvasDimension() { + const canvasImage = this.canvasImage.scale(1); + const { width, height } = canvasImage.getBoundingRect(); + const maxDimension = this._calcMaxDimension(width, height); + + this.setCanvasCssDimension({ + width: '100%', + height: '100%', // Set height '' for IE9 + 'max-width': `${maxDimension.width}px`, + 'max-height': `${maxDimension.height}px`, + }); + + this.setCanvasBackstoreDimension({ + width, + height, + }); + this._canvas.centerObject(canvasImage); + } + + /** + * Set canvas dimension - css only + * {@link http://fabricjs.com/docs/fabric.Canvas.html#setDimensions} + * @param {Object} dimension - Canvas css dimension + */ + setCanvasCssDimension(dimension) { + this._canvas.setDimensions(dimension, cssOnly); + } + + /** + * Set canvas dimension - backstore only + * {@link http://fabricjs.com/docs/fabric.Canvas.html#setDimensions} + * @param {Object} dimension - Canvas backstore dimension + */ + setCanvasBackstoreDimension(dimension) { + this._canvas.setDimensions(dimension, backstoreOnly); + } + + /** + * Set image properties + * {@link http://fabricjs.com/docs/fabric.Image.html#set} + * @param {Object} setting - Image properties + * @param {boolean} [withRendering] - If true, The changed image will be reflected in the canvas + */ + setImageProperties(setting, withRendering) { + const { canvasImage } = this; + + if (!canvasImage) { + return; + } + + canvasImage.set(setting).setCoords(); + if (withRendering) { + this._canvas.renderAll(); + } + } + + /** + * Returns canvas element of fabric.Canvas[[lower-canvas]] + * @returns {HTMLCanvasElement} + */ + getCanvasElement() { + return this._canvas.getElement(); + } + + /** + * Get fabric.Canvas instance + * @returns {fabric.Canvas} + * @private + */ + getCanvas() { + return this._canvas; + } + + /** + * Get canvasImage (fabric.Image instance) + * @returns {fabric.Image} + */ + getCanvasImage() { + return this.canvasImage; + } + + /** + * Get image name + * @returns {string} + */ + getImageName() { + return this.imageName; + } + + /** + * Add image object on canvas + * @param {string} imgUrl - Image url to make object + * @returns {Promise} + */ + addImageObject(imgUrl) { + const callback = this._callbackAfterLoadingImageObject.bind(this); + + return new Promise((resolve) => { + fabric.Image.fromURL( + imgUrl, + (image) => { + callback(image); + resolve(this.createObjectProperties(image)); + }, + { + crossOrigin: 'Anonymous', } - } - - /** - * Paste fabric object - * @returns {Promise} - */ - pasteObject() { - if (!this.targetObjectForCopyPaste) { - return Promise.resolve([]); + ); + }); + } + + /** + * Get center position of canvas + * @returns {Object} {left, top} + */ + getCenter() { + return this._canvas.getCenter(); + } + + /** + * Get cropped rect + * @returns {Object} rect + */ + getCropzoneRect() { + return this.getComponent(components.CROPPER).getCropzoneRect(); + } + + /** + * Get cropped rect + * @param {number} [mode] cropzone rect mode + */ + setCropzoneRect(mode) { + this.getComponent(components.CROPPER).setCropzoneRect(mode); + } + + /** + * Get cropped image data + * @param {Object} cropRect cropzone rect + * @param {Number} cropRect.left left position + * @param {Number} cropRect.top top position + * @param {Number} cropRect.width width + * @param {Number} cropRect.height height + * @returns {?{imageName: string, url: string}} cropped Image data + */ + getCroppedImageData(cropRect) { + return this.getComponent(components.CROPPER).getCroppedImageData(cropRect); + } + + /** + * Set brush option + * @param {Object} option brush option + * @param {Number} option.width width + * @param {String} option.color color like 'FFFFFF', 'rgba(0, 0, 0, 0.5)' + */ + setBrush(option) { + const drawingMode = this._drawingMode; + let compName = components.FREE_DRAWING; + + if (drawingMode === drawingModes.LINE_DRAWING) { + compName = components.LINE; + } + + this.getComponent(compName).setBrush(option); + } + + /** + * Set states of current drawing shape + * @param {string} type - Shape type (ex: 'rect', 'circle', 'triangle') + * @param {Object} [options] - Shape options + * @param {(ShapeFillOption | string)} [options.fill] - {@link ShapeFillOption} or + * Shape foreground color (ex: '#fff', 'transparent') + * @param {string} [options.stoke] - Shape outline color + * @param {number} [options.strokeWidth] - Shape outline width + * @param {number} [options.width] - Width value (When type option is 'rect', this options can use) + * @param {number} [options.height] - Height value (When type option is 'rect', this options can use) + * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use) + * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use) + * @param {number} [options.isRegular] - Whether resizing shape has 1:1 ratio or not + */ + setDrawingShape(type, options) { + this.getComponent(components.SHAPE).setStates(type, options); + } + + /** + * Set style of current drawing icon + * @param {string} type - icon type (ex: 'icon-arrow', 'icon-star') + * @param {Object} [iconColor] - Icon color + */ + setIconStyle(type, iconColor) { + this.getComponent(components.ICON).setStates(type, iconColor); + } + + /** + * Register icon paths + * @param {Object} pathInfos - Path infos + * @param {string} pathInfos.key - key + * @param {string} pathInfos.value - value + */ + registerPaths(pathInfos) { + this.getComponent(components.ICON).registerPaths(pathInfos); + } + + /** + * Change cursor style + * @param {string} cursorType - cursor type + */ + changeCursor(cursorType) { + const canvas = this.getCanvas(); + canvas.defaultCursor = cursorType; + canvas.renderAll(); + } + + /** + * Whether it has the filter or not + * @param {string} type - Filter type + * @returns {boolean} true if it has the filter + */ + hasFilter(type) { + return this.getComponent(components.FILTER).hasFilter(type); + } + + /** + * Set selection style of fabric object by init option + * @param {Object} styles - Selection styles + */ + setSelectionStyle(styles) { + extend(fObjectOptions.SELECTION_STYLE, styles); + } + + /** + * Set object properties + * @param {number} id - object id + * @param {Object} props - props + * @param {string} [props.fill] Color + * @param {string} [props.fontFamily] Font type for text + * @param {number} [props.fontSize] Size + * @param {string} [props.fontStyle] Type of inclination (normal / italic) + * @param {string} [props.fontWeight] Type of thicker or thinner looking (normal / bold) + * @param {string} [props.textAlign] Type of text align (left / center / right) + * @param {string} [props.textDecoration] Type of line (underline / line-through / overline) + * @returns {Object} applied properties + */ + setObjectProperties(id, props) { + const object = this.getObject(id); + const clone = extend({}, props); + + object.set(clone); + + object.setCoords(); + + this.getCanvas().renderAll(); + + return clone; + } + + /** + * Get object properties corresponding key + * @param {number} id - object id + * @param {Array|ObjectProps|string} keys - property's key + * @returns {Object} properties + */ + getObjectProperties(id, keys) { + const object = this.getObject(id); + const props = {}; + + if (isString(keys)) { + props[keys] = object[keys]; + } else if (isArray(keys)) { + forEachArray(keys, (value) => { + props[value] = object[value]; + }); + } else { + forEachOwnProperties(keys, (value, key) => { + props[key] = object[key]; + }); + } + + return props; + } + + /** + * Get object position by originX, originY + * @param {number} id - object id + * @param {string} originX - can be 'left', 'center', 'right' + * @param {string} originY - can be 'top', 'center', 'bottom' + * @returns {Object} {{x:number, y: number}} position by origin if id is valid, or null + */ + getObjectPosition(id, originX, originY) { + const targetObj = this.getObject(id); + if (!targetObj) { + return null; + } + + return targetObj.getPointByOrigin(originX, originY); + } + + /** + * Set object position by originX, originY + * @param {number} id - object id + * @param {Object} posInfo - position object + * @param {number} posInfo.x - x position + * @param {number} posInfo.y - y position + * @param {string} posInfo.originX - can be 'left', 'center', 'right' + * @param {string} posInfo.originY - can be 'top', 'center', 'bottom' + * @returns {boolean} true if target id is valid or false + */ + setObjectPosition(id, posInfo) { + const targetObj = this.getObject(id); + const { x, y, originX, originY } = posInfo; + if (!targetObj) { + return false; + } + + const targetOrigin = targetObj.getPointByOrigin(originX, originY); + const centerOrigin = targetObj.getPointByOrigin('center', 'center'); + const diffX = centerOrigin.x - targetOrigin.x; + const diffY = centerOrigin.y - targetOrigin.y; + + targetObj.set({ + left: x + diffX, + top: y + diffY, + }); + + targetObj.setCoords(); + + return true; + } + + /** + * Get the canvas size + * @returns {Object} {{width: number, height: number}} image size + */ + getCanvasSize() { + const image = this.getCanvasImage(); + + return { + width: image ? image.width : 0, + height: image ? image.height : 0, + }; + } + + /** + * Create fabric static canvas + * @returns {Object} {{width: number, height: number}} image size + */ + createStaticCanvas() { + const staticCanvas = new fabric.StaticCanvas(); + + staticCanvas.set({ + enableRetinaScaling: false, + }); + + return staticCanvas; + } + + /** + * Get a DrawingMode instance + * @param {string} modeName - DrawingMode Class Name + * @returns {DrawingMode} DrawingMode instance + * @private + */ + _getDrawingModeInstance(modeName) { + return this._drawingModeMap[modeName]; + } + + /** + * Set object caching to false. This brought many bugs when draw Shape & cropzone + * @see http://fabricjs.com/fabric-object-caching + * @private + */ + _setObjectCachingToFalse() { + fabric.Object.prototype.objectCaching = false; + } + + /** + * Set canvas element to fabric.Canvas + * @param {Element|string} element - Wrapper or canvas element or selector + * @private + */ + _setCanvasElement(element) { + let selectedElement; + let canvasElement; + + if (element.nodeType) { + selectedElement = element; + } else { + selectedElement = document.querySelector(element); + } + + if (selectedElement.nodeName.toUpperCase() !== 'CANVAS') { + canvasElement = document.createElement('canvas'); + selectedElement.appendChild(canvasElement); + } + + this._canvas = new fabric.Canvas(canvasElement, { + containerClass: 'tui-image-editor-canvas-container', + enableRetinaScaling: false, + }); + } + + /** + * Creates DrawingMode instances + * @private + */ + _createDrawingModeInstances() { + this._register(this._drawingModeMap, new CropperDrawingMode()); + this._register(this._drawingModeMap, new FreeDrawingMode()); + this._register(this._drawingModeMap, new LineDrawingMode()); + this._register(this._drawingModeMap, new ShapeDrawingMode()); + this._register(this._drawingModeMap, new TextDrawingMode()); + this._register(this._drawingModeMap, new IconDrawingMode()); + } + + /** + * Create components + * @private + */ + _createComponents() { + this._register(this._componentMap, new ImageLoader(this)); + this._register(this._componentMap, new Cropper(this)); + this._register(this._componentMap, new Flip(this)); + this._register(this._componentMap, new Rotation(this)); + this._register(this._componentMap, new FreeDrawing(this)); + this._register(this._componentMap, new Line(this)); + this._register(this._componentMap, new Text(this)); + this._register(this._componentMap, new Icon(this)); + this._register(this._componentMap, new Filter(this)); + this._register(this._componentMap, new Shape(this)); + } + + /** + * Register component + * @param {Object} map - map object + * @param {Object} module - module which has getName method + * @private + */ + _register(map, module) { + map[module.getName()] = module; + } + + /** + * Get the current drawing mode is same with given mode + * @param {string} mode drawing mode + * @returns {boolean} true if same or false + */ + _isSameDrawingMode(mode) { + return this.getDrawingMode() === mode; + } + + /** + * Calculate max dimension of canvas + * The css-max dimension is dynamically decided with maintaining image ratio + * The css-max dimension is lower than canvas dimension (attribute of canvas, not css) + * @param {number} width - Canvas width + * @param {number} height - Canvas height + * @returns {{width: number, height: number}} - Max width & Max height + * @private + */ + _calcMaxDimension(width, height) { + const wScaleFactor = this.cssMaxWidth / width; + const hScaleFactor = this.cssMaxHeight / height; + let cssMaxWidth = Math.min(width, this.cssMaxWidth); + let cssMaxHeight = Math.min(height, this.cssMaxHeight); + + if (wScaleFactor < 1 && wScaleFactor < hScaleFactor) { + cssMaxWidth = width * wScaleFactor; + cssMaxHeight = height * wScaleFactor; + } else if (hScaleFactor < 1 && hScaleFactor < wScaleFactor) { + cssMaxWidth = width * hScaleFactor; + cssMaxHeight = height * hScaleFactor; + } + + return { + width: Math.floor(cssMaxWidth), + height: Math.floor(cssMaxHeight), + }; + } + + /** + * Callback function after loading image + * @param {fabric.Image} obj - Fabric image object + * @private + */ + _callbackAfterLoadingImageObject(obj) { + const centerPos = this.getCanvasImage().getCenterPoint(); + + obj.set(fObjectOptions.SELECTION_STYLE); + obj.set({ + left: centerPos.x, + top: centerPos.y, + crossOrigin: 'Anonymous', + }); + + this.getCanvas().add(obj).setActiveObject(obj); + } + + /** + * Attach canvas's events + */ + _attachCanvasEvents() { + const canvas = this._canvas; + const handler = this._handler; + canvas.on({ + 'mouse:down': handler.onMouseDown, + 'object:added': handler.onObjectAdded, + 'object:removed': handler.onObjectRemoved, + 'object:moving': handler.onObjectMoved, + 'object:scaling': handler.onObjectScaled, + 'object:modified': handler.onObjectModified, + 'object:rotating': handler.onObjectRotated, + 'path:created': handler.onPathCreated, + 'selection:cleared': handler.onSelectionCleared, + 'selection:created': handler.onSelectionCreated, + 'selection:updated': handler.onObjectSelected, + }); + } + + /** + * "mouse:down" canvas event handler + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event + * @private + */ + _onMouseDown(fEvent) { + const { e: event, target } = fEvent; + const originPointer = this._canvas.getPointer(event); + + if (target) { + const { type } = target; + const undoData = makeSelectionUndoData(target, (item) => + makeSelectionUndoDatum(this.getObjectId(item), item, type === 'activeSelection') + ); + + setCachedUndoDataForDimension(undoData); + } + + this.fire(events.MOUSE_DOWN, event, originPointer); + } + + /** + * "object:added" canvas event handler + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event + * @private + */ + _onObjectAdded(fEvent) { + const obj = fEvent.target; + if (obj.isType('cropzone')) { + return; + } + + this._addFabricObject(obj); + } + + /** + * "object:removed" canvas event handler + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event + * @private + */ + _onObjectRemoved(fEvent) { + const obj = fEvent.target; + + this._removeFabricObject(stamp(obj)); + } + + /** + * "object:moving" canvas event handler + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event + * @private + */ + _onObjectMoved(fEvent) { + this._lazyFire( + events.OBJECT_MOVED, + (object) => this.createObjectProperties(object), + fEvent.target + ); + } + + /** + * "object:scaling" canvas event handler + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event + * @private + */ + _onObjectScaled(fEvent) { + this._lazyFire( + events.OBJECT_SCALED, + (object) => this.createObjectProperties(object), + fEvent.target + ); + } + + /** + * "object:modified" canvas event handler + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event + * @private + */ + _onObjectModified(fEvent) { + const { target } = fEvent; + if (target.type === 'activeSelection') { + const items = target.getObjects(); + + items.forEach((item) => item.fire('modifiedInGroup', target)); + } + + this.fire(events.OBJECT_MODIFIED, target, this.getObjectId(target)); + } + + /** + * "object:rotating" canvas event handler + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event + * @private + */ + _onObjectRotated(fEvent) { + this._lazyFire( + events.OBJECT_ROTATED, + (object) => this.createObjectProperties(object), + fEvent.target + ); + } + + /** + * Lazy event emitter + * @param {string} eventName - event name + * @param {Function} paramsMaker - make param function + * @param {Object} [target] - Object of the event owner. + * @private + */ + _lazyFire(eventName, paramsMaker, target) { + const existEventDelegation = target && target.canvasEventDelegation; + const delegationState = existEventDelegation ? target.canvasEventDelegation(eventName) : 'none'; + + if (delegationState === 'unregisted') { + target.canvasEventRegister(eventName, (object) => { + this.fire(eventName, paramsMaker(object)); + }); + } + + if (delegationState === 'none') { + this.fire(eventName, paramsMaker(target)); + } + } + + /** + * "object:selected" canvas event handler + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event + * @private + */ + _onObjectSelected(fEvent) { + const { target } = fEvent; + const params = this.createObjectProperties(target); + + this.fire(events.OBJECT_ACTIVATED, params); + } + + /** + * "path:created" canvas event handler + * @param {{path: fabric.Path}} obj - Path object + * @private + */ + _onPathCreated(obj) { + const { x: left, y: top } = obj.path.getCenterPoint(); + obj.path.set( + extend( + { + left, + top, + }, + fObjectOptions.SELECTION_STYLE + ) + ); + + const params = this.createObjectProperties(obj.path); + + this.fire(events.ADD_OBJECT, params); + } + + /** + * "selction:cleared" canvas event handler + * @private + */ + _onSelectionCleared() { + this.fire(events.SELECTION_CLEARED); + } + + /** + * "selction:created" canvas event handler + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event + * @private + */ + _onSelectionCreated(fEvent) { + const { target } = fEvent; + const params = this.createObjectProperties(target); + + this.fire(events.OBJECT_ACTIVATED, params); + this.fire(events.SELECTION_CREATED, fEvent.target); + } + + /** + * Canvas discard selection all + */ + discardSelection() { + this._canvas.discardActiveObject(); + this._canvas.renderAll(); + } + + /** + * Canvas Selectable status change + * @param {boolean} selectable - expect status + */ + changeSelectableAll(selectable) { + this._canvas.forEachObject((obj) => { + obj.selectable = selectable; + obj.hoverCursor = selectable ? 'move' : 'crosshair'; + }); + } + + /** + * Return object's properties + * @param {fabric.Object} obj - fabric object + * @returns {Object} properties object + */ + createObjectProperties(obj) { + const predefinedKeys = [ + 'left', + 'top', + 'width', + 'height', + 'fill', + 'stroke', + 'strokeWidth', + 'opacity', + 'angle', + ]; + const props = { + id: stamp(obj), + type: obj.type, + }; + + extend(props, getProperties(obj, predefinedKeys)); + + if (includes(['i-text', 'text'], obj.type)) { + extend(props, this._createTextProperties(obj, props)); + } else if (includes(['rect', 'triangle', 'circle'], obj.type)) { + const shapeComp = this.getComponent(components.SHAPE); + extend(props, { + fill: shapeComp.makeFillPropertyForUserEvent(obj), + }); + } + + return props; + } + + /** + * Get text object's properties + * @param {fabric.Object} obj - fabric text object + * @param {Object} props - properties + * @returns {Object} properties object + */ + _createTextProperties(obj) { + const predefinedKeys = [ + 'text', + 'fontFamily', + 'fontSize', + 'fontStyle', + 'textAlign', + 'textDecoration', + 'fontWeight', + ]; + const props = {}; + extend(props, getProperties(obj, predefinedKeys)); + + return props; + } + + /** + * Add object array by id + * @param {fabric.Object} obj - fabric object + * @returns {number} object id + */ + _addFabricObject(obj) { + const id = stamp(obj); + this._objects[id] = obj; + + return id; + } + + /** + * Remove an object in array yb id + * @param {number} id - object id + */ + _removeFabricObject(id) { + delete this._objects[id]; + } + + /** + * Reset targetObjectForCopyPaste value from activeObject + */ + resetTargetObjectForCopyPaste() { + const activeObject = this.getActiveObject(); + + if (activeObject) { + this.targetObjectForCopyPaste = activeObject; + } + } + + /** + * Paste fabric object + * @returns {Promise} + */ + pasteObject() { + if (!this.targetObjectForCopyPaste) { + return Promise.resolve([]); + } + + const targetObject = this.targetObjectForCopyPaste; + const isGroupSelect = targetObject.type === 'activeSelection'; + const targetObjects = isGroupSelect ? targetObject.getObjects() : [targetObject]; + let newTargetObject = null; + + this.discardSelection(); + + return this._cloneObject(targetObjects).then((addedObjects) => { + if (addedObjects.length > 1) { + newTargetObject = this.getActiveSelectionFromObjects(addedObjects); + } else { + [newTargetObject] = addedObjects; + } + this.targetObjectForCopyPaste = newTargetObject; + this.setActiveObject(newTargetObject); + }); + } + + /** + * Clone object + * @param {fabric.Object} targetObjects - fabric object + * @returns {Promise} + * @private + */ + _cloneObject(targetObjects) { + const addedObjects = snippet.map(targetObjects, (targetObject) => + this._cloneObjectItem(targetObject) + ); + + return Promise.all(addedObjects); + } + + /** + * Clone object one item + * @param {fabric.Object} targetObject - fabric object + * @returns {Promise} + * @private + */ + _cloneObjectItem(targetObject) { + return this._copyFabricObjectForPaste(targetObject).then((clonedObject) => { + const objectProperties = this.createObjectProperties(clonedObject); + this.add(clonedObject); + + this.fire(events.ADD_OBJECT, objectProperties); + + return clonedObject; + }); + } + + /** + * Copy fabric object with Changed position for copy and paste + * @param {fabric.Object} targetObject - fabric object + * @returns {Promise} + * @private + */ + _copyFabricObjectForPaste(targetObject) { + const addExtraPx = (value, isReverse) => + isReverse ? value - EXTRA_PX_FOR_PASTE : value + EXTRA_PX_FOR_PASTE; + + return this._copyFabricObject(targetObject).then((clonedObject) => { + const { left, top, width, height } = clonedObject; + const { width: canvasWidth, height: canvasHeight } = this.getCanvasSize(); + const rightEdge = left + width / 2; + const bottomEdge = top + height / 2; + + clonedObject.set( + snippet.extend( + { + left: addExtraPx(left, rightEdge + EXTRA_PX_FOR_PASTE > canvasWidth), + top: addExtraPx(top, bottomEdge + EXTRA_PX_FOR_PASTE > canvasHeight), + }, + fObjectOptions.SELECTION_STYLE + ) + ); + + return clonedObject; + }); + } + + /** + * Copy fabric object + * @param {fabric.Object} targetObject - fabric object + * @returns {Promise} + * @private + */ + _copyFabricObject(targetObject) { + return new Promise((resolve) => { + targetObject.clone((cloned) => { + const shapeComp = this.getComponent(components.SHAPE); + if (isShape(cloned)) { + shapeComp.processForCopiedObject(cloned, targetObject); } - const targetObject = this.targetObjectForCopyPaste; - const isGroupSelect = targetObject.type === 'activeSelection'; - const targetObjects = isGroupSelect ? targetObject.getObjects() : [targetObject]; - let newTargetObject = null; - - this.discardSelection(); - - return this._cloneObject(targetObjects).then(addedObjects => { - if (addedObjects.length > 1) { - newTargetObject = this.getActiveSelectionFromObjects(addedObjects); - } else { - ([newTargetObject] = addedObjects); - } - this.targetObjectForCopyPaste = newTargetObject; - this.setActiveObject(newTargetObject); - }); - } - - /** - * Clone object - * @param {fabric.Object} targetObjects - fabric object - * @returns {Promise} - * @private - */ - _cloneObject(targetObjects) { - const addedObjects = snippet.map(targetObjects, targetObject => ( - this._cloneObjectItem(targetObject) - )); - - return Promise.all(addedObjects); - } - - /** - * Clone object one item - * @param {fabric.Object} targetObject - fabric object - * @returns {Promise} - * @private - */ - _cloneObjectItem(targetObject) { - return this._copyFabricObjectForPaste(targetObject).then(clonedObject => { - const objectProperties = this.createObjectProperties(clonedObject); - this.add(clonedObject); - - this.fire(events.ADD_OBJECT, objectProperties); - - return clonedObject; - }); - } - - /** - * Copy fabric object with Changed position for copy and paste - * @param {fabric.Object} targetObject - fabric object - * @returns {Promise} - * @private - */ - _copyFabricObjectForPaste(targetObject) { - const addExtraPx = (value, isReverse) => isReverse ? value - EXTRA_PX_FOR_PASTE : value + EXTRA_PX_FOR_PASTE; - - return this._copyFabricObject(targetObject).then(clonedObject => { - const {left, top, width, height} = clonedObject; - const {width: canvasWidth, height: canvasHeight} = this.getCanvasSize(); - const rightEdge = left + (width / 2); - const bottomEdge = top + (height / 2); - - clonedObject.set(snippet.extend({ - left: addExtraPx(left, rightEdge + EXTRA_PX_FOR_PASTE > canvasWidth), - top: addExtraPx(top, bottomEdge + EXTRA_PX_FOR_PASTE > canvasHeight) - }, fObjectOptions.SELECTION_STYLE)); - - return clonedObject; - }); - } - - /** - * Copy fabric object - * @param {fabric.Object} targetObject - fabric object - * @returns {Promise} - * @private - */ - _copyFabricObject(targetObject) { - return new Promise(resolve => { - targetObject.clone(cloned => { - const shapeComp = this.getComponent(components.SHAPE); - if (isShape(cloned)) { - shapeComp.processForCopiedObject(cloned, targetObject); - } - - resolve(cloned); - }); - }); - } + resolve(cloned); + }); + }); + } } CustomEvents.mixin(Graphics); diff --git a/src/js/helper/imagetracer.js b/src/js/helper/imagetracer.js index 5030a576a..c0fc973d4 100644 --- a/src/js/helper/imagetracer.js +++ b/src/js/helper/imagetracer.js @@ -28,1124 +28,1369 @@ For more information, please refer to http://unlicense.org/ */ export default class ImageTracer { - static tracerDefaultOption() { - return { - pathomit: 100, - ltres: 0.1, - qtres: 1, - - scale: 1, - strokewidth: 5, - viewbox: false, - linefilter: true, - desc: false, - rightangleenhance: false, - pal: [{ - r: 0, - g: 0, - b: 0, - a: 255 - }, { - r: 255, - g: 255, - b: 255, - a: 255 - }] - }; - } - /* eslint-disable */ - constructor() { - this.versionnumber = '1.2.4'; - this.optionpresets = { - default: { - corsenabled: false, - ltres: 1, - qtres: 1, - pathomit: 8, - rightangleenhance: true, - colorsampling: 2, - numberofcolors: 16, - mincolorratio: 0, - colorquantcycles: 3, - layering: 0, - strokewidth: 1, - linefilter: false, - scale: 1, - roundcoords: 1, - viewbox: false, - desc: false, - lcpr: 0, - qcpr: 0, - blurradius: 0, - blurdelta: 20 - }, - 'posterized1': { - colorsampling: 0, - numberofcolors: 2 - }, - 'posterized2': { - numberofcolors: 4, - blurradius: 5 - }, - 'curvy': { - ltres: 0.01, - linefilter: true, - rightangleenhance: false}, - 'sharp': {qtres: 0.01, - linefilter: false}, - 'detailed': {pathomit: 0, - roundcoords: 2, - ltres: 0.5, - qtres: 0.5, - numberofcolors: 64}, - 'smoothed': {blurradius: 5, - blurdelta: 64}, - 'grayscale': {colorsampling: 0, - colorquantcycles: 1, - numberofcolors: 7}, - 'fixedpalette': {colorsampling: 0, - colorquantcycles: 1, - numberofcolors: 27}, - 'randomsampling1': {colorsampling: 1, - numberofcolors: 8}, - 'randomsampling2': {colorsampling: 1, - numberofcolors: 64}, - 'artistic1': {colorsampling: 0, - colorquantcycles: 1, - pathomit: 0, - blurradius: 5, - blurdelta: 64, - ltres: 0.01, - linefilter: true, - numberofcolors: 16, - strokewidth: 2}, - 'artistic2': {qtres: 0.01, - colorsampling: 0, - colorquantcycles: 1, - numberofcolors: 4, - strokewidth: 0}, - 'artistic3': {qtres: 10, - ltres: 10, - numberofcolors: 8}, - 'artistic4': {qtres: 10, - ltres: 10, - numberofcolors: 64, - blurradius: 5, - blurdelta: 256, - strokewidth: 2}, - 'posterized3': {ltres: 1, - qtres: 1, - pathomit: 20, - rightangleenhance: true, - colorsampling: 0, - numberofcolors: 3, - mincolorratio: 0, - colorquantcycles: 3, - blurradius: 3, - blurdelta: 20, - strokewidth: 0, - linefilter: false, - roundcoords: 1, - pal: [{r: 0, - g: 0, - b: 100, - a: 255}, {r: 255, - g: 255, - b: 255, - a: 255}]} - }; - - this.pathscan_combined_lookup = [ - [[-1,-1,-1,-1], [-1,-1,-1,-1], [-1,-1,-1,-1], [-1,-1,-1,-1]], - [[ 0, 1, 0,-1], [-1,-1,-1,-1], [-1,-1,-1,-1], [ 0, 2,-1, 0]], - [[-1,-1,-1,-1], [-1,-1,-1,-1], [ 0, 1, 0,-1], [ 0, 0, 1, 0]], - [[ 0, 0, 1, 0], [-1,-1,-1,-1], [ 0, 2,-1, 0], [-1,-1,-1,-1]], - [[-1,-1,-1,-1], [ 0, 0, 1, 0], [ 0, 3, 0, 1], [-1,-1,-1,-1]], - [[13, 3, 0, 1], [13, 2,-1, 0], [ 7, 1, 0,-1], [ 7, 0, 1, 0]], - [[-1,-1,-1,-1], [ 0, 1, 0,-1], [-1,-1,-1,-1], [ 0, 3, 0, 1]], - [[ 0, 3, 0, 1], [ 0, 2,-1, 0], [-1,-1,-1,-1], [-1,-1,-1,-1]], - [[ 0, 3, 0, 1], [ 0, 2,-1, 0], [-1,-1,-1,-1], [-1,-1,-1,-1]], - [[-1,-1,-1,-1], [ 0, 1, 0,-1], [-1,-1,-1,-1], [ 0, 3, 0, 1]], - [[11, 1, 0,-1], [14, 0, 1, 0], [14, 3, 0, 1], [11, 2,-1, 0]], - [[-1,-1,-1,-1], [ 0, 0, 1, 0], [ 0, 3, 0, 1], [-1,-1,-1,-1]], - [[ 0, 0, 1, 0], [-1,-1,-1,-1], [ 0, 2,-1, 0], [-1,-1,-1,-1]], - [[-1,-1,-1,-1], [-1,-1,-1,-1], [ 0, 1, 0,-1], [ 0, 0, 1, 0]], - [[ 0, 1, 0,-1], [-1,-1,-1,-1], [-1,-1,-1,-1], [ 0, 2,-1, 0]], - [[-1,-1,-1,-1], [-1,-1,-1,-1], [-1,-1,-1,-1], [-1,-1,-1,-1]] - ]; - - this.gks = [ [0.27901,0.44198,0.27901], [0.135336,0.228569,0.272192,0.228569,0.135336], [0.086776,0.136394,0.178908,0.195843,0.178908,0.136394,0.086776], - [0.063327,0.093095,0.122589,0.144599,0.152781,0.144599,0.122589,0.093095,0.063327], [0.049692,0.069304,0.089767,0.107988,0.120651,0.125194,0.120651,0.107988,0.089767,0.069304,0.049692] ]; - - this.specpalette = [ - {r:0,g:0,b:0,a:255}, {r:128,g:128,b:128,a:255}, {r:0,g:0,b:128,a:255}, {r:64,g:64,b:128,a:255}, - {r:192,g:192,b:192,a:255}, {r:255,g:255,b:255,a:255}, {r:128,g:128,b:192,a:255}, {r:0,g:0,b:192,a:255}, - {r:128,g:0,b:0,a:255}, {r:128,g:64,b:64,a:255}, {r:128,g:0,b:128,a:255}, {r:168,g:168,b:168,a:255}, - {r:192,g:128,b:128,a:255}, {r:192,g:0,b:0,a:255}, {r:255,g:255,b:255,a:255}, {r:0,g:128,b:0,a:255} - ]; - } - - imageToSVG(url, callback, options) { - options = this.checkoptions(options); - this.loadImage( - url, - canvas => { - callback( - this.imagedataToSVG(this.getImgdata(canvas), options) - ); - }, + static tracerDefaultOption() { + return { + pathomit: 100, + ltres: 0.1, + qtres: 1, + + scale: 1, + strokewidth: 5, + viewbox: false, + linefilter: true, + desc: false, + rightangleenhance: false, + pal: [ + { + r: 0, + g: 0, + b: 0, + a: 255, + }, + { + r: 255, + g: 255, + b: 255, + a: 255, + }, + ], + }; + } + /* eslint-disable */ + constructor() { + this.versionnumber = '1.2.4'; + this.optionpresets = { + default: { + corsenabled: false, + ltres: 1, + qtres: 1, + pathomit: 8, + rightangleenhance: true, + colorsampling: 2, + numberofcolors: 16, + mincolorratio: 0, + colorquantcycles: 3, + layering: 0, + strokewidth: 1, + linefilter: false, + scale: 1, + roundcoords: 1, + viewbox: false, + desc: false, + lcpr: 0, + qcpr: 0, + blurradius: 0, + blurdelta: 20, + }, + posterized1: { + colorsampling: 0, + numberofcolors: 2, + }, + posterized2: { + numberofcolors: 4, + blurradius: 5, + }, + curvy: { + ltres: 0.01, + linefilter: true, + rightangleenhance: false, + }, + sharp: { qtres: 0.01, linefilter: false }, + detailed: { pathomit: 0, roundcoords: 2, ltres: 0.5, qtres: 0.5, numberofcolors: 64 }, + smoothed: { blurradius: 5, blurdelta: 64 }, + grayscale: { colorsampling: 0, colorquantcycles: 1, numberofcolors: 7 }, + fixedpalette: { colorsampling: 0, colorquantcycles: 1, numberofcolors: 27 }, + randomsampling1: { colorsampling: 1, numberofcolors: 8 }, + randomsampling2: { colorsampling: 1, numberofcolors: 64 }, + artistic1: { + colorsampling: 0, + colorquantcycles: 1, + pathomit: 0, + blurradius: 5, + blurdelta: 64, + ltres: 0.01, + linefilter: true, + numberofcolors: 16, + strokewidth: 2, + }, + artistic2: { + qtres: 0.01, + colorsampling: 0, + colorquantcycles: 1, + numberofcolors: 4, + strokewidth: 0, + }, + artistic3: { qtres: 10, ltres: 10, numberofcolors: 8 }, + artistic4: { + qtres: 10, + ltres: 10, + numberofcolors: 64, + blurradius: 5, + blurdelta: 256, + strokewidth: 2, + }, + posterized3: { + ltres: 1, + qtres: 1, + pathomit: 20, + rightangleenhance: true, + colorsampling: 0, + numberofcolors: 3, + mincolorratio: 0, + colorquantcycles: 3, + blurradius: 3, + blurdelta: 20, + strokewidth: 0, + linefilter: false, + roundcoords: 1, + pal: [ + { r: 0, g: 0, b: 100, a: 255 }, + { r: 255, g: 255, b: 255, a: 255 }, + ], + }, + }; + + this.pathscan_combined_lookup = [ + [ + [-1, -1, -1, -1], + [-1, -1, -1, -1], + [-1, -1, -1, -1], + [-1, -1, -1, -1], + ], + [ + [0, 1, 0, -1], + [-1, -1, -1, -1], + [-1, -1, -1, -1], + [0, 2, -1, 0], + ], + [ + [-1, -1, -1, -1], + [-1, -1, -1, -1], + [0, 1, 0, -1], + [0, 0, 1, 0], + ], + [ + [0, 0, 1, 0], + [-1, -1, -1, -1], + [0, 2, -1, 0], + [-1, -1, -1, -1], + ], + [ + [-1, -1, -1, -1], + [0, 0, 1, 0], + [0, 3, 0, 1], + [-1, -1, -1, -1], + ], + [ + [13, 3, 0, 1], + [13, 2, -1, 0], + [7, 1, 0, -1], + [7, 0, 1, 0], + ], + [ + [-1, -1, -1, -1], + [0, 1, 0, -1], + [-1, -1, -1, -1], + [0, 3, 0, 1], + ], + [ + [0, 3, 0, 1], + [0, 2, -1, 0], + [-1, -1, -1, -1], + [-1, -1, -1, -1], + ], + [ + [0, 3, 0, 1], + [0, 2, -1, 0], + [-1, -1, -1, -1], + [-1, -1, -1, -1], + ], + [ + [-1, -1, -1, -1], + [0, 1, 0, -1], + [-1, -1, -1, -1], + [0, 3, 0, 1], + ], + [ + [11, 1, 0, -1], + [14, 0, 1, 0], + [14, 3, 0, 1], + [11, 2, -1, 0], + ], + [ + [-1, -1, -1, -1], + [0, 0, 1, 0], + [0, 3, 0, 1], + [-1, -1, -1, -1], + ], + [ + [0, 0, 1, 0], + [-1, -1, -1, -1], + [0, 2, -1, 0], + [-1, -1, -1, -1], + ], + [ + [-1, -1, -1, -1], + [-1, -1, -1, -1], + [0, 1, 0, -1], + [0, 0, 1, 0], + ], + [ + [0, 1, 0, -1], + [-1, -1, -1, -1], + [-1, -1, -1, -1], + [0, 2, -1, 0], + ], + [ + [-1, -1, -1, -1], + [-1, -1, -1, -1], + [-1, -1, -1, -1], + [-1, -1, -1, -1], + ], + ]; + + this.gks = [ + [0.27901, 0.44198, 0.27901], + [0.135336, 0.228569, 0.272192, 0.228569, 0.135336], + [0.086776, 0.136394, 0.178908, 0.195843, 0.178908, 0.136394, 0.086776], + [0.063327, 0.093095, 0.122589, 0.144599, 0.152781, 0.144599, 0.122589, 0.093095, 0.063327], + [ + 0.049692, + 0.069304, + 0.089767, + 0.107988, + 0.120651, + 0.125194, + 0.120651, + 0.107988, + 0.089767, + 0.069304, + 0.049692, + ], + ]; + + this.specpalette = [ + { r: 0, g: 0, b: 0, a: 255 }, + { r: 128, g: 128, b: 128, a: 255 }, + { r: 0, g: 0, b: 128, a: 255 }, + { r: 64, g: 64, b: 128, a: 255 }, + { r: 192, g: 192, b: 192, a: 255 }, + { r: 255, g: 255, b: 255, a: 255 }, + { r: 128, g: 128, b: 192, a: 255 }, + { r: 0, g: 0, b: 192, a: 255 }, + { r: 128, g: 0, b: 0, a: 255 }, + { r: 128, g: 64, b: 64, a: 255 }, + { r: 128, g: 0, b: 128, a: 255 }, + { r: 168, g: 168, b: 168, a: 255 }, + { r: 192, g: 128, b: 128, a: 255 }, + { r: 192, g: 0, b: 0, a: 255 }, + { r: 255, g: 255, b: 255, a: 255 }, + { r: 0, g: 128, b: 0, a: 255 }, + ]; + } + + imageToSVG(url, callback, options) { + options = this.checkoptions(options); + this.loadImage( + url, + (canvas) => { + callback(this.imagedataToSVG(this.getImgdata(canvas), options)); + }, + options + ); + } + + imagedataToSVG(imgd, options) { + options = this.checkoptions(options); + const td = this.imagedataToTracedata(imgd, options); + + return this.getsvgstring(td, options); + } + + imageToTracedata(url, callback, options) { + options = this.checkoptions(options); + this.loadImage( + url, + (canvas) => { + callback(this.imagedataToTracedata(this.getImgdata(canvas), options)); + }, + options + ); + } + + imagedataToTracedata(imgd, options) { + options = this.checkoptions(options); + const ii = this.colorquantization(imgd, options); + let tracedata; + if (options.layering === 0) { + tracedata = { + layers: [], + palette: ii.palette, + width: ii.array[0].length - 2, + height: ii.array.length - 2, + }; + + for (let colornum = 0; colornum < ii.palette.length; colornum += 1) { + const tracedlayer = this.batchtracepaths( + this.internodes( + this.pathscan(this.layeringstep(ii, colornum), options.pathomit), options + ), + options.ltres, + options.qtres ); + tracedata.layers.push(tracedlayer); + } + } else { + const ls = this.layering(ii); + if (options.layercontainerid) { + this.drawLayers(ls, this.specpalette, options.scale, options.layercontainerid); + } + const bps = this.batchpathscan(ls, options.pathomit); + const bis = this.batchinternodes(bps, options); + tracedata = { + layers: this.batchtracelayers(bis, options.ltres, options.qtres), + palette: ii.palette, + width: imgd.width, + height: imgd.height, + }; } - imagedataToSVG(imgd, options) { - options = this.checkoptions(options); - const td = this.imagedataToTracedata(imgd, options); - - return this.getsvgstring(td, options); + return tracedata; + } + + checkoptions(options) { + options = options || {}; + if (typeof options === 'string') { + options = options.toLowerCase(); + if (this.optionpresets[options]) { + options = this.optionpresets[options]; + } else { + options = {}; + } } - - imageToTracedata(url, callback, options) { - options = this.checkoptions(options); - this.loadImage( - url, - canvas => { - callback( - this.imagedataToTracedata(this.getImgdata(canvas), options) - ); - }, - options - ); + const ok = Object.keys(this.optionpresets['default']); + for (let k = 0; k < ok.length; k += 1) { + if (!options.hasOwnProperty(ok[k])) { + options[ok[k]] = this.optionpresets['default'][ok[k]]; + } } - imagedataToTracedata(imgd, options) { - options = this.checkoptions(options); - const ii = this.colorquantization(imgd, options); - let tracedata; - if (options.layering === 0) { - tracedata = { - layers: [], - palette: ii.palette, - width: ii.array[0].length - 2, - height: ii.array.length - 2 + return options; + } + + colorquantization(imgd, options) { + const arr = []; + let idx = 0; + let cd; + let cdl; + let ci; + const paletteacc = []; + const pixelnum = imgd.width * imgd.height; + let i; + let j; + let k; + let cnt; + let palette; + + for (j = 0; j < imgd.height + 2; j += 1) { + arr[j] = []; + for (i = 0; i < imgd.width + 2; i += 1) { + arr[j][i] = -1; + } + } + if (options.pal) { + palette = options.pal; + } else if (options.colorsampling === 0) { + palette = this.generatepalette(options.numberofcolors); + } else if (options.colorsampling === 1) { + palette = this.samplepalette(options.numberofcolors, imgd); + } else { + palette = this.samplepalette2(options.numberofcolors, imgd); + } + if (options.blurradius > 0) { + imgd = this.blur(imgd, options.blurradius, options.blurdelta); + } + for (cnt = 0; cnt < options.colorquantcycles; cnt += 1) { + if (cnt > 0) { + for (k = 0; k < palette.length; k += 1) { + if (paletteacc[k].n > 0) { + palette[k] = { + r: Math.floor(paletteacc[k].r / paletteacc[k].n), + g: Math.floor(paletteacc[k].g / paletteacc[k].n), + b: Math.floor(paletteacc[k].b / paletteacc[k].n), + a: Math.floor(paletteacc[k].a / paletteacc[k].n), }; - - for (let colornum = 0; colornum < ii.palette.length; colornum += 1) { - const tracedlayer = this.batchtracepaths( - this.internodes( - this.pathscan( - this.layeringstep(ii, colornum), - options.pathomit - ), - options - ), - options.ltres, - options.qtres - ); - tracedata.layers.push(tracedlayer); - } - } else { - const ls = this.layering(ii); - if (options.layercontainerid) { - this.drawLayers(ls, this.specpalette, options.scale, options.layercontainerid); - } - const bps = this.batchpathscan(ls, options.pathomit); - const bis = this.batchinternodes(bps, options); - tracedata = { - layers: this.batchtracelayers(bis, options.ltres, options.qtres), - palette: ii.palette, - width: imgd.width, - height: imgd.height + } + + if ( + paletteacc[k].n / pixelnum < options.mincolorratio && + cnt < options.colorquantcycles - 1 + ) { + palette[k] = { + r: Math.floor(Math.random() * 255), + g: Math.floor(Math.random() * 255), + b: Math.floor(Math.random() * 255), + a: Math.floor(Math.random() * 255), }; + } } + } + + for (i = 0; i < palette.length; i += 1) { + paletteacc[i] = { r: 0, g: 0, b: 0, a: 0, n: 0 }; + } + + for (j = 0; j < imgd.height; j += 1) { + for (i = 0; i < imgd.width; i += 1) { + idx = (j * imgd.width + i) * 4; + + ci = 0; + cdl = 1024; + for (k = 0; k < palette.length; k += 1) { + cd = + Math.abs(palette[k].r - imgd.data[idx]) + + Math.abs(palette[k].g - imgd.data[idx + 1]) + + Math.abs(palette[k].b - imgd.data[idx + 2]) + + Math.abs(palette[k].a - imgd.data[idx + 3]); + + if (cd < cdl) { + cdl = cd; + ci = k; + } + } - return tracedata; - } + paletteacc[ci].r += imgd.data[idx]; + paletteacc[ci].g += imgd.data[idx + 1]; + paletteacc[ci].b += imgd.data[idx + 2]; + paletteacc[ci].a += imgd.data[idx + 3]; + paletteacc[ci].n += 1; - checkoptions(options) { - options = options || {}; - if (typeof options === 'string') { - options = options.toLowerCase(); - if (this.optionpresets[options]) { - options = this.optionpresets[options]; - } else { - options = {}; - } - } - const ok = Object.keys(this.optionpresets['default']); - for (let k = 0; k < ok.length; k += 1) { - if (!options.hasOwnProperty(ok[k])) { - options[ok[k]] = this.optionpresets['default'][ok[k]]; - } + arr[j + 1][i + 1] = ci; } + } + } - return options; + return { array: arr, palette }; + } + + samplepalette(numberofcolors, imgd) { + let idx; + const palette = []; + for (let i = 0; i < numberofcolors; i += 1) { + idx = Math.floor((Math.random() * imgd.data.length) / 4) * 4; + palette.push({ + r: imgd.data[idx], + g: imgd.data[idx + 1], + b: imgd.data[idx + 2], + a: imgd.data[idx + 3], + }); } - colorquantization(imgd, options) { - const arr = []; - let idx = 0; - let cd; - let cdl; - let ci; - const paletteacc = []; - const pixelnum = imgd.width * imgd.height; - let i; - let j; - let k; - let cnt; - let palette; - - for (j = 0; j < imgd.height + 2; j += 1) { - arr[j] = []; - for (i = 0; i < imgd.width + 2; i += 1) { - arr[j][i] = -1; - } - } - if (options.pal) { - palette = options.pal; - } else if (options.colorsampling === 0) { - palette = this.generatepalette(options.numberofcolors); - } else if (options.colorsampling === 1) { - palette = this.samplepalette(options.numberofcolors, imgd); + return palette; + } + + samplepalette2(numberofcolors, imgd) { + let idx; + const palette = []; + const ni = Math.ceil(Math.sqrt(numberofcolors)); + const nj = Math.ceil(numberofcolors / ni); + const vx = imgd.width / (ni + 1); + const vy = imgd.height / (nj + 1); + for (let j = 0; j < nj; j += 1) { + for (let i = 0; i < ni; i += 1) { + if (palette.length === numberofcolors) { + break; } else { - palette = this.samplepalette2(options.numberofcolors, imgd); + idx = Math.floor((j + 1) * vy * imgd.width + (i + 1) * vx) * 4; + palette.push({ + r: imgd.data[idx], + g: imgd.data[idx + 1], + b: imgd.data[idx + 2], + a: imgd.data[idx + 3], + }); } - if (options.blurradius > 0) { - imgd = this.blur(imgd, options.blurradius, options.blurdelta); - } - for (cnt = 0; cnt < options.colorquantcycles; cnt += 1) { - if (cnt > 0) { - for (k = 0; k < palette.length; k += 1) { - if (paletteacc[k].n > 0) { - palette[k] = {r: Math.floor(paletteacc[k].r / paletteacc[k].n), - g: Math.floor(paletteacc[k].g / paletteacc[k].n), - b: Math.floor(paletteacc[k].b / paletteacc[k].n), - a: Math.floor(paletteacc[k].a / paletteacc[k].n)}; - } - - if ((paletteacc[k].n / pixelnum < options.mincolorratio) && (cnt < options.colorquantcycles - 1)) { - palette[k] = {r: Math.floor(Math.random() * 255), - g: Math.floor(Math.random() * 255), - b: Math.floor(Math.random() * 255), - a: Math.floor(Math.random() * 255)}; - } - } - } - - for (i = 0; i < palette.length; i += 1) { - paletteacc[i] = {r: 0, - g: 0, - b: 0, - a: 0, - n: 0}; - } - - for (j = 0; j < imgd.height; j += 1) { - for (i = 0; i < imgd.width; i += 1) { - idx = ((j * imgd.width) + i) * 4; - - ci = 0; - cdl = 1024; - for (k = 0; k < palette.length; k += 1) { - cd = Math.abs(palette[k].r - imgd.data[idx]) + - Math.abs(palette[k].g - imgd.data[idx + 1]) + - Math.abs(palette[k].b - imgd.data[idx + 2]) + - Math.abs(palette[k].a - imgd.data[idx + 3]); - - if (cd < cdl) { - cdl = cd; - ci = k; - } - } - - paletteacc[ci].r += imgd.data[idx]; - paletteacc[ci].g += imgd.data[idx + 1]; - paletteacc[ci].b += imgd.data[idx + 2]; - paletteacc[ci].a += imgd.data[idx + 3]; - paletteacc[ci].n += 1; - - arr[j + 1][i + 1] = ci; - } - } - } - - return {array: arr, - palette}; + } } - samplepalette(numberofcolors, imgd) { - let idx; - const palette = []; - for (let i = 0; i < numberofcolors; i += 1) { - idx = Math.floor(Math.random() * imgd.data.length / 4) * 4; - palette.push({r: imgd.data[idx], - g: imgd.data[idx + 1], - b: imgd.data[idx + 2], - a: imgd.data[idx + 3]}); + return palette; + } + + generatepalette(numberofcolors) { + const palette = []; + let rcnt; + let gcnt; + let bcnt; + if (numberofcolors < 8) { + const graystep = Math.floor(255 / (numberofcolors - 1)); + for (let i = 0; i < numberofcolors; i += 1) { + palette.push({ r: i * graystep, g: i * graystep, b: i * graystep, a: 255 }); + } + } else { + const colorqnum = Math.floor(Math.pow(numberofcolors, 1 / 3)); + const colorstep = Math.floor(255 / (colorqnum - 1)); + const rndnum = numberofcolors - colorqnum * colorqnum * colorqnum; + for (rcnt = 0; rcnt < colorqnum; rcnt += 1) { + for (gcnt = 0; gcnt < colorqnum; gcnt += 1) { + for (bcnt = 0; bcnt < colorqnum; bcnt += 1) { + palette.push({ r: rcnt * colorstep, g: gcnt * colorstep, b: bcnt * colorstep, a: 255 }); + } } - - return palette; + } + for (rcnt = 0; rcnt < rndnum; rcnt += 1) { + palette.push({ + r: Math.floor(Math.random() * 255), + g: Math.floor(Math.random() * 255), + b: Math.floor(Math.random() * 255), + a: Math.floor(Math.random() * 255), + }); + } } - samplepalette2(numberofcolors, imgd) { - let idx; - const palette = []; - const ni = Math.ceil(Math.sqrt(numberofcolors)); - const nj = Math.ceil(numberofcolors / ni); - const vx = imgd.width / (ni + 1); - const vy = imgd.height / (nj + 1); - for (let j = 0; j < nj; j += 1) { - for (let i = 0; i < ni; i += 1) { - if (palette.length === numberofcolors) { - break; - } else { - idx = Math.floor((((j + 1) * vy) * imgd.width) + ((i + 1) * vx)) * 4; - palette.push({r: imgd.data[idx], - g: imgd.data[idx + 1], - b: imgd.data[idx + 2], - a: imgd.data[idx + 3]}); - } - } + return palette; + } + + layering(ii) { + const layers = []; + let val = 0; + const ah = ii.array.length; + const aw = ii.array[0].length; + let n1; + let n2; + let n3; + let n4; + let n5; + let n6; + let n7; + let n8; + let i; + let j; + let k; + for (k = 0; k < ii.palette.length; k += 1) { + layers[k] = []; + for (j = 0; j < ah; j += 1) { + layers[k][j] = []; + for (i = 0; i < aw; i += 1) { + layers[k][j][i] = 0; } - - return palette; + } } - - generatepalette(numberofcolors) { - const palette = []; - let rcnt; - let gcnt; - let bcnt; - if (numberofcolors < 8) { - const graystep = Math.floor(255 / (numberofcolors - 1)); - for (let i = 0; i < numberofcolors; i += 1) { - palette.push({r: i * graystep, - g: i * graystep, - b: i * graystep, - a: 255}); - } - } else { - const colorqnum = Math.floor(Math.pow(numberofcolors, 1 / 3)); - const colorstep = Math.floor(255 / (colorqnum - 1)); - const rndnum = numberofcolors - (colorqnum * colorqnum * colorqnum); - for (rcnt = 0; rcnt < colorqnum; rcnt += 1) { - for (gcnt = 0; gcnt < colorqnum; gcnt += 1) { - for (bcnt = 0; bcnt < colorqnum; bcnt += 1) { - palette.push({r: rcnt * colorstep, - g: gcnt * colorstep, - b: bcnt * colorstep, - a: 255}); - } - } - } - for (rcnt = 0; rcnt < rndnum; rcnt += 1) { - palette.push({r: Math.floor(Math.random() * 255), - g: Math.floor(Math.random() * 255), - b: Math.floor(Math.random() * 255), - a: Math.floor(Math.random() * 255)}); - } + for (j = 1; j < ah - 1; j += 1) { + for (i = 1; i < aw - 1; i += 1) { + val = ii.array[j][i]; + + n1 = ii.array[j - 1][i - 1] === val ? 1 : 0; + n2 = ii.array[j - 1][i] === val ? 1 : 0; + n3 = ii.array[j - 1][i + 1] === val ? 1 : 0; + n4 = ii.array[j][i - 1] === val ? 1 : 0; + n5 = ii.array[j][i + 1] === val ? 1 : 0; + n6 = ii.array[j + 1][i - 1] === val ? 1 : 0; + n7 = ii.array[j + 1][i] === val ? 1 : 0; + n8 = ii.array[j + 1][i + 1] === val ? 1 : 0; + + layers[val][j + 1][i + 1] = 1 + n5 * 2 + n8 * 4 + n7 * 8; + if (!n4) { + layers[val][j + 1][i] = 0 + 2 + n7 * 4 + n6 * 8; } - - return palette; - } - - layering(ii) { - const layers = []; - let val = 0; - const ah = ii.array.length; - const aw = ii.array[0].length; - let n1; - let n2; - let n3; - let n4; - let n5; - let n6; - let n7; - let n8; - let i; - let j; - let k; - for (k = 0; k < ii.palette.length; k += 1) { - layers[k] = []; - for (j = 0; j < ah; j += 1) { - layers[k][j] = []; - for (i = 0; i < aw; i += 1) { - layers[k][j][i] = 0; - } - } + if (!n2) { + layers[val][j][i + 1] = 0 + n3 * 2 + n5 * 4 + 8; } - for (j = 1; j < ah - 1; j += 1) { - for (i = 1; i < aw - 1; i += 1) { - val = ii.array[j][i]; - - n1 = ii.array[j - 1][i - 1] === val ? 1 : 0; - n2 = ii.array[j - 1][i] === val ? 1 : 0; - n3 = ii.array[j - 1][i + 1] === val ? 1 : 0; - n4 = ii.array[j][i - 1] === val ? 1 : 0; - n5 = ii.array[j][i + 1] === val ? 1 : 0; - n6 = ii.array[j + 1][i - 1] === val ? 1 : 0; - n7 = ii.array[j + 1][i] === val ? 1 : 0; - n8 = ii.array[j + 1][i + 1] === val ? 1 : 0; - - layers[val][j + 1][i + 1] = 1 + (n5 * 2) + (n8 * 4) + (n7 * 8); - if (!n4) { - layers[val][j + 1][i] = 0 + 2 + (n7 * 4) + (n6 * 8); - } - if (!n2) { - layers[val][j][i + 1] = 0 + (n3 * 2) + (n5 * 4) + 8; - } - if (!n1) { - layers[val][j][i] = 0 + (n2 * 2) + 4 + (n4 * 8); - } - } + if (!n1) { + layers[val][j][i] = 0 + n2 * 2 + 4 + n4 * 8; } + } + } - return layers; + return layers; + } + + layeringstep(ii, cnum) { + const layer = []; + const ah = ii.array.length; + const aw = ii.array[0].length; + let i; + let j; + for (j = 0; j < ah; j += 1) { + layer[j] = []; + for (i = 0; i < aw; i += 1) { + layer[j][i] = 0; + } + } + for (j = 1; j < ah; j += 1) { + for (i = 1; i < aw; i += 1) { + layer[j][i] = + (ii.array[j - 1][i - 1] === cnum ? 1 : 0) + + (ii.array[j - 1][i] === cnum ? 2 : 0) + + (ii.array[j][i - 1] === cnum ? 8 : 0) + + (ii.array[j][i] === cnum ? 4 : 0); + } } - layeringstep(ii, cnum) { - const layer = []; - const ah = ii.array.length; - const aw = ii.array[0].length; - let i; - let j; - for (j = 0; j < ah; j += 1) { - layer[j] = []; - for (i = 0; i < aw; i += 1) { - layer[j][i] = 0; + return layer; + } + + pathscan(arr, pathomit) { + const paths = []; + let pacnt = 0; + let pcnt = 0; + let px = 0; + let py = 0; + const w = arr[0].length; + const h = arr.length; + let dir = 0; + let pathfinished = true; + let holepath = false; + let lookuprow; + for (let j = 0; j < h; j += 1) { + for (let i = 0; i < w; i += 1) { + if (arr[j][i] === 4 || arr[j][i] === 11) { + px = i; + py = j; + paths[pacnt] = {}; + paths[pacnt].points = []; + paths[pacnt].boundingbox = [px, py, px, py]; + paths[pacnt].holechildren = []; + pathfinished = false; + pcnt = 0; + holepath = arr[j][i] === 11; + dir = 1; + + while (!pathfinished) { + paths[pacnt].points[pcnt] = {}; + paths[pacnt].points[pcnt].x = px - 1; + paths[pacnt].points[pcnt].y = py - 1; + paths[pacnt].points[pcnt].t = arr[py][px]; + + if (px - 1 < paths[pacnt].boundingbox[0]) { + paths[pacnt].boundingbox[0] = px - 1; } - } - for (j = 1; j < ah; j += 1) { - for (i = 1; i < aw; i += 1) { - layer[j][i] = - (ii.array[j - 1][i - 1] === cnum ? 1 : 0) + - (ii.array[j - 1][i] === cnum ? 2 : 0) + - (ii.array[j][i - 1] === cnum ? 8 : 0) + - (ii.array[j][i] === cnum ? 4 : 0); + if (px - 1 > paths[pacnt].boundingbox[2]) { + paths[pacnt].boundingbox[2] = px - 1; + } + if (py - 1 < paths[pacnt].boundingbox[1]) { + paths[pacnt].boundingbox[1] = py - 1; + } + if (py - 1 > paths[pacnt].boundingbox[3]) { + paths[pacnt].boundingbox[3] = py - 1; } - } - - return layer; - } - pathscan(arr, pathomit) { - const paths = []; - let pacnt = 0; - let pcnt = 0; - let px = 0; - let py = 0; - const w = arr[0].length; - const h = arr.length; - let dir = 0; - let pathfinished = true; - let holepath = false; - let lookuprow; - for (let j = 0; j < h; j += 1) { - for (let i = 0; i < w; i += 1) { - if ((arr[j][i] === 4) || (arr[j][i] === 11)) { - px = i; - py = j; - paths[pacnt] = {}; - paths[pacnt].points = []; - paths[pacnt].boundingbox = [px, py, px, py]; - paths[pacnt].holechildren = []; - pathfinished = false; - pcnt = 0; - holepath = (arr[j][i] === 11); - dir = 1; - - while (!pathfinished) { - paths[pacnt].points[pcnt] = {}; - paths[pacnt].points[pcnt].x = px - 1; - paths[pacnt].points[pcnt].y = py - 1; - paths[pacnt].points[pcnt].t = arr[py][px]; - - if ((px - 1) < paths[pacnt].boundingbox[0]) { - paths[pacnt].boundingbox[0] = px - 1; - } - if ((px - 1) > paths[pacnt].boundingbox[2]) { - paths[pacnt].boundingbox[2] = px - 1; - } - if ((py - 1) < paths[pacnt].boundingbox[1]) { - paths[pacnt].boundingbox[1] = py - 1; - } - if ((py - 1) > paths[pacnt].boundingbox[3]) { - paths[pacnt].boundingbox[3] = py - 1; - } - - lookuprow = this.pathscan_combined_lookup[arr[py][px]][dir]; - arr[py][px] = lookuprow[0]; dir = lookuprow[1]; px += lookuprow[2]; py += lookuprow[3]; - - if ((px - 1 === paths[pacnt].points[0].x) && (py - 1 === paths[pacnt].points[0].y)) { - pathfinished = true; - - if (paths[pacnt].points.length < pathomit) { - paths.pop(); - } else { - paths[pacnt].isholepath = !!holepath; - - if (holepath) { - let parentidx = 0, parentbbox = [-1, -1, w + 1, h + 1]; - for (let parentcnt = 0; parentcnt < pacnt; parentcnt++) { - if ((!paths[parentcnt].isholepath) && - this.boundingboxincludes(paths[parentcnt].boundingbox, paths[pacnt].boundingbox) && - this.boundingboxincludes(parentbbox, paths[parentcnt].boundingbox) - ) { - parentidx = parentcnt; - parentbbox = paths[parentcnt].boundingbox; - } - } - paths[parentidx].holechildren.push(pacnt); - } - pacnt += 1; - } - } - pcnt += 1; + lookuprow = this.pathscan_combined_lookup[arr[py][px]][dir]; + arr[py][px] = lookuprow[0]; + dir = lookuprow[1]; + px += lookuprow[2]; + py += lookuprow[3]; + + if (px - 1 === paths[pacnt].points[0].x && py - 1 === paths[pacnt].points[0].y) { + pathfinished = true; + + if (paths[pacnt].points.length < pathomit) { + paths.pop(); + } else { + paths[pacnt].isholepath = !!holepath; + + if (holepath) { + let parentidx = 0, + parentbbox = [-1, -1, w + 1, h + 1]; + for (let parentcnt = 0; parentcnt < pacnt; parentcnt++) { + if ( + !paths[parentcnt].isholepath && + this.boundingboxincludes( + paths[parentcnt].boundingbox, + paths[pacnt].boundingbox + ) && + this.boundingboxincludes(parentbbox, paths[parentcnt].boundingbox) + ) { + parentidx = parentcnt; + parentbbox = paths[parentcnt].boundingbox; } + } + paths[parentidx].holechildren.push(pacnt); } + pacnt += 1; + } } + pcnt += 1; + } } - - return paths; + } } - boundingboxincludes(parentbbox, childbbox) { - return ((parentbbox[0] < childbbox[0]) && (parentbbox[1] < childbbox[1]) && - (parentbbox[2] > childbbox[2]) && (parentbbox[3] > childbbox[3])); + return paths; + } + + boundingboxincludes(parentbbox, childbbox) { + return ( + parentbbox[0] < childbbox[0] && + parentbbox[1] < childbbox[1] && + parentbbox[2] > childbbox[2] && + parentbbox[3] > childbbox[3] + ); + } + + batchpathscan(layers, pathomit) { + const bpaths = []; + for (const k in layers) { + if (!layers.hasOwnProperty(k)) { + continue; + } + bpaths[k] = this.pathscan(layers[k], pathomit); } - batchpathscan(layers, pathomit) { - const bpaths = []; - for (const k in layers) { - if (!layers.hasOwnProperty(k)) { - continue; - } - bpaths[k] = this.pathscan(layers[k], pathomit); + return bpaths; + } + + internodes(paths, options) { + const ins = []; + let palen = 0; + let nextidx = 0; + let nextidx2 = 0; + let previdx = 0; + let previdx2 = 0; + let pacnt; + let pcnt; + for (pacnt = 0; pacnt < paths.length; pacnt += 1) { + ins[pacnt] = {}; + ins[pacnt].points = []; + ins[pacnt].boundingbox = paths[pacnt].boundingbox; + ins[pacnt].holechildren = paths[pacnt].holechildren; + ins[pacnt].isholepath = paths[pacnt].isholepath; + palen = paths[pacnt].points.length; + + for (pcnt = 0; pcnt < palen; pcnt += 1) { + nextidx = (pcnt + 1) % palen; + nextidx2 = (pcnt + 2) % palen; + previdx = (pcnt - 1 + palen) % palen; + previdx2 = (pcnt - 2 + palen) % palen; + + if ( + options.rightangleenhance && + this.testrightangle(paths[pacnt], previdx2, previdx, pcnt, nextidx, nextidx2) + ) { + if (ins[pacnt].points.length > 0) { + ins[pacnt].points[ins[pacnt].points.length - 1].linesegment = this.getdirection( + ins[pacnt].points[ins[pacnt].points.length - 1].x, + ins[pacnt].points[ins[pacnt].points.length - 1].y, + paths[pacnt].points[pcnt].x, + paths[pacnt].points[pcnt].y + ); + } + + ins[pacnt].points.push({ + x: paths[pacnt].points[pcnt].x, + y: paths[pacnt].points[pcnt].y, + linesegment: this.getdirection( + paths[pacnt].points[pcnt].x, + paths[pacnt].points[pcnt].y, + (paths[pacnt].points[pcnt].x + paths[pacnt].points[nextidx].x) / 2, + (paths[pacnt].points[pcnt].y + paths[pacnt].points[nextidx].y) / 2 + ), + }); } - return bpaths; + ins[pacnt].points.push({ + x: (paths[pacnt].points[pcnt].x + paths[pacnt].points[nextidx].x) / 2, + y: (paths[pacnt].points[pcnt].y + paths[pacnt].points[nextidx].y) / 2, + linesegment: this.getdirection( + (paths[pacnt].points[pcnt].x + paths[pacnt].points[nextidx].x) / 2, + (paths[pacnt].points[pcnt].y + paths[pacnt].points[nextidx].y) / 2, + (paths[pacnt].points[nextidx].x + paths[pacnt].points[nextidx2].x) / 2, + (paths[pacnt].points[nextidx].y + paths[pacnt].points[nextidx2].y) / 2 + ), + }); + } } - internodes(paths, options) { - const ins = []; - let palen = 0; - let nextidx = 0; - let nextidx2 = 0; - let previdx = 0; - let previdx2 = 0; - let pacnt; - let pcnt; - for (pacnt = 0; pacnt < paths.length; pacnt += 1) { - ins[pacnt] = {}; - ins[pacnt].points = []; - ins[pacnt].boundingbox = paths[pacnt].boundingbox; - ins[pacnt].holechildren = paths[pacnt].holechildren; - ins[pacnt].isholepath = paths[pacnt].isholepath; - palen = paths[pacnt].points.length; - - for (pcnt = 0; pcnt < palen; pcnt += 1) { - nextidx = (pcnt + 1) % palen; nextidx2 = (pcnt + 2) % palen; previdx = (pcnt - 1 + palen) % palen; previdx2 = (pcnt - 2 + palen) % palen; - - if (options.rightangleenhance && this.testrightangle(paths[pacnt], previdx2, previdx, pcnt, nextidx, nextidx2)) { - if (ins[pacnt].points.length > 0) { - ins[pacnt].points[ins[pacnt].points.length - 1].linesegment = this.getdirection( - ins[pacnt].points[ins[pacnt].points.length - 1].x, - ins[pacnt].points[ins[pacnt].points.length - 1].y, - paths[pacnt].points[pcnt].x, - paths[pacnt].points[pcnt].y - ); - } + return ins; + } + + testrightangle(path, idx1, idx2, idx3, idx4, idx5) { + return ( + (path.points[idx3].x === path.points[idx1].x && + path.points[idx3].x === path.points[idx2].x && + path.points[idx3].y === path.points[idx4].y && + path.points[idx3].y === path.points[idx5].y) || + (path.points[idx3].y === path.points[idx1].y && + path.points[idx3].y === path.points[idx2].y && + path.points[idx3].x === path.points[idx4].x && + path.points[idx3].x === path.points[idx5].x) + ); + } + + getdirection(x1, y1, x2, y2) { + let val = 8; + if (x1 < x2) { + if (y1 < y2) { + val = 1; + } else if (y1 > y2) { + val = 7; + } else { + val = 0; + } + } else if (x1 > x2) { + if (y1 < y2) { + val = 3; + } else if (y1 > y2) { + val = 5; + } else { + val = 4; + } + } else if (y1 < y2) { + val = 2; + } else if (y1 > y2) { + val = 6; + } else { + val = 8; + } - ins[pacnt].points.push({ - x: paths[pacnt].points[pcnt].x, - y: paths[pacnt].points[pcnt].y, - linesegment: this.getdirection( - paths[pacnt].points[pcnt].x, - paths[pacnt].points[pcnt].y, - ((paths[pacnt].points[pcnt].x + paths[pacnt].points[nextidx].x) / 2), - ((paths[pacnt].points[pcnt].y + paths[pacnt].points[nextidx].y) / 2) - ) - }); - } + return val; + } - ins[pacnt].points.push({ - x: ((paths[pacnt].points[pcnt].x + paths[pacnt].points[nextidx].x) / 2), - y: ((paths[pacnt].points[pcnt].y + paths[pacnt].points[nextidx].y) / 2), - linesegment: this.getdirection( - ((paths[pacnt].points[pcnt].x + paths[pacnt].points[nextidx].x) / 2), - ((paths[pacnt].points[pcnt].y + paths[pacnt].points[nextidx].y) / 2), - ((paths[pacnt].points[nextidx].x + paths[pacnt].points[nextidx2].x) / 2), - ((paths[pacnt].points[nextidx].y + paths[pacnt].points[nextidx2].y) / 2) - ) - }); - } - } - - return ins; + batchinternodes(bpaths, options) { + const binternodes = []; + for (const k in bpaths) { + if (!bpaths.hasOwnProperty(k)) { + continue; + } + binternodes[k] = this.internodes(bpaths[k], options); } - testrightangle(path, idx1, idx2, idx3, idx4, idx5) { - return (((path.points[idx3].x === path.points[idx1].x) && - (path.points[idx3].x === path.points[idx2].x) && - (path.points[idx3].y === path.points[idx4].y) && - (path.points[idx3].y === path.points[idx5].y) - ) || - ((path.points[idx3].y === path.points[idx1].y) && - (path.points[idx3].y === path.points[idx2].y) && - (path.points[idx3].x === path.points[idx4].x) && - (path.points[idx3].x === path.points[idx5].x) - ) - ); + return binternodes; + } + + tracepath(path, ltres, qtres) { + let pcnt = 0; + let segtype1; + let segtype2; + let seqend; + const smp = {}; + smp.segments = []; + smp.boundingbox = path.boundingbox; + smp.holechildren = path.holechildren; + smp.isholepath = path.isholepath; + + while (pcnt < path.points.length) { + segtype1 = path.points[pcnt].linesegment; + segtype2 = -1; + seqend = pcnt + 1; + while ( + (path.points[seqend].linesegment === segtype1 || + path.points[seqend].linesegment === segtype2 || + segtype2 === -1) && + seqend < path.points.length - 1 + ) { + if (path.points[seqend].linesegment !== segtype1 && segtype2 === -1) { + segtype2 = path.points[seqend].linesegment; + } + seqend += 1; + } + if (seqend === path.points.length - 1) { + seqend = 0; + } + + smp.segments = smp.segments.concat(this.fitseq(path, ltres, qtres, pcnt, seqend)); + + if (seqend > 0) { + pcnt = seqend; + } else { + pcnt = path.points.length; + } } - getdirection(x1, y1, x2, y2) { - let val = 8; - if (x1 < x2) { - if (y1 < y2) { - val = 1; - } else if (y1 > y2) { - val = 7; - } else { - val = 0; - } - } else if (x1 > x2) { - if (y1 < y2) { - val = 3; - } else if (y1 > y2) { - val = 5; - } else { - val = 4; - } - } else if (y1 < y2) { - val = 2; - } else if (y1 > y2) { - val = 6; - } else { - val = 8; - } + return smp; + } - return val; + fitseq(path, ltres, qtres, seqstart, seqend) { + if (seqend > path.points.length || seqend < 0) { + return []; + } + let errorpoint = seqstart, + errorval = 0, + curvepass = true, + px, + py, + dist2; + let tl = seqend - seqstart; + if (tl < 0) { + tl += path.points.length; + } + let vx = (path.points[seqend].x - path.points[seqstart].x) / tl, + vy = (path.points[seqend].y - path.points[seqstart].y) / tl; + let pcnt = (seqstart + 1) % path.points.length, + pl; + while (pcnt != seqend) { + pl = pcnt - seqstart; + if (pl < 0) { + pl += path.points.length; + } + px = path.points[seqstart].x + vx * pl; + py = path.points[seqstart].y + vy * pl; + dist2 = + (path.points[pcnt].x - px) * (path.points[pcnt].x - px) + + (path.points[pcnt].y - py) * (path.points[pcnt].y - py); + if (dist2 > ltres) { + curvepass = false; + } + if (dist2 > errorval) { + errorpoint = pcnt; + errorval = dist2; + } + pcnt = (pcnt + 1) % path.points.length; + } + if (curvepass) { + return [ + { + type: 'L', + x1: path.points[seqstart].x, + y1: path.points[seqstart].y, + x2: path.points[seqend].x, + y2: path.points[seqend].y, + }, + ]; + } + const fitpoint = errorpoint; + curvepass = true; + errorval = 0; + let t = (fitpoint - seqstart) / tl, + t1 = (1 - t) * (1 - t), + t2 = 2 * (1 - t) * t, + t3 = t * t; + let cpx = + (t1 * path.points[seqstart].x + t3 * path.points[seqend].x - path.points[fitpoint].x) / -t2, + cpy = + (t1 * path.points[seqstart].y + t3 * path.points[seqend].y - path.points[fitpoint].y) / -t2; + pcnt = seqstart + 1; + while (pcnt != seqend) { + t = (pcnt - seqstart) / tl; + t1 = (1 - t) * (1 - t); + t2 = 2 * (1 - t) * t; + t3 = t * t; + px = t1 * path.points[seqstart].x + t2 * cpx + t3 * path.points[seqend].x; + py = t1 * path.points[seqstart].y + t2 * cpy + t3 * path.points[seqend].y; + dist2 = + (path.points[pcnt].x - px) * (path.points[pcnt].x - px) + + (path.points[pcnt].y - py) * (path.points[pcnt].y - py); + if (dist2 > qtres) { + curvepass = false; + } + if (dist2 > errorval) { + errorpoint = pcnt; + errorval = dist2; + } + pcnt = (pcnt + 1) % path.points.length; + } + if (curvepass) { + return [ + { + type: 'Q', + x1: path.points[seqstart].x, + y1: path.points[seqstart].y, + x2: cpx, + y2: cpy, + x3: path.points[seqend].x, + y3: path.points[seqend].y, + }, + ]; + } + const splitpoint = fitpoint; + + return this.fitseq(path, ltres, qtres, seqstart, splitpoint).concat( + this.fitseq(path, ltres, qtres, splitpoint, seqend) + ); + } + + batchtracepaths(internodepaths, ltres, qtres) { + const btracedpaths = []; + for (const k in internodepaths) { + if (!internodepaths.hasOwnProperty(k)) { + continue; + } + btracedpaths.push(this.tracepath(internodepaths[k], ltres, qtres)); } - batchinternodes(bpaths, options) { - const binternodes = []; - for (const k in bpaths) { - if (!bpaths.hasOwnProperty(k)) { - continue; - } - binternodes[k] = this.internodes(bpaths[k], options); - } + return btracedpaths; + } - return binternodes; + batchtracelayers(binternodes, ltres, qtres) { + const btbis = []; + for (const k in binternodes) { + if (!binternodes.hasOwnProperty(k)) { + continue; + } + btbis[k] = this.batchtracepaths(binternodes[k], ltres, qtres); } - tracepath(path, ltres, qtres) { - let pcnt = 0; - let segtype1; - let segtype2; - let seqend; - const smp = {}; - smp.segments = []; - smp.boundingbox = path.boundingbox; - smp.holechildren = path.holechildren; - smp.isholepath = path.isholepath; - - while (pcnt < path.points.length) { - segtype1 = path.points[pcnt].linesegment; - segtype2 = -1; - seqend = pcnt + 1; - while ( - ((path.points[seqend].linesegment === segtype1) || (path.points[seqend].linesegment === segtype2) || (segtype2 === -1)) && (seqend < path.points.length - 1)) { - if ((path.points[seqend].linesegment !== segtype1) && (segtype2 === -1)) { - segtype2 = path.points[seqend].linesegment; - } - seqend += 1; - } - if (seqend === path.points.length - 1) { - seqend = 0; - } + return btbis; + } - smp.segments = smp.segments.concat(this.fitseq(path, ltres, qtres, pcnt, seqend)); + roundtodec(val, places) { + return Number(val.toFixed(places)); + } - if (seqend > 0) { - pcnt = seqend; - } else { - pcnt = path.points.length; - } - } - - return smp; + svgpathstring(tracedata, lnum, pathnum, options) { + let layer = tracedata.layers[lnum], + smp = layer[pathnum], + str = '', + pcnt; + if (options.linefilter && smp.segments.length < 3) { + return str; } - - fitseq(path, ltres, qtres, seqstart, seqend) { - if ((seqend > path.points.length) || (seqend < 0)) { - return []; - } - let errorpoint = seqstart, errorval = 0, curvepass = true, px, py, dist2; - let tl = (seqend - seqstart); if (tl < 0) { - tl += path.points.length; - } - let vx = (path.points[seqend].x - path.points[seqstart].x) / tl, - vy = (path.points[seqend].y - path.points[seqstart].y) / tl; - let pcnt = (seqstart + 1) % path.points.length, pl; - while (pcnt != seqend) { - pl = pcnt - seqstart; if (pl < 0) { - pl += path.points.length; - } - px = path.points[seqstart].x + vx * pl; py = path.points[seqstart].y + vy * pl; - dist2 = (path.points[pcnt].x - px) * (path.points[pcnt].x - px) + (path.points[pcnt].y - py) * (path.points[pcnt].y - py); - if (dist2 > ltres) { - curvepass = false; - } - if (dist2 > errorval) { - errorpoint = pcnt; errorval = dist2; - } - pcnt = (pcnt + 1) % path.points.length; + str = `'; + if (options.lcpr || options.qcpr) { + for (pcnt = 0; pcnt < smp.segments.length; pcnt++) { + if (smp.segments[pcnt].hasOwnProperty('x3') && options.qcpr) { + str += ``; + str += ``; + str += ``; + str += ``; } - str = ``; } - for (var hcnt = 0; hcnt < smp.holechildren.length; hcnt++) { - var hsmp = layer[smp.holechildren[hcnt]]; - - if (options.roundcoords === -1) { - if (hsmp.segments[hsmp.segments.length - 1].hasOwnProperty('x3')) { - str += `M ${hsmp.segments[hsmp.segments.length - 1].x3 * options.scale} ${hsmp.segments[hsmp.segments.length - 1].y3 * options.scale} `; - } else { - str += `M ${hsmp.segments[hsmp.segments.length - 1].x2 * options.scale} ${hsmp.segments[hsmp.segments.length - 1].y2 * options.scale} `; - } - for (pcnt = hsmp.segments.length - 1; pcnt >= 0; pcnt--) { - str += `${hsmp.segments[pcnt].type} `; - if (hsmp.segments[pcnt].hasOwnProperty('x3')) { - str += `${hsmp.segments[pcnt].x2 * options.scale} ${hsmp.segments[pcnt].y2 * options.scale} `; - } - str += `${hsmp.segments[pcnt].x1 * options.scale} ${hsmp.segments[pcnt].y1 * options.scale} `; - } - } else { - if (hsmp.segments[hsmp.segments.length - 1].hasOwnProperty('x3')) { - str += `M ${this.roundtodec(hsmp.segments[hsmp.segments.length - 1].x3 * options.scale)} ${this.roundtodec(hsmp.segments[hsmp.segments.length - 1].y3 * options.scale)} `; - } else { - str += `M ${this.roundtodec(hsmp.segments[hsmp.segments.length - 1].x2 * options.scale)} ${this.roundtodec(hsmp.segments[hsmp.segments.length - 1].y2 * options.scale)} `; - } - for (pcnt = hsmp.segments.length - 1; pcnt >= 0; pcnt--) { - str += `${hsmp.segments[pcnt].type} `; - if (hsmp.segments[pcnt].hasOwnProperty('x3')) { - str += `${this.roundtodec(hsmp.segments[pcnt].x2 * options.scale)} ${this.roundtodec(hsmp.segments[pcnt].y2 * options.scale)} `; - } - str += `${this.roundtodec(hsmp.segments[pcnt].x1 * options.scale)} ${this.roundtodec(hsmp.segments[pcnt].y1 * options.scale)} `; - } - } - str += 'Z '; + } + + for (var hcnt = 0; hcnt < smp.holechildren.length; hcnt++) { + var hsmp = layer[smp.holechildren[hcnt]]; + for (pcnt = 0; pcnt < hsmp.segments.length; pcnt++) { + if (hsmp.segments[pcnt].hasOwnProperty('x3') && options.qcpr) { + str += ``; + str += ``; + str += ``; + str += ``; + } + if (!hsmp.segments[pcnt].hasOwnProperty('x3') && options.lcpr) { + str += ``; + } } - str += '" />'; - if (options.lcpr || options.qcpr) { - for (pcnt = 0; pcnt < smp.segments.length; pcnt++) { - if (smp.segments[pcnt].hasOwnProperty('x3') && options.qcpr) { - str += ``; - str += ``; - str += ``; - str += ``; - } - if ((!smp.segments[pcnt].hasOwnProperty('x3')) && options.lcpr) { - str += ``; - } - } - - for (var hcnt = 0; hcnt < smp.holechildren.length; hcnt++) { - var hsmp = layer[smp.holechildren[hcnt]]; - for (pcnt = 0; pcnt < hsmp.segments.length; pcnt++) { - if (hsmp.segments[pcnt].hasOwnProperty('x3') && options.qcpr) { - str += ``; - str += ``; - str += ``; - str += ``; - } - if ((!hsmp.segments[pcnt].hasOwnProperty('x3')) && options.lcpr) { - str += ``; - } - } - } - } - - return str; + } } - getsvgstring(tracedata, options) { - options = this.checkoptions(options); - const w = tracedata.width * options.scale; - const h = tracedata.height * options.scale; - - let svgstr = ``; - for (let lcnt = 0; lcnt < tracedata.layers.length; lcnt += 1) { - for (let pcnt = 0; pcnt < tracedata.layers[lcnt].length; pcnt += 1) { - if (!tracedata.layers[lcnt][pcnt].isholepath) { - svgstr += this.svgpathstring(tracedata, lcnt, pcnt, options); - } - } + return str; + } + + getsvgstring(tracedata, options) { + options = this.checkoptions(options); + const w = tracedata.width * options.scale; + const h = tracedata.height * options.scale; + + let svgstr = ``; + for (let lcnt = 0; lcnt < tracedata.layers.length; lcnt += 1) { + for (let pcnt = 0; pcnt < tracedata.layers[lcnt].length; pcnt += 1) { + if (!tracedata.layers[lcnt][pcnt].isholepath) { + svgstr += this.svgpathstring(tracedata, lcnt, pcnt, options); } - svgstr += ''; - - return svgstr; + } } - - compareNumbers(a, b) { - return a - b; + svgstr += ''; + + return svgstr; + } + + compareNumbers(a, b) { + return a - b; + } + + torgbastr(c) { + return `rgba(${c.r},${c.g},${c.b},${c.a})`; + } + + tosvgcolorstr(c, options) { + return `fill="rgb(${c.r},${c.g},${c.b})" stroke="rgb(${c.r},${c.g},${c.b})" stroke-width="${ + options.strokewidth + }" opacity="${c.a / 255.0}" `; + } + + appendSVGString(svgstr, parentid) { + let div; + if (parentid) { + div = document.getElementById(parentid); + if (!div) { + div = document.createElement('div'); + div.id = parentid; + document.body.appendChild(div); + } + } else { + div = document.createElement('div'); + document.body.appendChild(div); } - - torgbastr(c) { - return `rgba(${c.r},${c.g},${c.b},${c.a})`; + div.innerHTML += svgstr; + } + + blur(imgd, radius, delta) { + let i, j, k, d, idx, racc, gacc, bacc, aacc, wacc; + const imgd2 = { width: imgd.width, height: imgd.height, data: [] }; + radius = Math.floor(radius); + if (radius < 1) { + return imgd; } - - tosvgcolorstr(c, options) { - return `fill="rgb(${c.r},${c.g},${c.b})" stroke="rgb(${c.r},${c.g},${c.b})" stroke-width="${options.strokewidth}" opacity="${c.a / 255.0}" `; + if (radius > 5) { + radius = 5; } - - appendSVGString(svgstr, parentid) { - let div; - if (parentid) { - div = document.getElementById(parentid); - if (!div) { - div = document.createElement('div'); - div.id = parentid; - document.body.appendChild(div); - } - } else { - div = document.createElement('div'); - document.body.appendChild(div); - } - div.innerHTML += svgstr; + delta = Math.abs(delta); + if (delta > 1024) { + delta = 1024; } - - blur(imgd, radius, delta) { - let i, j, k, d, idx, racc, gacc, bacc, aacc, wacc; - const imgd2 = {width: imgd.width, - height: imgd.height, - data: []}; - radius = Math.floor(radius); if (radius < 1) { - return imgd; - } if (radius > 5) { - radius = 5; - } delta = Math.abs(delta); if (delta > 1024) { - delta = 1024; + const thisgk = this.gks[radius - 1]; + for (j = 0; j < imgd.height; j++) { + for (i = 0; i < imgd.width; i++) { + racc = 0; + gacc = 0; + bacc = 0; + aacc = 0; + wacc = 0; + + for (k = -radius; k < radius + 1; k++) { + if (i + k > 0 && i + k < imgd.width) { + idx = (j * imgd.width + i + k) * 4; + racc += imgd.data[idx] * thisgk[k + radius]; + gacc += imgd.data[idx + 1] * thisgk[k + radius]; + bacc += imgd.data[idx + 2] * thisgk[k + radius]; + aacc += imgd.data[idx + 3] * thisgk[k + radius]; + wacc += thisgk[k + radius]; + } } - const thisgk = this.gks[radius - 1]; - for (j = 0; j < imgd.height; j++) { - for (i = 0; i < imgd.width; i++) { - racc = 0; gacc = 0; bacc = 0; aacc = 0; wacc = 0; - - for (k = -radius; k < radius + 1; k++) { - if ((i + k > 0) && (i + k < imgd.width)) { - idx = (j * imgd.width + i + k) * 4; - racc += imgd.data[idx] * thisgk[k + radius]; - gacc += imgd.data[idx + 1] * thisgk[k + radius]; - bacc += imgd.data[idx + 2] * thisgk[k + radius]; - aacc += imgd.data[idx + 3] * thisgk[k + radius]; - wacc += thisgk[k + radius]; - } - } - idx = (j * imgd.width + i) * 4; - imgd2.data[idx] = Math.floor(racc / wacc); - imgd2.data[idx + 1] = Math.floor(gacc / wacc); - imgd2.data[idx + 2] = Math.floor(bacc / wacc); - imgd2.data[idx + 3] = Math.floor(aacc / wacc); - } - } - const himgd = new Uint8ClampedArray(imgd2.data); - for (j = 0; j < imgd.height; j++) { - for (i = 0; i < imgd.width; i++) { - racc = 0; gacc = 0; bacc = 0; aacc = 0; wacc = 0; - - for (k = -radius; k < radius + 1; k++) { - if ((j + k > 0) && (j + k < imgd.height)) { - idx = ((j + k) * imgd.width + i) * 4; - racc += himgd[idx] * thisgk[k + radius]; - gacc += himgd[idx + 1] * thisgk[k + radius]; - bacc += himgd[idx + 2] * thisgk[k + radius]; - aacc += himgd[idx + 3] * thisgk[k + radius]; - wacc += thisgk[k + radius]; - } - } - - idx = (j * imgd.width + i) * 4; - imgd2.data[idx] = Math.floor(racc / wacc); - imgd2.data[idx + 1] = Math.floor(gacc / wacc); - imgd2.data[idx + 2] = Math.floor(bacc / wacc); - imgd2.data[idx + 3] = Math.floor(aacc / wacc); - } - } - for (j = 0; j < imgd.height; j++) { - for (i = 0; i < imgd.width; i++) { - idx = (j * imgd.width + i) * 4; - - d = Math.abs(imgd2.data[idx] - imgd.data[idx]) + Math.abs(imgd2.data[idx + 1] - imgd.data[idx + 1]) + - Math.abs(imgd2.data[idx + 2] - imgd.data[idx + 2]) + Math.abs(imgd2.data[idx + 3] - imgd.data[idx + 3]); - - if (d > delta) { - imgd2.data[idx] = imgd.data[idx]; - imgd2.data[idx + 1] = imgd.data[idx + 1]; - imgd2.data[idx + 2] = imgd.data[idx + 2]; - imgd2.data[idx + 3] = imgd.data[idx + 3]; - } - } + idx = (j * imgd.width + i) * 4; + imgd2.data[idx] = Math.floor(racc / wacc); + imgd2.data[idx + 1] = Math.floor(gacc / wacc); + imgd2.data[idx + 2] = Math.floor(bacc / wacc); + imgd2.data[idx + 3] = Math.floor(aacc / wacc); + } + } + const himgd = new Uint8ClampedArray(imgd2.data); + for (j = 0; j < imgd.height; j++) { + for (i = 0; i < imgd.width; i++) { + racc = 0; + gacc = 0; + bacc = 0; + aacc = 0; + wacc = 0; + + for (k = -radius; k < radius + 1; k++) { + if (j + k > 0 && j + k < imgd.height) { + idx = ((j + k) * imgd.width + i) * 4; + racc += himgd[idx] * thisgk[k + radius]; + gacc += himgd[idx + 1] * thisgk[k + radius]; + bacc += himgd[idx + 2] * thisgk[k + radius]; + aacc += himgd[idx + 3] * thisgk[k + radius]; + wacc += thisgk[k + radius]; + } } - return imgd2; + idx = (j * imgd.width + i) * 4; + imgd2.data[idx] = Math.floor(racc / wacc); + imgd2.data[idx + 1] = Math.floor(gacc / wacc); + imgd2.data[idx + 2] = Math.floor(bacc / wacc); + imgd2.data[idx + 3] = Math.floor(aacc / wacc); + } } - - loadImage(url, callback, options) { - const img = new Image(); - if (options && options.corsenabled) { - img.crossOrigin = 'Anonymous'; + for (j = 0; j < imgd.height; j++) { + for (i = 0; i < imgd.width; i++) { + idx = (j * imgd.width + i) * 4; + + d = + Math.abs(imgd2.data[idx] - imgd.data[idx]) + + Math.abs(imgd2.data[idx + 1] - imgd.data[idx + 1]) + + Math.abs(imgd2.data[idx + 2] - imgd.data[idx + 2]) + + Math.abs(imgd2.data[idx + 3] - imgd.data[idx + 3]); + + if (d > delta) { + imgd2.data[idx] = imgd.data[idx]; + imgd2.data[idx + 1] = imgd.data[idx + 1]; + imgd2.data[idx + 2] = imgd.data[idx + 2]; + imgd2.data[idx + 3] = imgd.data[idx + 3]; } - img.src = url; - img.onload = function() { - const canvas = document.createElement('canvas'); - canvas.width = img.width; - canvas.height = img.height; - const context = canvas.getContext('2d'); - context.drawImage(img, 0, 0); - callback(canvas); - }; + } } - getImgdata(canvas) { - const context = canvas.getContext('2d'); + return imgd2; + } - return context.getImageData(0, 0, canvas.width, canvas.height); + loadImage(url, callback, options) { + const img = new Image(); + if (options && options.corsenabled) { + img.crossOrigin = 'Anonymous'; } - - drawLayers(layers, palette, scale, parentid) { - scale = scale || 1; - let w, h, i, j, k; - let div; - if (parentid) { - div = document.getElementById(parentid); - if (!div) { - div = document.createElement('div'); - div.id = parentid; - document.body.appendChild(div); - } - } else { - div = document.createElement('div'); - document.body.appendChild(div); + img.src = url; + img.onload = function () { + const canvas = document.createElement('canvas'); + canvas.width = img.width; + canvas.height = img.height; + const context = canvas.getContext('2d'); + context.drawImage(img, 0, 0); + callback(canvas); + }; + } + + getImgdata(canvas) { + const context = canvas.getContext('2d'); + + return context.getImageData(0, 0, canvas.width, canvas.height); + } + + drawLayers(layers, palette, scale, parentid) { + scale = scale || 1; + let w, h, i, j, k; + let div; + if (parentid) { + div = document.getElementById(parentid); + if (!div) { + div = document.createElement('div'); + div.id = parentid; + document.body.appendChild(div); + } + } else { + div = document.createElement('div'); + document.body.appendChild(div); + } + for (k in layers) { + if (!layers.hasOwnProperty(k)) { + continue; + } + + w = layers[k][0].length; + h = layers[k].length; + + const canvas = document.createElement('canvas'); + canvas.width = w * scale; + canvas.height = h * scale; + const context = canvas.getContext('2d'); + + for (j = 0; j < h; j += 1) { + for (i = 0; i < w; i += 1) { + context.fillStyle = this.torgbastr(palette[layers[k][j][i] % palette.length]); + context.fillRect(i * scale, j * scale, scale, scale); } - for (k in layers) { - if (!layers.hasOwnProperty(k)) { - continue; - } - - w = layers[k][0].length; - h = layers[k].length; + } - const canvas = document.createElement('canvas'); - canvas.width = w * scale; - canvas.height = h * scale; - const context = canvas.getContext('2d'); - - for (j = 0; j < h; j += 1) { - for (i = 0; i < w; i += 1) { - context.fillStyle = this.torgbastr(palette[layers[k][j][i] % palette.length]); - context.fillRect(i * scale, j * scale, scale, scale); - } - } - - div.appendChild(canvas); - } + div.appendChild(canvas); } + } } diff --git a/src/js/helper/selectionModifyHelper.js b/src/js/helper/selectionModifyHelper.js index 725234db9..3b8e378e9 100644 --- a/src/js/helper/selectionModifyHelper.js +++ b/src/js/helper/selectionModifyHelper.js @@ -3,7 +3,7 @@ * @fileoverview Selection modification helper */ -import {extend} from 'tui-code-snippet/src/js/object'; +import { extend } from 'tui-code-snippet/src/js/object'; /** * Cached selection's info @@ -18,7 +18,7 @@ let cachedUndoDataForChangeDimension = null; * @private */ export function setCachedUndoDataForDimension(undoData) { - cachedUndoDataForChangeDimension = undoData; + cachedUndoDataForChangeDimension = undoData; } /** @@ -27,7 +27,7 @@ export function setCachedUndoDataForDimension(undoData) { * @private */ export function getCachedUndoDataForDimension() { - return cachedUndoDataForChangeDimension; + return cachedUndoDataForChangeDimension; } /** @@ -38,32 +38,32 @@ export function getCachedUndoDataForDimension() { * @private */ export function makeSelectionUndoData(obj, undoDatumMaker) { - let undoData; + let undoData; - if (obj.type === 'activeSelection') { - undoData = obj.getObjects().map(item => { - const {angle, left, top, scaleX, scaleY, width, height} = item; + if (obj.type === 'activeSelection') { + undoData = obj.getObjects().map((item) => { + const { angle, left, top, scaleX, scaleY, width, height } = item; - obj.realizeTransform(item); - const result = undoDatumMaker(item); + obj.realizeTransform(item); + const result = undoDatumMaker(item); - item.set({ - angle, - left, - top, - width, - height, - scaleX, - scaleY - }); + item.set({ + angle, + left, + top, + width, + height, + scaleX, + scaleY, + }); - return result; - }); - } else { - undoData = [undoDatumMaker(obj)]; - } + return result; + }); + } else { + undoData = [undoDatumMaker(obj)]; + } - return undoData; + return undoData; } /** @@ -75,7 +75,8 @@ export function makeSelectionUndoData(obj, undoDatumMaker) { * @private */ export function makeSelectionUndoDatum(id, obj, isSelection) { - return isSelection ? { + return isSelection + ? { id, width: obj.width, height: obj.height, @@ -83,6 +84,7 @@ export function makeSelectionUndoDatum(id, obj, isSelection) { left: obj.left, angle: obj.angle, scaleX: obj.scaleX, - scaleY: obj.scaleY - } : extend({id}, obj); + scaleY: obj.scaleY, + } + : extend({ id }, obj); } diff --git a/src/js/helper/shapeFilterFillHelper.js b/src/js/helper/shapeFilterFillHelper.js index 740caab6e..40b9c51c7 100644 --- a/src/js/helper/shapeFilterFillHelper.js +++ b/src/js/helper/shapeFilterFillHelper.js @@ -2,17 +2,17 @@ * @author NHN. FE Development Team * @fileoverview Shape resize helper */ -import {forEach, map, extend} from 'tui-code-snippet'; -import {capitalizeString, flipObject, setCustomProperty, getCustomProperty} from '../util'; +import { forEach, map, extend } from 'tui-code-snippet'; +import { capitalizeString, flipObject, setCustomProperty, getCustomProperty } from '../util'; import resizeHelper from '../helper/shapeResizeHelper'; const FILTER_OPTION_MAP = { - 'pixelate': 'blocksize', - 'blur': 'blur' + pixelate: 'blocksize', + blur: 'blur', }; const POSITION_DIMENSION_MAP = { - x: 'width', - y: 'height' + x: 'width', + y: 'height', }; const FILTER_NAME_VALUE_MAP = flipObject(FILTER_OPTION_MAP); @@ -31,10 +31,10 @@ let cachedCanvasImageElement = null; * @private */ export function getFillImageFromShape(shapeObj) { - const {patternSourceCanvas} = getCustomProperty(shapeObj, 'patternSourceCanvas'); - const [fillImage] = patternSourceCanvas.getObjects(); + const { patternSourceCanvas } = getCustomProperty(shapeObj, 'patternSourceCanvas'); + const [fillImage] = patternSourceCanvas.getObjects(); - return fillImage; + return fillImage; } /** @@ -43,46 +43,46 @@ export function getFillImageFromShape(shapeObj) { * @private */ export function rePositionFilterTypeFillImage(shapeObj) { - const {angle, flipX, flipY} = shapeObj; - const fillImage = getFillImageFromShape(shapeObj); - const rotatedShapeCornerDimension = getRotatedDimension(shapeObj); - const {right, bottom} = rotatedShapeCornerDimension; - let {width, height} = rotatedShapeCornerDimension; - const diffLeft = (width - shapeObj.width) / 2; - const diffTop = (height - shapeObj.height) / 2; - const cropX = shapeObj.left - (shapeObj.width / 2) - diffLeft; - const cropY = shapeObj.top - (shapeObj.height / 2) - diffTop; - let left = (width / 2) - diffLeft; - let top = (height / 2) - diffTop; - const fillImageMaxSize = Math.max(width, height) + Math.max(diffLeft, diffTop); - - ([left, top, width, height] = calculateFillImageDimensionOutsideCanvas({ - shapeObj, - left, - top, - width, - height, - cropX, - cropY, - flipX, - flipY, - right, - bottom - })); - - fillImage.set({ - angle: flipX === flipY ? -angle : angle, - left, - top, - width, - height, - cropX, - cropY, - flipX, - flipY - }); - - setCustomProperty(fillImage, {fillImageMaxSize}); + const { angle, flipX, flipY } = shapeObj; + const fillImage = getFillImageFromShape(shapeObj); + const rotatedShapeCornerDimension = getRotatedDimension(shapeObj); + const { right, bottom } = rotatedShapeCornerDimension; + let { width, height } = rotatedShapeCornerDimension; + const diffLeft = (width - shapeObj.width) / 2; + const diffTop = (height - shapeObj.height) / 2; + const cropX = shapeObj.left - shapeObj.width / 2 - diffLeft; + const cropY = shapeObj.top - shapeObj.height / 2 - diffTop; + let left = width / 2 - diffLeft; + let top = height / 2 - diffTop; + const fillImageMaxSize = Math.max(width, height) + Math.max(diffLeft, diffTop); + + [left, top, width, height] = calculateFillImageDimensionOutsideCanvas({ + shapeObj, + left, + top, + width, + height, + cropX, + cropY, + flipX, + flipY, + right, + bottom, + }); + + fillImage.set({ + angle: flipX === flipY ? -angle : angle, + left, + top, + width, + height, + cropX, + cropY, + flipX, + flipY, + }); + + setCustomProperty(fillImage, { fillImageMaxSize }); } /** @@ -91,13 +91,13 @@ export function rePositionFilterTypeFillImage(shapeObj) { * @returns {object} */ export function makeFilterOptionFromFabricImage(imageObject) { - return map(imageObject.filters, filter => { - const [key] = Object.keys(filter); + return map(imageObject.filters, (filter) => { + const [key] = Object.keys(filter); - return { - [FILTER_NAME_VALUE_MAP[key]]: filter[key] - }; - }); + return { + [FILTER_NAME_VALUE_MAP[key]]: filter[key], + }; + }); } /** @@ -115,42 +115,53 @@ export function makeFilterOptionFromFabricImage(imageObject) { * @returns {Object} */ function calculateFillImageDimensionOutsideCanvas({ - shapeObj, left, top, width, height, cropX, cropY, flipX, flipY, right, bottom + shapeObj, + left, + top, + width, + height, + cropX, + cropY, + flipX, + flipY, + right, + bottom, }) { - const overflowAreaPositionFixer = (type, outDistance, imageLeft, imageTop) => calculateDistanceOverflowPart({ - type, - outDistance, - shapeObj, - flipX, - flipY, - left: imageLeft, - top: imageTop + const overflowAreaPositionFixer = (type, outDistance, imageLeft, imageTop) => + calculateDistanceOverflowPart({ + type, + outDistance, + shapeObj, + flipX, + flipY, + left: imageLeft, + top: imageTop, }); - const [originalWidth, originalHeight] = [width, height]; - - ([left, top, width, height] = calculateDimensionLeftTopEdge(overflowAreaPositionFixer, { - left, - top, - width, - height, - cropX, - cropY - })); - - ([left, top, width, height] = calculateDimensionRightBottomEdge(overflowAreaPositionFixer, { - left, - top, - insideCanvasRealImageWidth: width, - insideCanvasRealImageHeight: height, - right, - bottom, - cropX, - cropY, - originalWidth, - originalHeight - })); - - return [left, top, width, height]; + const [originalWidth, originalHeight] = [width, height]; + + [left, top, width, height] = calculateDimensionLeftTopEdge(overflowAreaPositionFixer, { + left, + top, + width, + height, + cropX, + cropY, + }); + + [left, top, width, height] = calculateDimensionRightBottomEdge(overflowAreaPositionFixer, { + left, + top, + insideCanvasRealImageWidth: width, + insideCanvasRealImageHeight: height, + right, + bottom, + cropX, + cropY, + originalWidth, + originalHeight, + }); + + return [left, top, width, height]; } /** @@ -171,42 +182,43 @@ function calculateFillImageDimensionOutsideCanvas({ * @returns {Object} */ function calculateDimensionRightBottomEdge( - overflowAreaPositionFixer, { - left, - top, - insideCanvasRealImageWidth, - insideCanvasRealImageHeight, - right, - bottom, - cropX, - cropY, - originalWidth, - originalHeight - } + overflowAreaPositionFixer, + { + left, + top, + insideCanvasRealImageWidth, + insideCanvasRealImageHeight, + right, + bottom, + cropX, + cropY, + originalWidth, + originalHeight, + } ) { - let [width, height] = [insideCanvasRealImageWidth, insideCanvasRealImageHeight]; - const {width: canvasWidth, height: canvasHeight} = cachedCanvasImageElement; - - if (right > canvasWidth && cropX > 0) { - width = originalWidth - Math.abs(right - canvasWidth); - } - if (bottom > canvasHeight && cropY > 0) { - height = originalHeight - Math.abs(bottom - canvasHeight); + let [width, height] = [insideCanvasRealImageWidth, insideCanvasRealImageHeight]; + const { width: canvasWidth, height: canvasHeight } = cachedCanvasImageElement; + + if (right > canvasWidth && cropX > 0) { + width = originalWidth - Math.abs(right - canvasWidth); + } + if (bottom > canvasHeight && cropY > 0) { + height = originalHeight - Math.abs(bottom - canvasHeight); + } + + const diff = { + x: (insideCanvasRealImageWidth - width) / 2, + y: (insideCanvasRealImageHeight - height) / 2, + }; + + forEach(['x', 'y'], (type) => { + const cropDistance2 = diff[type]; + if (cropDistance2 > 0) { + [left, top] = overflowAreaPositionFixer(type, cropDistance2, left, top); } + }); - const diff = { - x: (insideCanvasRealImageWidth - width) / 2, - y: (insideCanvasRealImageHeight - height) / 2 - }; - - forEach(['x', 'y'], type => { - const cropDistance2 = diff[type]; - if (cropDistance2 > 0) { - [left, top] = overflowAreaPositionFixer(type, cropDistance2, left, top); - } - }); - - return [left, top, width, height]; + return [left, top, width, height]; } /** @@ -222,29 +234,32 @@ function calculateDimensionRightBottomEdge( * @param {number} cropY - image cropY * @returns {Object} */ -function calculateDimensionLeftTopEdge(overflowAreaPositionFixer, {left, top, width, height, cropX, cropY}) { - const dimension = { - width, - height - }; +function calculateDimensionLeftTopEdge( + overflowAreaPositionFixer, + { left, top, width, height, cropX, cropY } +) { + const dimension = { + width, + height, + }; - forEach(['x', 'y'], type => { - const cropDistance = type === 'x' ? cropX : cropY; - const compareSize = dimension[POSITION_DIMENSION_MAP[type]]; - const standardSize = cachedCanvasImageElement[POSITION_DIMENSION_MAP[type]]; + forEach(['x', 'y'], (type) => { + const cropDistance = type === 'x' ? cropX : cropY; + const compareSize = dimension[POSITION_DIMENSION_MAP[type]]; + const standardSize = cachedCanvasImageElement[POSITION_DIMENSION_MAP[type]]; - if (compareSize > standardSize) { - const outDistance = (compareSize - standardSize) / 2; + if (compareSize > standardSize) { + const outDistance = (compareSize - standardSize) / 2; - dimension[POSITION_DIMENSION_MAP[type]] = standardSize; - [left, top] = overflowAreaPositionFixer(type, outDistance, left, top); - } - if (cropDistance < 0) { - [left, top] = overflowAreaPositionFixer(type, cropDistance, left, top); - } - }); + dimension[POSITION_DIMENSION_MAP[type]] = standardSize; + [left, top] = overflowAreaPositionFixer(type, outDistance, left, top); + } + if (cropDistance < 0) { + [left, top] = overflowAreaPositionFixer(type, cropDistance, left, top); + } + }); - return [left, top, dimension.width, dimension.height]; + return [left, top, dimension.width, dimension.height]; } /** @@ -255,20 +270,20 @@ function calculateDimensionLeftTopEdge(overflowAreaPositionFixer, {left, top, wi * @returns {Object} */ export function makeFillPatternForFilter(canvasImage, filterOption, patternSourceCanvas) { - const copiedCanvasElement = getCachedCanvasImageElement(canvasImage); - const fillImage = makeFillImage(copiedCanvasElement, canvasImage.angle, filterOption); - patternSourceCanvas.add(fillImage); - - const fabricProperty = { - fill: new fabric.Pattern({ - source: patternSourceCanvas.getElement(), - repeat: 'no-repeat' - }) - }; + const copiedCanvasElement = getCachedCanvasImageElement(canvasImage); + const fillImage = makeFillImage(copiedCanvasElement, canvasImage.angle, filterOption); + patternSourceCanvas.add(fillImage); - setCustomProperty(fabricProperty, {patternSourceCanvas}); + const fabricProperty = { + fill: new fabric.Pattern({ + source: patternSourceCanvas.getElement(), + repeat: 'no-repeat', + }), + }; - return fabricProperty; + setCustomProperty(fabricProperty, { patternSourceCanvas }); + + return fabricProperty; } /** @@ -276,15 +291,15 @@ export function makeFillPatternForFilter(canvasImage, filterOption, patternSourc * @param {fabric.StaticCanvas} patternSourceCanvas - fabric static canvas */ export function resetFillPatternCanvas(patternSourceCanvas) { - const [innerImage] = patternSourceCanvas.getObjects(); - let {fillImageMaxSize} = getCustomProperty(innerImage, 'fillImageMaxSize'); - fillImageMaxSize = Math.max(1, fillImageMaxSize); - - patternSourceCanvas.setDimensions({ - width: fillImageMaxSize, - height: fillImageMaxSize - }); - patternSourceCanvas.renderAll(); + const [innerImage] = patternSourceCanvas.getObjects(); + let { fillImageMaxSize } = getCustomProperty(innerImage, 'fillImageMaxSize'); + fillImageMaxSize = Math.max(1, fillImageMaxSize); + + patternSourceCanvas.setDimensions({ + width: fillImageMaxSize, + height: fillImageMaxSize, + }); + patternSourceCanvas.renderAll(); } /** @@ -293,30 +308,30 @@ export function resetFillPatternCanvas(patternSourceCanvas) { * @param {fabric.Image} canvasImage - canvas background image */ export function reMakePatternImageSource(shapeObj, canvasImage) { - const {patternSourceCanvas} = getCustomProperty(shapeObj, 'patternSourceCanvas'); - const [fillImage] = patternSourceCanvas.getObjects(); - const filterOption = makeFilterOptionFromFabricImage(fillImage); + const { patternSourceCanvas } = getCustomProperty(shapeObj, 'patternSourceCanvas'); + const [fillImage] = patternSourceCanvas.getObjects(); + const filterOption = makeFilterOptionFromFabricImage(fillImage); - patternSourceCanvas.remove(fillImage); + patternSourceCanvas.remove(fillImage); - const copiedCanvasElement = getCachedCanvasImageElement(canvasImage, true); - const newFillImage = makeFillImage(copiedCanvasElement, canvasImage.angle, filterOption); + const copiedCanvasElement = getCachedCanvasImageElement(canvasImage, true); + const newFillImage = makeFillImage(copiedCanvasElement, canvasImage.angle, filterOption); - patternSourceCanvas.add(newFillImage); + patternSourceCanvas.add(newFillImage); } /** - * Calculate a point line outside the canvas. + * Calculate a point line outside the canvas. * @param {fabric.Image} canvasImage - canvas background image * @param {boolean} reset - default is false * @returns {HTMLImageElement} */ export function getCachedCanvasImageElement(canvasImage, reset = false) { - if (!cachedCanvasImageElement || reset) { - cachedCanvasImageElement = canvasImage.toCanvasElement(); - } + if (!cachedCanvasImageElement || reset) { + cachedCanvasImageElement = canvasImage.toCanvasElement(); + } - return cachedCanvasImageElement; + return cachedCanvasImageElement; } /** @@ -328,23 +343,34 @@ export function getCachedCanvasImageElement(canvasImage, reset = false) { * @param {number} top - original top position * @returns {Array} */ -function calculateDistanceOverflowPart({type, shapeObj, outDistance, left, top, flipX, flipY}) { - const shapePointNavigation = getShapeEdgePoint(shapeObj); - const shapeNeighborPointNavigation = [[1, 2], [0, 3], [0, 3], [1, 2]]; - const linePointsOutsideCanvas = - calculateLinePointsOutsideCanvas(type, shapePointNavigation, shapeNeighborPointNavigation); - const reatAngles = - calculateLineAngleOfOutsideCanvas(type, shapePointNavigation, linePointsOutsideCanvas); - const {startPointIndex} = linePointsOutsideCanvas; - const diffPosition = getReversePositionForFlip({ - outDistance, - startPointIndex, - flipX, - flipY, - reatAngles - }); - - return [left + diffPosition.left, top + diffPosition.top]; +function calculateDistanceOverflowPart({ type, shapeObj, outDistance, left, top, flipX, flipY }) { + const shapePointNavigation = getShapeEdgePoint(shapeObj); + const shapeNeighborPointNavigation = [ + [1, 2], + [0, 3], + [0, 3], + [1, 2], + ]; + const linePointsOutsideCanvas = calculateLinePointsOutsideCanvas( + type, + shapePointNavigation, + shapeNeighborPointNavigation + ); + const reatAngles = calculateLineAngleOfOutsideCanvas( + type, + shapePointNavigation, + linePointsOutsideCanvas + ); + const { startPointIndex } = linePointsOutsideCanvas; + const diffPosition = getReversePositionForFlip({ + outDistance, + startPointIndex, + flipX, + flipY, + reatAngles, + }); + + return [left + diffPosition.left, top + diffPosition.top]; } /** @@ -355,27 +381,27 @@ function calculateDistanceOverflowPart({type, shapeObj, outDistance, left, top, * @param {Array} reatAngles - Line angle of the rectangle vertex. * @returns {Object} diffPosition */ -function getReversePositionForFlip({outDistance, startPointIndex, flipX, flipY, reatAngles}) { - const rotationChangePoint1 = outDistance * Math.cos(reatAngles[0] * Math.PI / 180); - const rotationChangePoint2 = outDistance * Math.cos(reatAngles[1] * Math.PI / 180); - const isForward = startPointIndex === 2 || startPointIndex === 3; - const diffPosition = { - top: isForward ? rotationChangePoint1 : rotationChangePoint2, - left: isForward ? rotationChangePoint2 : rotationChangePoint1 - }; - - if (isReverseLeftPositionForFlip(startPointIndex, flipX, flipY)) { - diffPosition.left = diffPosition.left * -1; - } - if (isReverseTopPositionForFlip(startPointIndex, flipX, flipY)) { - diffPosition.top = diffPosition.top * -1; - } - - return diffPosition; +function getReversePositionForFlip({ outDistance, startPointIndex, flipX, flipY, reatAngles }) { + const rotationChangePoint1 = outDistance * Math.cos((reatAngles[0] * Math.PI) / 180); + const rotationChangePoint2 = outDistance * Math.cos((reatAngles[1] * Math.PI) / 180); + const isForward = startPointIndex === 2 || startPointIndex === 3; + const diffPosition = { + top: isForward ? rotationChangePoint1 : rotationChangePoint2, + left: isForward ? rotationChangePoint2 : rotationChangePoint1, + }; + + if (isReverseLeftPositionForFlip(startPointIndex, flipX, flipY)) { + diffPosition.left = diffPosition.left * -1; + } + if (isReverseTopPositionForFlip(startPointIndex, flipX, flipY)) { + diffPosition.top = diffPosition.top * -1; + } + + return diffPosition; } /** - * Calculate a point line outside the canvas. + * Calculate a point line outside the canvas. * @param {string} type - 'x' or 'y' * @param {Array} shapePointNavigation - shape edge positions * @param {Object} shapePointNavigation.lefttop - left top position @@ -385,27 +411,31 @@ function getReversePositionForFlip({outDistance, startPointIndex, flipX, flipY, * @param {Array} shapeNeighborPointNavigation - Array to find adjacent edges. * @returns {Object} */ -function calculateLinePointsOutsideCanvas(type, shapePointNavigation, shapeNeighborPointNavigation) { - let minimumPoint = 0; - let minimumPointIndex = 0; - forEach(shapePointNavigation, (point, index) => { - if (point[type] < minimumPoint) { - minimumPoint = point[type]; - minimumPointIndex = index; - } - }); +function calculateLinePointsOutsideCanvas( + type, + shapePointNavigation, + shapeNeighborPointNavigation +) { + let minimumPoint = 0; + let minimumPointIndex = 0; + forEach(shapePointNavigation, (point, index) => { + if (point[type] < minimumPoint) { + minimumPoint = point[type]; + minimumPointIndex = index; + } + }); - const [endPointIndex1, endPointIndex2] = shapeNeighborPointNavigation[minimumPointIndex]; + const [endPointIndex1, endPointIndex2] = shapeNeighborPointNavigation[minimumPointIndex]; - return { - startPointIndex: minimumPointIndex, - endPointIndex1, - endPointIndex2 - }; + return { + startPointIndex: minimumPointIndex, + endPointIndex1, + endPointIndex2, + }; } /** - * Calculate a point line outside the canvas. + * Calculate a point line outside the canvas. * @param {string} type - 'x' or 'y' * @param {Array} shapePointNavigation - shape edge positions * @param {object} shapePointNavigation.lefttop - left top position @@ -419,17 +449,17 @@ function calculateLinePointsOutsideCanvas(type, shapePointNavigation, shapeNeigh * @returns {Object} */ function calculateLineAngleOfOutsideCanvas(type, shapePointNavigation, linePointsOfOneVertexIndex) { - const {startPointIndex, endPointIndex1, endPointIndex2} = linePointsOfOneVertexIndex; - const horizontalVerticalAngle = type === 'x' ? 180 : 270; + const { startPointIndex, endPointIndex1, endPointIndex2 } = linePointsOfOneVertexIndex; + const horizontalVerticalAngle = type === 'x' ? 180 : 270; - return map([endPointIndex1, endPointIndex2], pointIndex => { - const startPoint = shapePointNavigation[startPointIndex]; - const endPoint = shapePointNavigation[pointIndex]; - const diffY = startPoint.y - endPoint.y; - const diffX = startPoint.x - endPoint.x; + return map([endPointIndex1, endPointIndex2], (pointIndex) => { + const startPoint = shapePointNavigation[startPointIndex]; + const endPoint = shapePointNavigation[pointIndex]; + const diffY = startPoint.y - endPoint.y; + const diffX = startPoint.x - endPoint.x; - return (Math.atan2(diffY, diffX) * 180 / Math.PI) - horizontalVerticalAngle; - }); + return (Math.atan2(diffY, diffX) * 180) / Math.PI - horizontalVerticalAngle; + }); } /* eslint-disable complexity */ @@ -441,30 +471,30 @@ function calculateLineAngleOfOutsideCanvas(type, shapePointNavigation, linePoint * @returns {boolean} flipY - flip y statux */ function isReverseLeftPositionForFlip(startPointIndex, flipX, flipY) { - return ( - (((!flipX && flipY) || (!flipX && !flipY)) && startPointIndex === 0) || - (((flipX && flipY) || (flipX && !flipY)) && startPointIndex === 1) || - (((!flipX && !flipY) || (!flipX && flipY)) && startPointIndex === 2) || - (((flipX && !flipY) || (flipX && flipY)) && startPointIndex === 3) - ); + return ( + (((!flipX && flipY) || (!flipX && !flipY)) && startPointIndex === 0) || + (((flipX && flipY) || (flipX && !flipY)) && startPointIndex === 1) || + (((!flipX && !flipY) || (!flipX && flipY)) && startPointIndex === 2) || + (((flipX && !flipY) || (flipX && flipY)) && startPointIndex === 3) + ); } /* eslint-enable complexity */ /* eslint-disable complexity */ /** - * Calculate a point line outside the canvas for vertical. + * Calculate a point line outside the canvas for vertical. * @param {number} startPointIndex - start point index * @param {boolean} flipX - flip x statux * @param {boolean} flipY - flip y statux * @returns {boolean} flipY - flip y statux */ function isReverseTopPositionForFlip(startPointIndex, flipX, flipY) { - return ( - (((flipX && !flipY) || (!flipX && !flipY)) && startPointIndex === 0) || - (((!flipX && !flipY) || (flipX && !flipY)) && startPointIndex === 1) || - (((flipX && flipY) || (!flipX && flipY)) && startPointIndex === 2) || - (((!flipX && flipY) || (flipX && flipY)) && startPointIndex === 3) - ); + return ( + (((flipX && !flipY) || (!flipX && !flipY)) && startPointIndex === 0) || + (((!flipX && !flipY) || (flipX && !flipY)) && startPointIndex === 1) || + (((flipX && flipY) || (!flipX && flipY)) && startPointIndex === 2) || + (((!flipX && flipY) || (flipX && flipY)) && startPointIndex === 3) + ); } /* eslint-enable complexity */ @@ -474,12 +504,12 @@ function isReverseTopPositionForFlip(startPointIndex, flipX, flipY) { * @returns {Array} shapeEdgePoint - shape edge positions */ function getShapeEdgePoint(shapeObj) { - return [ - shapeObj.getPointByOrigin('left', 'top'), - shapeObj.getPointByOrigin('right', 'top'), - shapeObj.getPointByOrigin('left', 'bottom'), - shapeObj.getPointByOrigin('right', 'bottom') - ]; + return [ + shapeObj.getPointByOrigin('left', 'top'), + shapeObj.getPointByOrigin('right', 'top'), + shapeObj.getPointByOrigin('left', 'bottom'), + shapeObj.getPointByOrigin('right', 'bottom'), + ]; } /** @@ -488,26 +518,26 @@ function getShapeEdgePoint(shapeObj) { * @returns {Object} Rotated shape dimension */ function getRotatedDimension(shapeObj) { - const [ - {x: ax, y: ay}, - {x: bx, y: by}, - {x: cx, y: cy}, - {x: dx, y: dy} - ] = getShapeEdgePoint(shapeObj); - - const left = Math.min(ax, bx, cx, dx); - const top = Math.min(ay, by, cy, dy); - const right = Math.max(ax, bx, cx, dx); - const bottom = Math.max(ay, by, cy, dy); - - return { - left, - top, - right, - bottom, - width: right - left, - height: bottom - top - }; + const [ + { x: ax, y: ay }, + { x: bx, y: by }, + { x: cx, y: cy }, + { x: dx, y: dy }, + ] = getShapeEdgePoint(shapeObj); + + const left = Math.min(ax, bx, cx, dx); + const top = Math.min(ay, by, cy, dy); + const right = Math.max(ax, bx, cx, dx); + const bottom = Math.max(ay, by, cy, dy); + + return { + left, + top, + right, + bottom, + width: right - left, + height: bottom - top, + }; } /** @@ -519,22 +549,22 @@ function getRotatedDimension(shapeObj) { * @private */ function makeFillImage(copiedCanvasElement, currentCanvasImageAngle, filterOption) { - const fillImage = new fabric.Image(copiedCanvasElement); - - forEach(extend({}, ...filterOption), (value, key) => { - const fabricFiterClassName = capitalizeString(key); - const filter = new fabric.Image.filters[fabricFiterClassName]({ - [FILTER_OPTION_MAP[key]]: value - }); - fillImage.filters.push(filter); - }); - fillImage.applyFilters(); + const fillImage = new fabric.Image(copiedCanvasElement); - setCustomProperty(fillImage, { - originalAngle: currentCanvasImageAngle, - fillImageMaxSize: Math.max(fillImage.width, fillImage.height) + forEach(extend({}, ...filterOption), (value, key) => { + const fabricFiterClassName = capitalizeString(key); + const filter = new fabric.Image.filters[fabricFiterClassName]({ + [FILTER_OPTION_MAP[key]]: value, }); - resizeHelper.adjustOriginToCenter(fillImage); + fillImage.filters.push(filter); + }); + fillImage.applyFilters(); + + setCustomProperty(fillImage, { + originalAngle: currentCanvasImageAngle, + fillImageMaxSize: Math.max(fillImage.width, fillImage.height), + }); + resizeHelper.adjustOriginToCenter(fillImage); - return fillImage; + return fillImage; } diff --git a/src/js/helper/shapeResizeHelper.js b/src/js/helper/shapeResizeHelper.js index c8938b171..c76f8676f 100644 --- a/src/js/helper/shapeResizeHelper.js +++ b/src/js/helper/shapeResizeHelper.js @@ -3,23 +3,23 @@ * @fileoverview Shape resize helper */ const DIVISOR = { - rect: 1, - circle: 2, - triangle: 1 + rect: 1, + circle: 2, + triangle: 1, }; const DIMENSION_KEYS = { - rect: { - w: 'width', - h: 'height' - }, - circle: { - w: 'rx', - h: 'ry' - }, - triangle: { - w: 'width', - h: 'height' - } + rect: { + w: 'width', + h: 'height', + }, + circle: { + w: 'rx', + h: 'ry', + }, + triangle: { + w: 'width', + h: 'height', + }, }; /** @@ -28,10 +28,10 @@ const DIMENSION_KEYS = { * @ignore */ function setStartPoint(shape) { - const {originX, originY} = shape; - const originKey = originX.substring(0, 1) + originY.substring(0, 1); + const { originX, originY } = shape; + const originKey = originX.substring(0, 1) + originY.substring(0, 1); - shape.startPoint = shape.origins[originKey]; + shape.startPoint = shape.origins[originKey]; } /** @@ -43,18 +43,18 @@ function setStartPoint(shape) { * @ignore */ function getPositionsOfRotatedOrigin(origin, pointer, angle) { - const sx = origin.x; - const sy = origin.y; - const px = pointer.x; - const py = pointer.y; - const r = angle * Math.PI / 180; - const rx = ((px - sx) * Math.cos(r)) - ((py - sy) * Math.sin(r)) + sx; - const ry = ((px - sx) * Math.sin(r)) + ((py - sy) * Math.cos(r)) + sy; - - return { - originX: (sx > rx) ? 'right' : 'left', - originY: (sy > ry) ? 'bottom' : 'top' - }; + const sx = origin.x; + const sy = origin.y; + const px = pointer.x; + const py = pointer.y; + const r = (angle * Math.PI) / 180; + const rx = (px - sx) * Math.cos(r) - (py - sy) * Math.sin(r) + sx; + const ry = (px - sx) * Math.sin(r) + (py - sy) * Math.cos(r) + sy; + + return { + originX: sx > rx ? 'right' : 'left', + originY: sy > ry ? 'bottom' : 'top', + }; } /** @@ -64,8 +64,7 @@ function getPositionsOfRotatedOrigin(origin, pointer, angle) { * @ignore */ function hasCenterOrigin(shape) { - return (shape.originX === 'center' && - shape.originY === 'center'); + return shape.originX === 'center' && shape.originY === 'center'; } /** @@ -75,22 +74,22 @@ function hasCenterOrigin(shape) { * @ignore */ function adjustOriginByStartPoint(pointer, shape) { - const centerPoint = shape.getPointByOrigin('center', 'center'); - const angle = -shape.angle; - const originPositions = getPositionsOfRotatedOrigin(centerPoint, pointer, angle); - const {originX, originY} = originPositions; - const origin = shape.getPointByOrigin(originX, originY); - const left = shape.left - (centerPoint.x - origin.x); - const top = shape.top - (centerPoint.y - origin.y); - - shape.set({ - originX, - originY, - left, - top - }); - - shape.setCoords(); + const centerPoint = shape.getPointByOrigin('center', 'center'); + const angle = -shape.angle; + const originPositions = getPositionsOfRotatedOrigin(centerPoint, pointer, angle); + const { originX, originY } = originPositions; + const origin = shape.getPointByOrigin(originX, originY); + const left = shape.left - (centerPoint.x - origin.x); + const top = shape.top - (centerPoint.y - origin.y); + + shape.set({ + originX, + originY, + left, + top, + }); + + shape.setCoords(); } /** @@ -100,13 +99,13 @@ function adjustOriginByStartPoint(pointer, shape) { * @ignore */ function adjustOriginByMovingPointer(pointer, shape) { - const origin = shape.startPoint; - const angle = -shape.angle; - const originPositions = getPositionsOfRotatedOrigin(origin, pointer, angle); - const {originX, originY} = originPositions; + const origin = shape.startPoint; + const angle = -shape.angle; + const originPositions = getPositionsOfRotatedOrigin(origin, pointer, angle); + const { originX, originY } = originPositions; - shape.setPositionByOrigin(origin, originX, originY); - shape.setCoords(); + shape.setPositionByOrigin(origin, originX, originY); + shape.setCoords(); } /** @@ -115,29 +114,29 @@ function adjustOriginByMovingPointer(pointer, shape) { * @ignore */ function adjustDimensionOnScaling(shape) { - const {type, scaleX, scaleY} = shape; - const dimensionKeys = DIMENSION_KEYS[type]; - let width = shape[dimensionKeys.w] * scaleX; - let height = shape[dimensionKeys.h] * scaleY; + const { type, scaleX, scaleY } = shape; + const dimensionKeys = DIMENSION_KEYS[type]; + let width = shape[dimensionKeys.w] * scaleX; + let height = shape[dimensionKeys.h] * scaleY; - if (shape.isRegular) { - const maxScale = Math.max(scaleX, scaleY); + if (shape.isRegular) { + const maxScale = Math.max(scaleX, scaleY); - width = shape[dimensionKeys.w] * maxScale; - height = shape[dimensionKeys.h] * maxScale; - } + width = shape[dimensionKeys.w] * maxScale; + height = shape[dimensionKeys.h] * maxScale; + } - const options = { - hasControls: false, - hasBorders: false, - scaleX: 1, - scaleY: 1 - }; + const options = { + hasControls: false, + hasBorders: false, + scaleX: 1, + scaleY: 1, + }; - options[dimensionKeys.w] = width; - options[dimensionKeys.h] = height; + options[dimensionKeys.w] = width; + options[dimensionKeys.h] = height; - shape.set(options); + shape.set(options); } /** @@ -147,96 +146,96 @@ function adjustDimensionOnScaling(shape) { * @ignore */ function adjustDimensionOnMouseMove(pointer, shape) { - const {type, strokeWidth, startPoint: origin} = shape; - const divisor = DIVISOR[type]; - const dimensionKeys = DIMENSION_KEYS[type]; - const isTriangle = !!(shape.type === 'triangle'); - const options = {}; - let width = Math.abs(origin.x - pointer.x) / divisor; - let height = Math.abs(origin.y - pointer.y) / divisor; - - if (width > strokeWidth) { - width -= strokeWidth / divisor; + const { type, strokeWidth, startPoint: origin } = shape; + const divisor = DIVISOR[type]; + const dimensionKeys = DIMENSION_KEYS[type]; + const isTriangle = !!(shape.type === 'triangle'); + const options = {}; + let width = Math.abs(origin.x - pointer.x) / divisor; + let height = Math.abs(origin.y - pointer.y) / divisor; + + if (width > strokeWidth) { + width -= strokeWidth / divisor; + } + + if (height > strokeWidth) { + height -= strokeWidth / divisor; + } + + if (shape.isRegular) { + width = height = Math.max(width, height); + + if (isTriangle) { + height = (Math.sqrt(3) / 2) * width; } + } - if (height > strokeWidth) { - height -= strokeWidth / divisor; - } + options[dimensionKeys.w] = width; + options[dimensionKeys.h] = height; + + shape.set(options); +} - if (shape.isRegular) { - width = height = Math.max(width, height); +module.exports = { + /** + * Set each origin value to shape + * @param {fabric.Object} shape - Shape object + */ + setOrigins(shape) { + const leftTopPoint = shape.getPointByOrigin('left', 'top'); + const rightTopPoint = shape.getPointByOrigin('right', 'top'); + const rightBottomPoint = shape.getPointByOrigin('right', 'bottom'); + const leftBottomPoint = shape.getPointByOrigin('left', 'bottom'); + + shape.origins = { + lt: leftTopPoint, + rt: rightTopPoint, + rb: rightBottomPoint, + lb: leftBottomPoint, + }; + }, + + /** + * Resize the shape + * @param {fabric.Object} shape - Shape object + * @param {{x: number, y: number}} pointer - Mouse pointer values on canvas + * @param {boolean} isScaling - Whether the resizing action is scaling or not + */ + resize(shape, pointer, isScaling) { + if (hasCenterOrigin(shape)) { + adjustOriginByStartPoint(pointer, shape); + setStartPoint(shape); + } - if (isTriangle) { - height = Math.sqrt(3) / 2 * width; - } + if (isScaling) { + adjustDimensionOnScaling(shape, pointer); + } else { + adjustDimensionOnMouseMove(pointer, shape); } - options[dimensionKeys.w] = width; - options[dimensionKeys.h] = height; + adjustOriginByMovingPointer(pointer, shape); + }, - shape.set(options); -} + /** + * Adjust the origin position of shape to center + * @param {fabric.Object} shape - Shape object + */ + adjustOriginToCenter(shape) { + const centerPoint = shape.getPointByOrigin('center', 'center'); + const { originX, originY } = shape; + const origin = shape.getPointByOrigin(originX, originY); + const left = shape.left + (centerPoint.x - origin.x); + const top = shape.top + (centerPoint.y - origin.y); -module.exports = { - /** - * Set each origin value to shape - * @param {fabric.Object} shape - Shape object - */ - setOrigins(shape) { - const leftTopPoint = shape.getPointByOrigin('left', 'top'); - const rightTopPoint = shape.getPointByOrigin('right', 'top'); - const rightBottomPoint = shape.getPointByOrigin('right', 'bottom'); - const leftBottomPoint = shape.getPointByOrigin('left', 'bottom'); - - shape.origins = { - lt: leftTopPoint, - rt: rightTopPoint, - rb: rightBottomPoint, - lb: leftBottomPoint - }; - }, - - /** - * Resize the shape - * @param {fabric.Object} shape - Shape object - * @param {{x: number, y: number}} pointer - Mouse pointer values on canvas - * @param {boolean} isScaling - Whether the resizing action is scaling or not - */ - resize(shape, pointer, isScaling) { - if (hasCenterOrigin(shape)) { - adjustOriginByStartPoint(pointer, shape); - setStartPoint(shape); - } - - if (isScaling) { - adjustDimensionOnScaling(shape, pointer); - } else { - adjustDimensionOnMouseMove(pointer, shape); - } - - adjustOriginByMovingPointer(pointer, shape); - }, - - /** - * Adjust the origin position of shape to center - * @param {fabric.Object} shape - Shape object - */ - adjustOriginToCenter(shape) { - const centerPoint = shape.getPointByOrigin('center', 'center'); - const {originX, originY} = shape; - const origin = shape.getPointByOrigin(originX, originY); - const left = shape.left + (centerPoint.x - origin.x); - const top = shape.top + (centerPoint.y - origin.y); - - shape.set({ - hasControls: true, - hasBorders: true, - originX: 'center', - originY: 'center', - left, - top - }); - - shape.setCoords(); // For left, top properties - } + shape.set({ + hasControls: true, + hasBorders: true, + originX: 'center', + originY: 'center', + left, + top, + }); + + shape.setCoords(); // For left, top properties + }, }; diff --git a/src/js/imageEditor.js b/src/js/imageEditor.js index 716b24cfa..7c42cbb97 100644 --- a/src/js/imageEditor.js +++ b/src/js/imageEditor.js @@ -8,32 +8,30 @@ import UI from './ui'; import action from './action'; import commandFactory from './factory/command'; import Graphics from './graphics'; -import {sendHostName, Promise} from './util'; -import {eventNames as events, commandNames as commands, keyCodes, rejectMessages} from './consts'; -import { - makeSelectionUndoData, - makeSelectionUndoDatum -} from './helper/selectionModifyHelper'; +import { sendHostName, Promise } from './util'; +import { eventNames as events, commandNames as commands, keyCodes, rejectMessages } from './consts'; +import { makeSelectionUndoData, makeSelectionUndoDatum } from './helper/selectionModifyHelper'; -const {isUndefined, forEach, CustomEvents} = snippet; +const { isUndefined, forEach, CustomEvents } = snippet; const { - MOUSE_DOWN, - OBJECT_MOVED, - OBJECT_SCALED, - OBJECT_ACTIVATED, - OBJECT_ROTATED, - OBJECT_ADDED, - OBJECT_MODIFIED, - ADD_TEXT, - ADD_OBJECT, - TEXT_EDITING, - TEXT_CHANGED, - ICON_CREATE_RESIZE, - ICON_CREATE_END, - SELECTION_CLEARED, - SELECTION_CREATED, - ADD_OBJECT_AFTER} = events; + MOUSE_DOWN, + OBJECT_MOVED, + OBJECT_SCALED, + OBJECT_ACTIVATED, + OBJECT_ROTATED, + OBJECT_ADDED, + OBJECT_MODIFIED, + ADD_TEXT, + ADD_OBJECT, + TEXT_EDITING, + TEXT_CHANGED, + ICON_CREATE_RESIZE, + ICON_CREATE_END, + SELECTION_CLEARED, + SELECTION_CREATED, + ADD_OBJECT_AFTER, +} = events; /** * Image filter result @@ -98,8 +96,8 @@ const { * @typedef {object} ShapeFillOption - fill option of shape * @property {string} type - fill type ('color' or 'filter') * @property {Array.} [filter] - {@link ShapeFilterOption} List. - * only applies to filter types - * (ex: \[\{pixelate: 20\}, \{blur: 0.3\}\]) + * only applies to filter types + * (ex: \[\{pixelate: 20\}, \{blur: 0.3\}\]) * @property {string} [color] - Shape foreground color (ex: '#fff', 'transparent') */ @@ -158,1508 +156,1510 @@ const { * }); */ class ImageEditor { - constructor(wrapper, options) { - options = snippet.extend({ - includeUI: false, - usageStatistics: true - }, options); - - this.mode = null; - - this.activeObjectId = null; - - /** - * UI instance - * @type {Ui} - */ - if (options.includeUI) { - const UIOption = options.includeUI; - UIOption.usageStatistics = options.usageStatistics; - - this.ui = new UI(wrapper, UIOption, this.getActions()); - options = this.ui.setUiDefaultSelectionStyle(options); - } - - /** - * Invoker - * @type {Invoker} - * @private - */ - this._invoker = new Invoker(); - - /** - * Graphics instance - * @type {Graphics} - * @private - */ - this._graphics = new Graphics( - this.ui ? this.ui.getEditorArea() : wrapper, { - cssMaxWidth: options.cssMaxWidth, - cssMaxHeight: options.cssMaxHeight - } - ); - - /** - * Event handler list - * @type {Object} - * @private - */ - this._handlers = { - keydown: this._onKeyDown.bind(this), - mousedown: this._onMouseDown.bind(this), - objectActivated: this._onObjectActivated.bind(this), - objectMoved: this._onObjectMoved.bind(this), - objectScaled: this._onObjectScaled.bind(this), - objectRotated: this._onObjectRotated.bind(this), - objectAdded: this._onObjectAdded.bind(this), - objectModified: this._onObjectModified.bind(this), - createdPath: this._onCreatedPath, - addText: this._onAddText.bind(this), - addObject: this._onAddObject.bind(this), - textEditing: this._onTextEditing.bind(this), - textChanged: this._onTextChanged.bind(this), - iconCreateResize: this._onIconCreateResize.bind(this), - iconCreateEnd: this._onIconCreateEnd.bind(this), - selectionCleared: this._selectionCleared.bind(this), - selectionCreated: this._selectionCreated.bind(this) - }; - - this._attachInvokerEvents(); - this._attachGraphicsEvents(); - this._attachDomEvents(); - this._setSelectionStyle(options.selectionStyle, { - applyCropSelectionStyle: options.applyCropSelectionStyle, - applyGroupSelectionStyle: options.applyGroupSelectionStyle - }); - - if (options.usageStatistics) { - sendHostName(); - } - - if (this.ui) { - this.ui.initCanvas(); - this.setReAction(); - } - fabric.enableGLFiltering = false; - } - - /** - * Set selection style by init option - * @param {Object} selectionStyle - Selection styles - * @param {Object} applyTargets - Selection apply targets - * @param {boolean} applyCropSelectionStyle - whether apply with crop selection style or not - * @param {boolean} applyGroupSelectionStyle - whether apply with group selection style or not - * @private - */ - _setSelectionStyle(selectionStyle, {applyCropSelectionStyle, applyGroupSelectionStyle}) { - if (selectionStyle) { - this._graphics.setSelectionStyle(selectionStyle); - } - - if (applyCropSelectionStyle) { - this._graphics.setCropSelectionStyle(selectionStyle); - } - - if (applyGroupSelectionStyle) { - this.on('selectionCreated', eventTarget => { - if (eventTarget.type === 'activeSelection') { - eventTarget.set(selectionStyle); - } - }); - } - } - - /** - * Attach invoker events - * @private - */ - _attachInvokerEvents() { - const { - UNDO_STACK_CHANGED, - REDO_STACK_CHANGED - } = events; - - /** - * Undo stack changed event - * @event ImageEditor#undoStackChanged - * @param {Number} length - undo stack length - * @example - * imageEditor.on('undoStackChanged', function(length) { - * console.log(length); - * }); - */ - this._invoker.on(UNDO_STACK_CHANGED, this.fire.bind(this, UNDO_STACK_CHANGED)); - /** - * Redo stack changed event - * @event ImageEditor#redoStackChanged - * @param {Number} length - redo stack length - * @example - * imageEditor.on('redoStackChanged', function(length) { - * console.log(length); - * }); - */ - this._invoker.on(REDO_STACK_CHANGED, this.fire.bind(this, REDO_STACK_CHANGED)); - } - - /** - * Attach canvas events - * @private - */ - _attachGraphicsEvents() { - this._graphics.on({ - [MOUSE_DOWN]: this._handlers.mousedown, - [OBJECT_MOVED]: this._handlers.objectMoved, - [OBJECT_SCALED]: this._handlers.objectScaled, - [OBJECT_ROTATED]: this._handlers.objectRotated, - [OBJECT_ACTIVATED]: this._handlers.objectActivated, - [OBJECT_ADDED]: this._handlers.objectAdded, - [OBJECT_MODIFIED]: this._handlers.objectModified, - [ADD_TEXT]: this._handlers.addText, - [ADD_OBJECT]: this._handlers.addObject, - [TEXT_EDITING]: this._handlers.textEditing, - [TEXT_CHANGED]: this._handlers.textChanged, - [ICON_CREATE_RESIZE]: this._handlers.iconCreateResize, - [ICON_CREATE_END]: this._handlers.iconCreateEnd, - [SELECTION_CLEARED]: this._handlers.selectionCleared, - [SELECTION_CREATED]: this._handlers.selectionCreated - }); - } - - /** - * Attach dom events - * @private - */ - _attachDomEvents() { - // ImageEditor supports IE 9 higher - document.addEventListener('keydown', this._handlers.keydown); - } - - /** - * Detach dom events - * @private - */ - _detachDomEvents() { - // ImageEditor supports IE 9 higher - document.removeEventListener('keydown', this._handlers.keydown); - } - - /** - * Keydown event handler - * @param {KeyboardEvent} e - Event object - * @private - */ - /* eslint-disable complexity */ - _onKeyDown(e) { - const {ctrlKey, keyCode, metaKey} = e; - const isModifierKey = (ctrlKey || metaKey); - - if (isModifierKey) { - if (keyCode === keyCodes.C) { - this._graphics.resetTargetObjectForCopyPaste(); - } else if (keyCode === keyCodes.V) { - this._graphics.pasteObject(); - this.clearRedoStack(); - } else if (keyCode === keyCodes.Z) { - // There is no error message on shortcut when it's empty - this.undo()['catch'](() => { - }); - } else if (keyCode === keyCodes.Y) { - // There is no error message on shortcut when it's empty - this.redo()['catch'](() => { - }); - } - } - - const isDeleteKey = keyCode === keyCodes.BACKSPACE || keyCode === keyCodes.DEL; - const isRemoveReady = this._graphics.isReadyRemoveObject(); + constructor(wrapper, options) { + options = snippet.extend( + { + includeUI: false, + usageStatistics: true, + }, + options + ); - if (isRemoveReady && isDeleteKey) { - e.preventDefault(); - this.removeActiveObject(); - } - } - - /** - * Remove Active Object - */ - removeActiveObject() { - const activeObjectId = this._graphics.getActiveObjectIdForRemove(); - - this.removeObject(activeObjectId); - } - - /** - * mouse down event handler - * @param {Event} event - mouse down event - * @param {Object} originPointer - origin pointer - * @param {Number} originPointer.x x position - * @param {Number} originPointer.y y position - * @private - */ - _onMouseDown(event, originPointer) { - /** - * The mouse down event with position x, y on canvas - * @event ImageEditor#mousedown - * @param {Object} event - browser mouse event object - * @param {Object} originPointer origin pointer - * @param {Number} originPointer.x x position - * @param {Number} originPointer.y y position - * @example - * imageEditor.on('mousedown', function(event, originPointer) { - * console.log(event); - * console.log(originPointer); - * if (imageEditor.hasFilter('colorFilter')) { - * imageEditor.applyFilter('colorFilter', { - * x: parseInt(originPointer.x, 10), - * y: parseInt(originPointer.y, 10) - * }); - * } - * }); - */ - - this.fire(events.MOUSE_DOWN, event, originPointer); - } - - /** - * Add a 'addObject' command - * @param {Object} obj - Fabric object - * @private - */ - _pushAddObjectCommand(obj) { - const command = commandFactory.create(commands.ADD_OBJECT, this._graphics, obj); - this._invoker.pushUndoStack(command); - } - - /** - * Add a 'changeSelection' command - * @param {fabric.Object} obj - selection object - * @private - */ - _pushModifyObjectCommand(obj) { - const {type} = obj; - const props = makeSelectionUndoData(obj, item => makeSelectionUndoDatum(this._graphics.getObjectId(item), item, type === 'activeSelection')); - const command = commandFactory.create(commands.CHANGE_SELECTION, this._graphics, props); - command.execute(this._graphics, props); - - this._invoker.pushUndoStack(command); - } - - /** - * 'objectActivated' event handler - * @param {ObjectProps} props - object properties - * @private - */ - _onObjectActivated(props) { - /** - * The event when object is selected(aka activated). - * @event ImageEditor#objectActivated - * @param {ObjectProps} objectProps - object properties - * @example - * imageEditor.on('objectActivated', function(props) { - * console.log(props); - * console.log(props.type); - * console.log(props.id); - * }); - */ - this.fire(events.OBJECT_ACTIVATED, props); - } - - /** - * 'objectMoved' event handler - * @param {ObjectProps} props - object properties - * @private - */ - _onObjectMoved(props) { - /** - * The event when object is moved - * @event ImageEditor#objectMoved - * @param {ObjectProps} props - object properties - * @example - * imageEditor.on('objectMoved', function(props) { - * console.log(props); - * console.log(props.type); - * }); - */ - this.fire(events.OBJECT_MOVED, props); - } - - /** - * 'objectScaled' event handler - * @param {ObjectProps} props - object properties - * @private - */ - _onObjectScaled(props) { - /** - * The event when scale factor is changed - * @event ImageEditor#objectScaled - * @param {ObjectProps} props - object properties - * @example - * imageEditor.on('objectScaled', function(props) { - * console.log(props); - * console.log(props.type); - * }); - */ - this.fire(events.OBJECT_SCALED, props); - } - - /** - * 'objectRotated' event handler - * @param {ObjectProps} props - object properties - * @private - */ - _onObjectRotated(props) { - /** - * The event when object angle is changed - * @event ImageEditor#objectRotated - * @param {ObjectProps} props - object properties - * @example - * imageEditor.on('objectRotated', function(props) { - * console.log(props); - * console.log(props.type); - * }); - */ - this.fire(events.OBJECT_ROTATED, props); - } - - /** - * Get current drawing mode - * @returns {string} - * @example - * // Image editor drawing mode - * // - * // NORMAL: 'NORMAL' - * // CROPPER: 'CROPPER' - * // FREE_DRAWING: 'FREE_DRAWING' - * // LINE_DRAWING: 'LINE_DRAWING' - * // TEXT: 'TEXT' - * // - * if (imageEditor.getDrawingMode() === 'FREE_DRAWING') { - * imageEditor.stopDrawingMode(); - * } - */ - getDrawingMode() { - return this._graphics.getDrawingMode(); - } - - /** - * Clear all objects - * @returns {Promise} - * @example - * imageEditor.clearObjects(); - */ - clearObjects() { - return this.execute(commands.CLEAR_OBJECTS); - } - - /** - * Deactivate all objects - * @example - * imageEditor.deactivateAll(); - */ - deactivateAll() { - this._graphics.deactivateAll(); - this._graphics.renderAll(); - } - - /** - * discard selction - * @example - * imageEditor.discardSelection(); - */ - discardSelection() { - this._graphics.discardSelection(); - } + this.mode = null; - /** - * selectable status change - * @param {boolean} selectable - selctable status - * @example - * imageEditor.changeSelectableAll(false); // or true - */ - changeSelectableAll(selectable) { - this._graphics.changeSelectableAll(selectable); - } + this.activeObjectId = null; /** - * Invoke command - * @param {String} commandName - Command name - * @param {...*} args - Arguments for creating command - * @returns {Promise} - * @private + * UI instance + * @type {Ui} */ - execute(commandName, ...args) { - // Inject an Graphics instance as first parameter - const theArgs = [this._graphics].concat(args); + if (options.includeUI) { + const UIOption = options.includeUI; + UIOption.usageStatistics = options.usageStatistics; - return this._invoker.execute(commandName, ...theArgs); + this.ui = new UI(wrapper, UIOption, this.getActions()); + options = this.ui.setUiDefaultSelectionStyle(options); } /** - * Invoke command - * @param {String} commandName - Command name - * @param {...*} args - Arguments for creating command - * @returns {Promise} + * Invoker + * @type {Invoker} * @private */ - executeSilent(commandName, ...args) { - // Inject an Graphics instance as first parameter - const theArgs = [this._graphics].concat(args); - - return this._invoker.executeSilent(commandName, ...theArgs); - } - - /** - * Undo - * @returns {Promise} - * @example - * imageEditor.undo(); - */ - undo() { - return this._invoker.undo(); - } - - /** - * Redo - * @returns {Promise} - * @example - * imageEditor.redo(); - */ - redo() { - return this._invoker.redo(); - } - - /** - * Load image from file - * @param {File} imgFile - Image file - * @param {string} [imageName] - imageName - * @returns {Promise} - * @example - * imageEditor.loadImageFromFile(file).then(result => { - * console.log('old : ' + result.oldWidth + ', ' + result.oldHeight); - * console.log('new : ' + result.newWidth + ', ' + result.newHeight); - * }); - */ - loadImageFromFile(imgFile, imageName) { - if (!imgFile) { - return Promise.reject(rejectMessages.invalidParameters); - } - - const imgUrl = URL.createObjectURL(imgFile); - imageName = imageName || imgFile.name; - - return this.loadImageFromURL(imgUrl, imageName).then(value => { - URL.revokeObjectURL(imgFile); - - return value; - }); - } - - /** - * Load image from url - * @param {string} url - File url - * @param {string} imageName - imageName - * @returns {Promise} - * @example - * imageEditor.loadImageFromURL('http://url/testImage.png', 'lena').then(result => { - * console.log('old : ' + result.oldWidth + ', ' + result.oldHeight); - * console.log('new : ' + result.newWidth + ', ' + result.newHeight); - * }); - */ - loadImageFromURL(url, imageName) { - if (!imageName || !url) { - return Promise.reject(rejectMessages.invalidParameters); - } - - return this.execute(commands.LOAD_IMAGE, imageName, url); - } - - /** - * Add image object on canvas - * @param {string} imgUrl - Image url to make object - * @returns {Promise} - * @example - * imageEditor.addImageObject('path/fileName.jpg').then(objectProps => { - * console.log(ojectProps.id); - * }); - */ - addImageObject(imgUrl) { - if (!imgUrl) { - return Promise.reject(rejectMessages.invalidParameters); - } - - return this.execute(commands.ADD_IMAGE_OBJECT, imgUrl); - } - - /** - * Start a drawing mode. If the current mode is not 'NORMAL', 'stopDrawingMode()' will be called first. - * @param {String} mode Can be one of 'CROPPER', 'FREE_DRAWING', 'LINE_DRAWING', 'TEXT', 'SHAPE' - * @param {Object} [option] parameters of drawing mode, it's available with 'FREE_DRAWING', 'LINE_DRAWING' - * @param {Number} [option.width] brush width - * @param {String} [option.color] brush color - * @param {Object} [option.arrowType] arrow decorate - * @param {string} [option.arrowType.tail] arrow decorate for tail. 'chevron' or 'triangle' - * @param {string} [option.arrowType.head] arrow decorate for head. 'chevron' or 'triangle' - * @returns {boolean} true if success or false - * @example - * imageEditor.startDrawingMode('FREE_DRAWING', { - * width: 10, - * color: 'rgba(255,0,0,0.5)' - * }); - * imageEditor.startDrawingMode('LINE_DRAWING', { - * width: 10, - * color: 'rgba(255,0,0,0.5)', - * arrowType: { - * tail: 'chevron' // triangle - * } - * }); - * - */ - startDrawingMode(mode, option) { - return this._graphics.startDrawingMode(mode, option); - } + this._invoker = new Invoker(); /** - * Stop the current drawing mode and back to the 'NORMAL' mode - * @example - * imageEditor.stopDrawingMode(); - */ - stopDrawingMode() { - this._graphics.stopDrawingMode(); - } - - /** - * Crop this image with rect - * @param {Object} rect crop rect - * @param {Number} rect.left left position - * @param {Number} rect.top top position - * @param {Number} rect.width width - * @param {Number} rect.height height - * @returns {Promise} - * @example - * imageEditor.crop(imageEditor.getCropzoneRect()); - */ - crop(rect) { - const data = this._graphics.getCroppedImageData(rect); - if (!data) { - return Promise.reject(rejectMessages.invalidParameters); - } - - return this.loadImageFromURL(data.url, data.imageName); - } - - /** - * Get the cropping rect - * @returns {Object} {{left: number, top: number, width: number, height: number}} rect - */ - getCropzoneRect() { - return this._graphics.getCropzoneRect(); - } - - /** - * Set the cropping rect - * @param {number} [mode] crop rect mode [1, 1.5, 1.3333333333333333, 1.25, 1.7777777777777777] - */ - setCropzoneRect(mode) { - this._graphics.setCropzoneRect(mode); - } - - /** - * Flip - * @returns {Promise} - * @param {string} type - 'flipX' or 'flipY' or 'reset' - * @returns {Promise} + * Graphics instance + * @type {Graphics} * @private */ - _flip(type) { - return this.execute(commands.FLIP_IMAGE, type); - } - - /** - * Flip x - * @returns {Promise} - * @example - * imageEditor.flipX().then((status => { - * console.log('flipX: ', status.flipX); - * console.log('flipY: ', status.flipY); - * console.log('angle: ', status.angle); - * }).catch(message => { - * console.log('error: ', message); - * }); - */ - flipX() { - return this._flip('flipX'); - } + this._graphics = new Graphics(this.ui ? this.ui.getEditorArea() : wrapper, { + cssMaxWidth: options.cssMaxWidth, + cssMaxHeight: options.cssMaxHeight, + }); /** - * Flip y - * @returns {Promise} - * @example - * imageEditor.flipY().then(status => { - * console.log('flipX: ', status.flipX); - * console.log('flipY: ', status.flipY); - * console.log('angle: ', status.angle); - * }).catch(message => { - * console.log('error: ', message); - * }); - */ - flipY() { - return this._flip('flipY'); - } - - /** - * Reset flip - * @returns {Promise} - * @example - * imageEditor.resetFlip().then(status => { - * console.log('flipX: ', status.flipX); - * console.log('flipY: ', status.flipY); - * console.log('angle: ', status.angle); - * }).catch(message => { - * console.log('error: ', message); - * });; - */ - resetFlip() { - return this._flip('reset'); - } - - /** - * @param {string} type - 'rotate' or 'setAngle' - * @param {number} angle - angle value (degree) - * @param {boolean} isSilent - is silent execution or not - * @returns {Promise} + * Event handler list + * @type {Object} * @private */ - _rotate(type, angle, isSilent) { - let result = null; - if (isSilent) { - result = this.executeSilent(commands.ROTATE_IMAGE, type, angle); - } else { - result = this.execute(commands.ROTATE_IMAGE, type, angle); + this._handlers = { + keydown: this._onKeyDown.bind(this), + mousedown: this._onMouseDown.bind(this), + objectActivated: this._onObjectActivated.bind(this), + objectMoved: this._onObjectMoved.bind(this), + objectScaled: this._onObjectScaled.bind(this), + objectRotated: this._onObjectRotated.bind(this), + objectAdded: this._onObjectAdded.bind(this), + objectModified: this._onObjectModified.bind(this), + createdPath: this._onCreatedPath, + addText: this._onAddText.bind(this), + addObject: this._onAddObject.bind(this), + textEditing: this._onTextEditing.bind(this), + textChanged: this._onTextChanged.bind(this), + iconCreateResize: this._onIconCreateResize.bind(this), + iconCreateEnd: this._onIconCreateEnd.bind(this), + selectionCleared: this._selectionCleared.bind(this), + selectionCreated: this._selectionCreated.bind(this), + }; + + this._attachInvokerEvents(); + this._attachGraphicsEvents(); + this._attachDomEvents(); + this._setSelectionStyle(options.selectionStyle, { + applyCropSelectionStyle: options.applyCropSelectionStyle, + applyGroupSelectionStyle: options.applyGroupSelectionStyle, + }); + + if (options.usageStatistics) { + sendHostName(); + } + + if (this.ui) { + this.ui.initCanvas(); + this.setReAction(); + } + fabric.enableGLFiltering = false; + } + + /** + * Set selection style by init option + * @param {Object} selectionStyle - Selection styles + * @param {Object} applyTargets - Selection apply targets + * @param {boolean} applyCropSelectionStyle - whether apply with crop selection style or not + * @param {boolean} applyGroupSelectionStyle - whether apply with group selection style or not + * @private + */ + _setSelectionStyle(selectionStyle, { applyCropSelectionStyle, applyGroupSelectionStyle }) { + if (selectionStyle) { + this._graphics.setSelectionStyle(selectionStyle); + } + + if (applyCropSelectionStyle) { + this._graphics.setCropSelectionStyle(selectionStyle); + } + + if (applyGroupSelectionStyle) { + this.on('selectionCreated', (eventTarget) => { + if (eventTarget.type === 'activeSelection') { + eventTarget.set(selectionStyle); } - - return result; + }); } + } - /** - * Rotate image - * @returns {Promise} - * @param {number} angle - Additional angle to rotate image - * @param {boolean} isSilent - is silent execution or not - * @returns {Promise} - * @example - * imageEditor.rotate(10); // angle = 10 - * imageEditor.rotate(10); // angle = 20 - * imageEidtor.rotate(5); // angle = 5 - * imageEidtor.rotate(-95); // angle = -90 - * imageEditor.rotate(10).then(status => { - * console.log('angle: ', status.angle); - * })).catch(message => { - * console.log('error: ', message); - * }); - */ - rotate(angle, isSilent) { - return this._rotate('rotate', angle, isSilent); - } + /** + * Attach invoker events + * @private + */ + _attachInvokerEvents() { + const { UNDO_STACK_CHANGED, REDO_STACK_CHANGED } = events; /** - * Set angle - * @param {number} angle - Angle of image - * @param {boolean} isSilent - is silent execution or not - * @returns {Promise} + * Undo stack changed event + * @event ImageEditor#undoStackChanged + * @param {Number} length - undo stack length * @example - * imageEditor.setAngle(10); // angle = 10 - * imageEditor.rotate(10); // angle = 20 - * imageEidtor.setAngle(5); // angle = 5 - * imageEidtor.rotate(50); // angle = 55 - * imageEidtor.setAngle(-40); // angle = -40 - * imageEditor.setAngle(10).then(status => { - * console.log('angle: ', status.angle); - * })).catch(message => { - * console.log('error: ', message); + * imageEditor.on('undoStackChanged', function(length) { + * console.log(length); * }); */ - setAngle(angle, isSilent) { - return this._rotate('setAngle', angle, isSilent); - } - + this._invoker.on(UNDO_STACK_CHANGED, this.fire.bind(this, UNDO_STACK_CHANGED)); /** - * Set drawing brush - * @param {Object} option brush option - * @param {Number} option.width width - * @param {String} option.color color like 'FFFFFF', 'rgba(0, 0, 0, 0.5)' - * @example - * imageEditor.startDrawingMode('FREE_DRAWING'); - * imageEditor.setBrush({ - * width: 12, - * color: 'rgba(0, 0, 0, 0.5)' - * }); - * imageEditor.setBrush({ - * width: 8, - * color: 'FFFFFF' - * }); - */ - setBrush(option) { - this._graphics.setBrush(option); - } - - /** - * Set states of current drawing shape - * @param {string} type - Shape type (ex: 'rect', 'circle', 'triangle') - * @param {Object} [options] - Shape options - * @param {(ShapeFillOption | string)} [options.fill] - {@link ShapeFillOption} or - * Shape foreground color (ex: '#fff', 'transparent') - * @param {string} [options.stoke] - Shape outline color - * @param {number} [options.strokeWidth] - Shape outline width - * @param {number} [options.width] - Width value (When type option is 'rect', this options can use) - * @param {number} [options.height] - Height value (When type option is 'rect', this options can use) - * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use) - * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use) - * @param {number} [options.isRegular] - Whether resizing shape has 1:1 ratio or not - * @example - * imageEditor.setDrawingShape('rect', { - * fill: 'red', - * width: 100, - * height: 200 - * }); - * @example - * imageEditor.setDrawingShape('rect', { - * fill: { - * type: 'filter', - * filter: [{blur: 0.3}, {pixelate: 20}] - * }, - * width: 100, - * height: 200 - * }); - * @example - * imageEditor.setDrawingShape('circle', { - * fill: 'transparent', - * stroke: 'blue', - * strokeWidth: 3, - * rx: 10, - * ry: 100 - * }); - * @example - * imageEditor.setDrawingShape('triangle', { // When resizing, the shape keep the 1:1 ratio - * width: 1, - * height: 1, - * isRegular: true - * }); + * Redo stack changed event + * @event ImageEditor#redoStackChanged + * @param {Number} length - redo stack length * @example - * imageEditor.setDrawingShape('circle', { // When resizing, the shape keep the 1:1 ratio - * rx: 10, - * ry: 10, - * isRegular: true + * imageEditor.on('redoStackChanged', function(length) { + * console.log(length); * }); */ - setDrawingShape(type, options) { - this._graphics.setDrawingShape(type, options); - } - - setDrawingIcon(type, iconColor) { - this._graphics.setIconStyle(type, iconColor); - } - - /** - * Add shape - * @param {string} type - Shape type (ex: 'rect', 'circle', 'triangle') - * @param {Object} options - Shape options - * @param {(ShapeFillOption | string)} [options.fill] - {@link ShapeFillOption} or - * Shape foreground color (ex: '#fff', 'transparent') - * @param {string} [options.stroke] - Shape outline color - * @param {number} [options.strokeWidth] - Shape outline width - * @param {number} [options.width] - Width value (When type option is 'rect', this options can use) - * @param {number} [options.height] - Height value (When type option is 'rect', this options can use) - * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use) - * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use) - * @param {number} [options.left] - Shape x position - * @param {number} [options.top] - Shape y position - * @param {boolean} [options.isRegular] - Whether resizing shape has 1:1 ratio or not - * @returns {Promise} - * @example - * imageEditor.addShape('rect', { - * fill: 'red', - * stroke: 'blue', - * strokeWidth: 3, - * width: 100, - * height: 200, - * left: 10, - * top: 10, - * isRegular: true - * }); - * @example - * imageEditor.addShape('circle', { - * fill: 'red', - * stroke: 'blue', - * strokeWidth: 3, - * rx: 10, - * ry: 100, - * isRegular: false - * }).then(objectProps => { - * console.log(objectProps.id); - * }); - * @example - * imageEditor.addShape('rect', { - * fill: { - * type: 'filter', - * filter: [{blur: 0.3}, {pixelate: 20}] - * }, - * stroke: 'blue', - * strokeWidth: 3, - * rx: 10, - * ry: 100, - * isRegular: false - * }).then(objectProps => { - * console.log(objectProps.id); - * }); - */ - addShape(type, options) { - options = options || {}; - - this._setPositions(options); - - return this.execute(commands.ADD_SHAPE, type, options); - } - - /** - * Change shape - * @param {number} id - object id - * @param {Object} options - Shape options - * @param {(ShapeFillOption | string)} [options.fill] - {@link ShapeFillOption} or - * Shape foreground color (ex: '#fff', 'transparent') - * @param {string} [options.stroke] - Shape outline color - * @param {number} [options.strokeWidth] - Shape outline width - * @param {number} [options.width] - Width value (When type option is 'rect', this options can use) - * @param {number} [options.height] - Height value (When type option is 'rect', this options can use) - * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use) - * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use) - * @param {boolean} [options.isRegular] - Whether resizing shape has 1:1 ratio or not - * @param {boolean} isSilent - is silent execution or not - * @returns {Promise} - * @example - * // call after selecting shape object on canvas - * imageEditor.changeShape(id, { // change rectagle or triangle - * fill: 'red', - * stroke: 'blue', - * strokeWidth: 3, - * width: 100, - * height: 200 - * }); - * @example - * // call after selecting shape object on canvas - * imageEditor.changeShape(id, { // change circle - * fill: 'red', - * stroke: 'blue', - * strokeWidth: 3, - * rx: 10, - * ry: 100 - * }); - */ - changeShape(id, options, isSilent) { - const executeMethodName = isSilent ? 'executeSilent' : 'execute'; - - return this[executeMethodName](commands.CHANGE_SHAPE, id, options); - } - - /** - * Add text on image - * @param {string} text - Initial input text - * @param {Object} [options] Options for generating text - * @param {Object} [options.styles] Initial styles - * @param {string} [options.styles.fill] Color - * @param {string} [options.styles.fontFamily] Font type for text - * @param {number} [options.styles.fontSize] Size - * @param {string} [options.styles.fontStyle] Type of inclination (normal / italic) - * @param {string} [options.styles.fontWeight] Type of thicker or thinner looking (normal / bold) - * @param {string} [options.styles.textAlign] Type of text align (left / center / right) - * @param {string} [options.styles.textDecoration] Type of line (underline / line-through / overline) - * @param {{x: number, y: number}} [options.position] - Initial position - * @param {boolean} [options.autofocus] - text autofocus, default is true - * @returns {Promise} - * @example - * imageEditor.addText('init text'); - * @example - * imageEditor.addText('init text', { - * styles: { - * fill: '#000', - * fontSize: 20, - * fontWeight: 'bold' - * }, - * position: { - * x: 10, - * y: 10 - * } - * }).then(objectProps => { - * console.log(objectProps.id); - * }); - */ - addText(text, options) { - text = text || ''; - options = options || {}; - - return this.execute(commands.ADD_TEXT, text, options); - } - - /** - * Change contents of selected text object on image - * @param {number} id - object id - * @param {string} text - Changing text - * @returns {Promise} - * @example - * imageEditor.changeText(id, 'change text'); - */ - changeText(id, text) { - text = text || ''; - - return this.execute(commands.CHANGE_TEXT, id, text); - } - - /** - * Set style - * @param {number} id - object id - * @param {Object} styleObj - text styles - * @param {string} [styleObj.fill] Color - * @param {string} [styleObj.fontFamily] Font type for text - * @param {number} [styleObj.fontSize] Size - * @param {string} [styleObj.fontStyle] Type of inclination (normal / italic) - * @param {string} [styleObj.fontWeight] Type of thicker or thinner looking (normal / bold) - * @param {string} [styleObj.textAlign] Type of text align (left / center / right) - * @param {string} [styleObj.textDecoration] Type of line (underline / line-through / overline) - * @param {boolean} isSilent - is silent execution or not - * @returns {Promise} - * @example - * imageEditor.changeTextStyle(id, { - * fontStyle: 'italic' - * }); - */ - changeTextStyle(id, styleObj, isSilent) { - const executeMethodName = isSilent ? 'executeSilent' : 'execute'; - - return this[executeMethodName](commands.CHANGE_TEXT_STYLE, id, styleObj); - } - - /** - * change text mode - * @param {string} type - change type - * @private - */ - _changeActivateMode(type) { - if (type !== 'ICON' && this.getDrawingMode() !== type) { - this.startDrawingMode(type); - } - } - - /** - * 'textChanged' event handler - * @param {Object} objectProps changed object properties - * @private - */ - _onTextChanged(objectProps) { - this.changeText(objectProps.id, objectProps.text); - } - - /** - * 'iconCreateResize' event handler - * @param {Object} originPointer origin pointer - * @param {Number} originPointer.x x position - * @param {Number} originPointer.y y position - * @private - */ - _onIconCreateResize(originPointer) { - this.fire(events.ICON_CREATE_RESIZE, originPointer); - } - - /** - * 'iconCreateEnd' event handler + this._invoker.on(REDO_STACK_CHANGED, this.fire.bind(this, REDO_STACK_CHANGED)); + } + + /** + * Attach canvas events + * @private + */ + _attachGraphicsEvents() { + this._graphics.on({ + [MOUSE_DOWN]: this._handlers.mousedown, + [OBJECT_MOVED]: this._handlers.objectMoved, + [OBJECT_SCALED]: this._handlers.objectScaled, + [OBJECT_ROTATED]: this._handlers.objectRotated, + [OBJECT_ACTIVATED]: this._handlers.objectActivated, + [OBJECT_ADDED]: this._handlers.objectAdded, + [OBJECT_MODIFIED]: this._handlers.objectModified, + [ADD_TEXT]: this._handlers.addText, + [ADD_OBJECT]: this._handlers.addObject, + [TEXT_EDITING]: this._handlers.textEditing, + [TEXT_CHANGED]: this._handlers.textChanged, + [ICON_CREATE_RESIZE]: this._handlers.iconCreateResize, + [ICON_CREATE_END]: this._handlers.iconCreateEnd, + [SELECTION_CLEARED]: this._handlers.selectionCleared, + [SELECTION_CREATED]: this._handlers.selectionCreated, + }); + } + + /** + * Attach dom events + * @private + */ + _attachDomEvents() { + // ImageEditor supports IE 9 higher + document.addEventListener('keydown', this._handlers.keydown); + } + + /** + * Detach dom events + * @private + */ + _detachDomEvents() { + // ImageEditor supports IE 9 higher + document.removeEventListener('keydown', this._handlers.keydown); + } + + /** + * Keydown event handler + * @param {KeyboardEvent} e - Event object + * @private + */ + /* eslint-disable complexity */ + _onKeyDown(e) { + const { ctrlKey, keyCode, metaKey } = e; + const isModifierKey = ctrlKey || metaKey; + + if (isModifierKey) { + if (keyCode === keyCodes.C) { + this._graphics.resetTargetObjectForCopyPaste(); + } else if (keyCode === keyCodes.V) { + this._graphics.pasteObject(); + this.clearRedoStack(); + } else if (keyCode === keyCodes.Z) { + // There is no error message on shortcut when it's empty + this.undo()['catch'](() => {}); + } else if (keyCode === keyCodes.Y) { + // There is no error message on shortcut when it's empty + this.redo()['catch'](() => {}); + } + } + + const isDeleteKey = keyCode === keyCodes.BACKSPACE || keyCode === keyCodes.DEL; + const isRemoveReady = this._graphics.isReadyRemoveObject(); + + if (isRemoveReady && isDeleteKey) { + e.preventDefault(); + this.removeActiveObject(); + } + } + + /** + * Remove Active Object + */ + removeActiveObject() { + const activeObjectId = this._graphics.getActiveObjectIdForRemove(); + + this.removeObject(activeObjectId); + } + + /** + * mouse down event handler + * @param {Event} event - mouse down event + * @param {Object} originPointer - origin pointer + * @param {Number} originPointer.x x position + * @param {Number} originPointer.y y position + * @private + */ + _onMouseDown(event, originPointer) { + /** + * The mouse down event with position x, y on canvas + * @event ImageEditor#mousedown + * @param {Object} event - browser mouse event object * @param {Object} originPointer origin pointer * @param {Number} originPointer.x x position * @param {Number} originPointer.y y position - * @private - */ - _onIconCreateEnd(originPointer) { - this.fire(events.ICON_CREATE_END, originPointer); - } - - /** - * 'textEditing' event handler - * @private - */ - _onTextEditing() { - /** - * The event which starts to edit text object - * @event ImageEditor#textEditing - * @example - * imageEditor.on('textEditing', function() { - * console.log('text editing'); - * }); - */ - this.fire(events.TEXT_EDITING); - } - - /** - * Mousedown event handler in case of 'TEXT' drawing mode - * @param {fabric.Event} event - Current mousedown event object - * @private - */ - _onAddText(event) { - /** - * The event when 'TEXT' drawing mode is enabled and click non-object area. - * @event ImageEditor#addText - * @param {Object} pos - * @param {Object} pos.originPosition - Current position on origin canvas - * @param {Number} pos.originPosition.x - x - * @param {Number} pos.originPosition.y - y - * @param {Object} pos.clientPosition - Current position on client area - * @param {Number} pos.clientPosition.x - x - * @param {Number} pos.clientPosition.y - y - * @example - * imageEditor.on('addText', function(pos) { - * console.log('text position on canvas: ' + pos.originPosition); - * console.log('text position on brwoser: ' + pos.clientPosition); - * }); - */ - this.fire(events.ADD_TEXT, { - originPosition: event.originPosition, - clientPosition: event.clientPosition - }); - } - - /** - * 'addObject' event handler - * @param {Object} objectProps added object properties - * @private - */ - _onAddObject(objectProps) { - const obj = this._graphics.getObject(objectProps.id); - this._pushAddObjectCommand(obj); - } - - /** - * 'objectAdded' event handler - * @param {Object} objectProps added object properties - * @private - */ - _onObjectAdded(objectProps) { - /** - * The event when object added - * @event ImageEditor#objectAdded - * @param {ObjectProps} props - object properties - * @example - * imageEditor.on('objectAdded', function(props) { - * console.log(props); - * }); - */ - this.fire(OBJECT_ADDED, objectProps); - - /** - * The event when object added (deprecated) - * @event ImageEditor#addObjectAfter - * @param {ObjectProps} props - object properties - * @deprecated - */ - this.fire(ADD_OBJECT_AFTER, objectProps); - } - - /** - * 'objectModified' event handler - * @param {fabric.Object} obj - selection object - * @private - */ - _onObjectModified(obj) { - this._pushModifyObjectCommand(obj); - } - - /** - * 'selectionCleared' event handler - * @private - */ - _selectionCleared() { - this.fire(SELECTION_CLEARED); - } - - /** - * 'selectionCreated' event handler - * @param {Object} eventTarget - Fabric object - * @private - */ - _selectionCreated(eventTarget) { - this.fire(SELECTION_CREATED, eventTarget); - } - - /** - * Register custom icons - * @param {{iconType: string, pathValue: string}} infos - Infos to register icons * @example - * imageEditor.registerIcons({ - * customIcon: 'M 0 0 L 20 20 L 10 10 Z', - * customArrow: 'M 60 0 L 120 60 H 90 L 75 45 V 180 H 45 V 45 L 30 60 H 0 Z' + * imageEditor.on('mousedown', function(event, originPointer) { + * console.log(event); + * console.log(originPointer); + * if (imageEditor.hasFilter('colorFilter')) { + * imageEditor.applyFilter('colorFilter', { + * x: parseInt(originPointer.x, 10), + * y: parseInt(originPointer.y, 10) + * }); + * } * }); */ - registerIcons(infos) { - this._graphics.registerPaths(infos); - } - /** - * Change canvas cursor type - * @param {string} cursorType - cursor type + this.fire(events.MOUSE_DOWN, event, originPointer); + } + + /** + * Add a 'addObject' command + * @param {Object} obj - Fabric object + * @private + */ + _pushAddObjectCommand(obj) { + const command = commandFactory.create(commands.ADD_OBJECT, this._graphics, obj); + this._invoker.pushUndoStack(command); + } + + /** + * Add a 'changeSelection' command + * @param {fabric.Object} obj - selection object + * @private + */ + _pushModifyObjectCommand(obj) { + const { type } = obj; + const props = makeSelectionUndoData(obj, (item) => + makeSelectionUndoDatum(this._graphics.getObjectId(item), item, type === 'activeSelection') + ); + const command = commandFactory.create(commands.CHANGE_SELECTION, this._graphics, props); + command.execute(this._graphics, props); + + this._invoker.pushUndoStack(command); + } + + /** + * 'objectActivated' event handler + * @param {ObjectProps} props - object properties + * @private + */ + _onObjectActivated(props) { + /** + * The event when object is selected(aka activated). + * @event ImageEditor#objectActivated + * @param {ObjectProps} objectProps - object properties * @example - * imageEditor.changeCursor('crosshair'); - */ - changeCursor(cursorType) { - this._graphics.changeCursor(cursorType); - } - - /** - * Add icon on canvas - * @param {string} type - Icon type ('arrow', 'cancel', custom icon name) - * @param {Object} options - Icon options - * @param {string} [options.fill] - Icon foreground color - * @param {number} [options.left] - Icon x position - * @param {number} [options.top] - Icon y position - * @returns {Promise} - * @example - * imageEditor.addIcon('arrow'); // The position is center on canvas - * @example - * imageEditor.addIcon('arrow', { - * left: 100, - * top: 100 - * }).then(objectProps => { - * console.log(objectProps.id); + * imageEditor.on('objectActivated', function(props) { + * console.log(props); + * console.log(props.type); + * console.log(props.id); * }); */ - addIcon(type, options) { - options = options || {}; - - this._setPositions(options); - - return this.execute(commands.ADD_ICON, type, options); - } - - /** - * Change icon color - * @param {number} id - object id - * @param {string} color - Color for icon - * @returns {Promise} - * @example - * imageEditor.changeIconColor(id, '#000000'); - */ - changeIconColor(id, color) { - return this.execute(commands.CHANGE_ICON_COLOR, id, color); - } - - /** - * Remove an object or group by id - * @param {number} id - object id - * @returns {Promise} - * @example - * imageEditor.removeObject(id); - */ - removeObject(id) { - return this.execute(commands.REMOVE_OBJECT, id); - } - - /** - * Whether it has the filter or not - * @param {string} type - Filter type - * @returns {boolean} true if it has the filter - */ - hasFilter(type) { - return this._graphics.hasFilter(type); - } + this.fire(events.OBJECT_ACTIVATED, props); + } + /** + * 'objectMoved' event handler + * @param {ObjectProps} props - object properties + * @private + */ + _onObjectMoved(props) { /** - * Remove filter on canvas image - * @param {string} type - Filter type - * @returns {Promise} + * The event when object is moved + * @event ImageEditor#objectMoved + * @param {ObjectProps} props - object properties * @example - * imageEditor.removeFilter('Grayscale').then(obj => { - * console.log('filterType: ', obj.type); - * console.log('actType: ', obj.action); - * }).catch(message => { - * console.log('error: ', message); + * imageEditor.on('objectMoved', function(props) { + * console.log(props); + * console.log(props.type); * }); */ - removeFilter(type) { - return this.execute(commands.REMOVE_FILTER, type); - } + this.fire(events.OBJECT_MOVED, props); + } + /** + * 'objectScaled' event handler + * @param {ObjectProps} props - object properties + * @private + */ + _onObjectScaled(props) { /** - * Apply filter on canvas image - * @param {string} type - Filter type - * @param {Object} options - Options to apply filter - * @param {number} options.maskObjId - masking image object id - * @param {boolean} isSilent - is silent execution or not - * @returns {Promise} - * @example - * imageEditor.applyFilter('Grayscale'); - * @example - * imageEditor.applyFilter('mask', {maskObjId: id}).then(obj => { - * console.log('filterType: ', obj.type); - * console.log('actType: ', obj.action); - * }).catch(message => { - * console.log('error: ', message); - * });; - */ - applyFilter(type, options, isSilent) { - const executeMethodName = isSilent ? 'executeSilent' : 'execute'; - - return this[executeMethodName](commands.APPLY_FILTER, type, options); - } - - /** - * Get data url - * @param {Object} options - options for toDataURL - * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png" - * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg. - * @param {Number} [options.multiplier=1] Multiplier to scale by - * @param {Number} [options.left] Cropping left offset. Introduced in fabric v1.2.14 - * @param {Number} [options.top] Cropping top offset. Introduced in fabric v1.2.14 - * @param {Number} [options.width] Cropping width. Introduced in fabric v1.2.14 - * @param {Number} [options.height] Cropping height. Introduced in fabric v1.2.14 - * @returns {string} A DOMString containing the requested data URI + * The event when scale factor is changed + * @event ImageEditor#objectScaled + * @param {ObjectProps} props - object properties * @example - * imgEl.src = imageEditor.toDataURL(); - * - * imageEditor.loadImageFromURL(imageEditor.toDataURL(), 'FilterImage').then(() => { - * imageEditor.addImageObject(imgUrl); + * imageEditor.on('objectScaled', function(props) { + * console.log(props); + * console.log(props.type); * }); */ - toDataURL(options) { - return this._graphics.toDataURL(options); - } - - /** - * Get image name - * @returns {string} image name - * @example - * console.log(imageEditor.getImageName()); - */ - getImageName() { - return this._graphics.getImageName(); - } - - /** - * Clear undoStack - * @example - * imageEditor.clearUndoStack(); - */ - clearUndoStack() { - this._invoker.clearUndoStack(); - } - - /** - * Clear redoStack - * @example - * imageEditor.clearRedoStack(); - */ - clearRedoStack() { - this._invoker.clearRedoStack(); - } - - /** - * Whehter the undo stack is empty or not - * @returns {boolean} - * imageEditor.isEmptyUndoStack(); - */ - isEmptyUndoStack() { - return this._invoker.isEmptyUndoStack(); - } - - /** - * Whehter the redo stack is empty or not - * @returns {boolean} - * imageEditor.isEmptyRedoStack(); - */ - isEmptyRedoStack() { - return this._invoker.isEmptyRedoStack(); - } - - /** - * Resize canvas dimension - * @param {{width: number, height: number}} dimension - Max width & height - * @returns {Promise} - */ - resizeCanvasDimension(dimension) { - if (!dimension) { - return Promise.reject(rejectMessages.invalidParameters); - } - - return this.execute(commands.RESIZE_CANVAS_DIMENSION, dimension); - } - - /** - * Destroy - */ - destroy() { - this.stopDrawingMode(); - this._detachDomEvents(); - this._graphics.destroy(); - this._graphics = null; - - if (this.ui) { - this.ui.destroy(); - } - - forEach(this, (value, key) => { - this[key] = null; - }, this); - } - - /** - * Set position - * @param {Object} options - Position options (left or top) - * @private - */ - _setPositions(options) { - const centerPosition = this._graphics.getCenter(); - - if (isUndefined(options.left)) { - options.left = centerPosition.left; - } - - if (isUndefined(options.top)) { - options.top = centerPosition.top; - } - } + this.fire(events.OBJECT_SCALED, props); + } + /** + * 'objectRotated' event handler + * @param {ObjectProps} props - object properties + * @private + */ + _onObjectRotated(props) { /** - * Set properties of active object - * @param {number} id - object id - * @param {Object} keyValue - key & value - * @returns {Promise} + * The event when object angle is changed + * @event ImageEditor#objectRotated + * @param {ObjectProps} props - object properties * @example - * imageEditor.setObjectProperties(id, { - * left:100, - * top:100, - * width: 200, - * height: 200, - * opacity: 0.5 + * imageEditor.on('objectRotated', function(props) { + * console.log(props); + * console.log(props.type); * }); */ - setObjectProperties(id, keyValue) { - return this.execute(commands.SET_OBJECT_PROPERTIES, id, keyValue); - } - - /** - * Set properties of active object, Do not leave an invoke history. - * @param {number} id - object id - * @param {Object} keyValue - key & value + this.fire(events.OBJECT_ROTATED, props); + } + + /** + * Get current drawing mode + * @returns {string} + * @example + * // Image editor drawing mode + * // + * // NORMAL: 'NORMAL' + * // CROPPER: 'CROPPER' + * // FREE_DRAWING: 'FREE_DRAWING' + * // LINE_DRAWING: 'LINE_DRAWING' + * // TEXT: 'TEXT' + * // + * if (imageEditor.getDrawingMode() === 'FREE_DRAWING') { + * imageEditor.stopDrawingMode(); + * } + */ + getDrawingMode() { + return this._graphics.getDrawingMode(); + } + + /** + * Clear all objects + * @returns {Promise} + * @example + * imageEditor.clearObjects(); + */ + clearObjects() { + return this.execute(commands.CLEAR_OBJECTS); + } + + /** + * Deactivate all objects + * @example + * imageEditor.deactivateAll(); + */ + deactivateAll() { + this._graphics.deactivateAll(); + this._graphics.renderAll(); + } + + /** + * discard selction + * @example + * imageEditor.discardSelection(); + */ + discardSelection() { + this._graphics.discardSelection(); + } + + /** + * selectable status change + * @param {boolean} selectable - selctable status + * @example + * imageEditor.changeSelectableAll(false); // or true + */ + changeSelectableAll(selectable) { + this._graphics.changeSelectableAll(selectable); + } + + /** + * Invoke command + * @param {String} commandName - Command name + * @param {...*} args - Arguments for creating command + * @returns {Promise} + * @private + */ + execute(commandName, ...args) { + // Inject an Graphics instance as first parameter + const theArgs = [this._graphics].concat(args); + + return this._invoker.execute(commandName, ...theArgs); + } + + /** + * Invoke command + * @param {String} commandName - Command name + * @param {...*} args - Arguments for creating command + * @returns {Promise} + * @private + */ + executeSilent(commandName, ...args) { + // Inject an Graphics instance as first parameter + const theArgs = [this._graphics].concat(args); + + return this._invoker.executeSilent(commandName, ...theArgs); + } + + /** + * Undo + * @returns {Promise} + * @example + * imageEditor.undo(); + */ + undo() { + return this._invoker.undo(); + } + + /** + * Redo + * @returns {Promise} + * @example + * imageEditor.redo(); + */ + redo() { + return this._invoker.redo(); + } + + /** + * Load image from file + * @param {File} imgFile - Image file + * @param {string} [imageName] - imageName + * @returns {Promise} + * @example + * imageEditor.loadImageFromFile(file).then(result => { + * console.log('old : ' + result.oldWidth + ', ' + result.oldHeight); + * console.log('new : ' + result.newWidth + ', ' + result.newHeight); + * }); + */ + loadImageFromFile(imgFile, imageName) { + if (!imgFile) { + return Promise.reject(rejectMessages.invalidParameters); + } + + const imgUrl = URL.createObjectURL(imgFile); + imageName = imageName || imgFile.name; + + return this.loadImageFromURL(imgUrl, imageName).then((value) => { + URL.revokeObjectURL(imgFile); + + return value; + }); + } + + /** + * Load image from url + * @param {string} url - File url + * @param {string} imageName - imageName + * @returns {Promise} + * @example + * imageEditor.loadImageFromURL('http://url/testImage.png', 'lena').then(result => { + * console.log('old : ' + result.oldWidth + ', ' + result.oldHeight); + * console.log('new : ' + result.newWidth + ', ' + result.newHeight); + * }); + */ + loadImageFromURL(url, imageName) { + if (!imageName || !url) { + return Promise.reject(rejectMessages.invalidParameters); + } + + return this.execute(commands.LOAD_IMAGE, imageName, url); + } + + /** + * Add image object on canvas + * @param {string} imgUrl - Image url to make object + * @returns {Promise} + * @example + * imageEditor.addImageObject('path/fileName.jpg').then(objectProps => { + * console.log(ojectProps.id); + * }); + */ + addImageObject(imgUrl) { + if (!imgUrl) { + return Promise.reject(rejectMessages.invalidParameters); + } + + return this.execute(commands.ADD_IMAGE_OBJECT, imgUrl); + } + + /** + * Start a drawing mode. If the current mode is not 'NORMAL', 'stopDrawingMode()' will be called first. + * @param {String} mode Can be one of 'CROPPER', 'FREE_DRAWING', 'LINE_DRAWING', 'TEXT', 'SHAPE' + * @param {Object} [option] parameters of drawing mode, it's available with 'FREE_DRAWING', 'LINE_DRAWING' + * @param {Number} [option.width] brush width + * @param {String} [option.color] brush color + * @param {Object} [option.arrowType] arrow decorate + * @param {string} [option.arrowType.tail] arrow decorate for tail. 'chevron' or 'triangle' + * @param {string} [option.arrowType.head] arrow decorate for head. 'chevron' or 'triangle' + * @returns {boolean} true if success or false + * @example + * imageEditor.startDrawingMode('FREE_DRAWING', { + * width: 10, + * color: 'rgba(255,0,0,0.5)' + * }); + * imageEditor.startDrawingMode('LINE_DRAWING', { + * width: 10, + * color: 'rgba(255,0,0,0.5)', + * arrowType: { + * tail: 'chevron' // triangle + * } + * }); + * + */ + startDrawingMode(mode, option) { + return this._graphics.startDrawingMode(mode, option); + } + + /** + * Stop the current drawing mode and back to the 'NORMAL' mode + * @example + * imageEditor.stopDrawingMode(); + */ + stopDrawingMode() { + this._graphics.stopDrawingMode(); + } + + /** + * Crop this image with rect + * @param {Object} rect crop rect + * @param {Number} rect.left left position + * @param {Number} rect.top top position + * @param {Number} rect.width width + * @param {Number} rect.height height + * @returns {Promise} + * @example + * imageEditor.crop(imageEditor.getCropzoneRect()); + */ + crop(rect) { + const data = this._graphics.getCroppedImageData(rect); + if (!data) { + return Promise.reject(rejectMessages.invalidParameters); + } + + return this.loadImageFromURL(data.url, data.imageName); + } + + /** + * Get the cropping rect + * @returns {Object} {{left: number, top: number, width: number, height: number}} rect + */ + getCropzoneRect() { + return this._graphics.getCropzoneRect(); + } + + /** + * Set the cropping rect + * @param {number} [mode] crop rect mode [1, 1.5, 1.3333333333333333, 1.25, 1.7777777777777777] + */ + setCropzoneRect(mode) { + this._graphics.setCropzoneRect(mode); + } + + /** + * Flip + * @returns {Promise} + * @param {string} type - 'flipX' or 'flipY' or 'reset' + * @returns {Promise} + * @private + */ + _flip(type) { + return this.execute(commands.FLIP_IMAGE, type); + } + + /** + * Flip x + * @returns {Promise} + * @example + * imageEditor.flipX().then((status => { + * console.log('flipX: ', status.flipX); + * console.log('flipY: ', status.flipY); + * console.log('angle: ', status.angle); + * }).catch(message => { + * console.log('error: ', message); + * }); + */ + flipX() { + return this._flip('flipX'); + } + + /** + * Flip y + * @returns {Promise} + * @example + * imageEditor.flipY().then(status => { + * console.log('flipX: ', status.flipX); + * console.log('flipY: ', status.flipY); + * console.log('angle: ', status.angle); + * }).catch(message => { + * console.log('error: ', message); + * }); + */ + flipY() { + return this._flip('flipY'); + } + + /** + * Reset flip + * @returns {Promise} + * @example + * imageEditor.resetFlip().then(status => { + * console.log('flipX: ', status.flipX); + * console.log('flipY: ', status.flipY); + * console.log('angle: ', status.angle); + * }).catch(message => { + * console.log('error: ', message); + * });; + */ + resetFlip() { + return this._flip('reset'); + } + + /** + * @param {string} type - 'rotate' or 'setAngle' + * @param {number} angle - angle value (degree) + * @param {boolean} isSilent - is silent execution or not + * @returns {Promise} + * @private + */ + _rotate(type, angle, isSilent) { + let result = null; + if (isSilent) { + result = this.executeSilent(commands.ROTATE_IMAGE, type, angle); + } else { + result = this.execute(commands.ROTATE_IMAGE, type, angle); + } + + return result; + } + + /** + * Rotate image + * @returns {Promise} + * @param {number} angle - Additional angle to rotate image + * @param {boolean} isSilent - is silent execution or not + * @returns {Promise} + * @example + * imageEditor.rotate(10); // angle = 10 + * imageEditor.rotate(10); // angle = 20 + * imageEidtor.rotate(5); // angle = 5 + * imageEidtor.rotate(-95); // angle = -90 + * imageEditor.rotate(10).then(status => { + * console.log('angle: ', status.angle); + * })).catch(message => { + * console.log('error: ', message); + * }); + */ + rotate(angle, isSilent) { + return this._rotate('rotate', angle, isSilent); + } + + /** + * Set angle + * @param {number} angle - Angle of image + * @param {boolean} isSilent - is silent execution or not + * @returns {Promise} + * @example + * imageEditor.setAngle(10); // angle = 10 + * imageEditor.rotate(10); // angle = 20 + * imageEidtor.setAngle(5); // angle = 5 + * imageEidtor.rotate(50); // angle = 55 + * imageEidtor.setAngle(-40); // angle = -40 + * imageEditor.setAngle(10).then(status => { + * console.log('angle: ', status.angle); + * })).catch(message => { + * console.log('error: ', message); + * }); + */ + setAngle(angle, isSilent) { + return this._rotate('setAngle', angle, isSilent); + } + + /** + * Set drawing brush + * @param {Object} option brush option + * @param {Number} option.width width + * @param {String} option.color color like 'FFFFFF', 'rgba(0, 0, 0, 0.5)' + * @example + * imageEditor.startDrawingMode('FREE_DRAWING'); + * imageEditor.setBrush({ + * width: 12, + * color: 'rgba(0, 0, 0, 0.5)' + * }); + * imageEditor.setBrush({ + * width: 8, + * color: 'FFFFFF' + * }); + */ + setBrush(option) { + this._graphics.setBrush(option); + } + + /** + * Set states of current drawing shape + * @param {string} type - Shape type (ex: 'rect', 'circle', 'triangle') + * @param {Object} [options] - Shape options + * @param {(ShapeFillOption | string)} [options.fill] - {@link ShapeFillOption} or + * Shape foreground color (ex: '#fff', 'transparent') + * @param {string} [options.stoke] - Shape outline color + * @param {number} [options.strokeWidth] - Shape outline width + * @param {number} [options.width] - Width value (When type option is 'rect', this options can use) + * @param {number} [options.height] - Height value (When type option is 'rect', this options can use) + * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use) + * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use) + * @param {number} [options.isRegular] - Whether resizing shape has 1:1 ratio or not + * @example + * imageEditor.setDrawingShape('rect', { + * fill: 'red', + * width: 100, + * height: 200 + * }); + * @example + * imageEditor.setDrawingShape('rect', { + * fill: { + * type: 'filter', + * filter: [{blur: 0.3}, {pixelate: 20}] + * }, + * width: 100, + * height: 200 + * }); + * @example + * imageEditor.setDrawingShape('circle', { + * fill: 'transparent', + * stroke: 'blue', + * strokeWidth: 3, + * rx: 10, + * ry: 100 + * }); + * @example + * imageEditor.setDrawingShape('triangle', { // When resizing, the shape keep the 1:1 ratio + * width: 1, + * height: 1, + * isRegular: true + * }); + * @example + * imageEditor.setDrawingShape('circle', { // When resizing, the shape keep the 1:1 ratio + * rx: 10, + * ry: 10, + * isRegular: true + * }); + */ + setDrawingShape(type, options) { + this._graphics.setDrawingShape(type, options); + } + + setDrawingIcon(type, iconColor) { + this._graphics.setIconStyle(type, iconColor); + } + + /** + * Add shape + * @param {string} type - Shape type (ex: 'rect', 'circle', 'triangle') + * @param {Object} options - Shape options + * @param {(ShapeFillOption | string)} [options.fill] - {@link ShapeFillOption} or + * Shape foreground color (ex: '#fff', 'transparent') + * @param {string} [options.stroke] - Shape outline color + * @param {number} [options.strokeWidth] - Shape outline width + * @param {number} [options.width] - Width value (When type option is 'rect', this options can use) + * @param {number} [options.height] - Height value (When type option is 'rect', this options can use) + * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use) + * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use) + * @param {number} [options.left] - Shape x position + * @param {number} [options.top] - Shape y position + * @param {boolean} [options.isRegular] - Whether resizing shape has 1:1 ratio or not + * @returns {Promise} + * @example + * imageEditor.addShape('rect', { + * fill: 'red', + * stroke: 'blue', + * strokeWidth: 3, + * width: 100, + * height: 200, + * left: 10, + * top: 10, + * isRegular: true + * }); + * @example + * imageEditor.addShape('circle', { + * fill: 'red', + * stroke: 'blue', + * strokeWidth: 3, + * rx: 10, + * ry: 100, + * isRegular: false + * }).then(objectProps => { + * console.log(objectProps.id); + * }); + * @example + * imageEditor.addShape('rect', { + * fill: { + * type: 'filter', + * filter: [{blur: 0.3}, {pixelate: 20}] + * }, + * stroke: 'blue', + * strokeWidth: 3, + * rx: 10, + * ry: 100, + * isRegular: false + * }).then(objectProps => { + * console.log(objectProps.id); + * }); + */ + addShape(type, options) { + options = options || {}; + + this._setPositions(options); + + return this.execute(commands.ADD_SHAPE, type, options); + } + + /** + * Change shape + * @param {number} id - object id + * @param {Object} options - Shape options + * @param {(ShapeFillOption | string)} [options.fill] - {@link ShapeFillOption} or + * Shape foreground color (ex: '#fff', 'transparent') + * @param {string} [options.stroke] - Shape outline color + * @param {number} [options.strokeWidth] - Shape outline width + * @param {number} [options.width] - Width value (When type option is 'rect', this options can use) + * @param {number} [options.height] - Height value (When type option is 'rect', this options can use) + * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use) + * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use) + * @param {boolean} [options.isRegular] - Whether resizing shape has 1:1 ratio or not + * @param {boolean} isSilent - is silent execution or not + * @returns {Promise} + * @example + * // call after selecting shape object on canvas + * imageEditor.changeShape(id, { // change rectagle or triangle + * fill: 'red', + * stroke: 'blue', + * strokeWidth: 3, + * width: 100, + * height: 200 + * }); + * @example + * // call after selecting shape object on canvas + * imageEditor.changeShape(id, { // change circle + * fill: 'red', + * stroke: 'blue', + * strokeWidth: 3, + * rx: 10, + * ry: 100 + * }); + */ + changeShape(id, options, isSilent) { + const executeMethodName = isSilent ? 'executeSilent' : 'execute'; + + return this[executeMethodName](commands.CHANGE_SHAPE, id, options); + } + + /** + * Add text on image + * @param {string} text - Initial input text + * @param {Object} [options] Options for generating text + * @param {Object} [options.styles] Initial styles + * @param {string} [options.styles.fill] Color + * @param {string} [options.styles.fontFamily] Font type for text + * @param {number} [options.styles.fontSize] Size + * @param {string} [options.styles.fontStyle] Type of inclination (normal / italic) + * @param {string} [options.styles.fontWeight] Type of thicker or thinner looking (normal / bold) + * @param {string} [options.styles.textAlign] Type of text align (left / center / right) + * @param {string} [options.styles.textDecoration] Type of line (underline / line-through / overline) + * @param {{x: number, y: number}} [options.position] - Initial position + * @param {boolean} [options.autofocus] - text autofocus, default is true + * @returns {Promise} + * @example + * imageEditor.addText('init text'); + * @example + * imageEditor.addText('init text', { + * styles: { + * fill: '#000', + * fontSize: 20, + * fontWeight: 'bold' + * }, + * position: { + * x: 10, + * y: 10 + * } + * }).then(objectProps => { + * console.log(objectProps.id); + * }); + */ + addText(text, options) { + text = text || ''; + options = options || {}; + + return this.execute(commands.ADD_TEXT, text, options); + } + + /** + * Change contents of selected text object on image + * @param {number} id - object id + * @param {string} text - Changing text + * @returns {Promise} + * @example + * imageEditor.changeText(id, 'change text'); + */ + changeText(id, text) { + text = text || ''; + + return this.execute(commands.CHANGE_TEXT, id, text); + } + + /** + * Set style + * @param {number} id - object id + * @param {Object} styleObj - text styles + * @param {string} [styleObj.fill] Color + * @param {string} [styleObj.fontFamily] Font type for text + * @param {number} [styleObj.fontSize] Size + * @param {string} [styleObj.fontStyle] Type of inclination (normal / italic) + * @param {string} [styleObj.fontWeight] Type of thicker or thinner looking (normal / bold) + * @param {string} [styleObj.textAlign] Type of text align (left / center / right) + * @param {string} [styleObj.textDecoration] Type of line (underline / line-through / overline) + * @param {boolean} isSilent - is silent execution or not + * @returns {Promise} + * @example + * imageEditor.changeTextStyle(id, { + * fontStyle: 'italic' + * }); + */ + changeTextStyle(id, styleObj, isSilent) { + const executeMethodName = isSilent ? 'executeSilent' : 'execute'; + + return this[executeMethodName](commands.CHANGE_TEXT_STYLE, id, styleObj); + } + + /** + * change text mode + * @param {string} type - change type + * @private + */ + _changeActivateMode(type) { + if (type !== 'ICON' && this.getDrawingMode() !== type) { + this.startDrawingMode(type); + } + } + + /** + * 'textChanged' event handler + * @param {Object} objectProps changed object properties + * @private + */ + _onTextChanged(objectProps) { + this.changeText(objectProps.id, objectProps.text); + } + + /** + * 'iconCreateResize' event handler + * @param {Object} originPointer origin pointer + * @param {Number} originPointer.x x position + * @param {Number} originPointer.y y position + * @private + */ + _onIconCreateResize(originPointer) { + this.fire(events.ICON_CREATE_RESIZE, originPointer); + } + + /** + * 'iconCreateEnd' event handler + * @param {Object} originPointer origin pointer + * @param {Number} originPointer.x x position + * @param {Number} originPointer.y y position + * @private + */ + _onIconCreateEnd(originPointer) { + this.fire(events.ICON_CREATE_END, originPointer); + } + + /** + * 'textEditing' event handler + * @private + */ + _onTextEditing() { + /** + * The event which starts to edit text object + * @event ImageEditor#textEditing * @example - * imageEditor.setObjectPropertiesQuietly(id, { - * left:100, - * top:100, - * width: 200, - * height: 200, - * opacity: 0.5 + * imageEditor.on('textEditing', function() { + * console.log('text editing'); * }); */ - setObjectPropertiesQuietly(id, keyValue) { - this._graphics.setObjectProperties(id, keyValue); - } - - /** - * Get properties of active object corresponding key - * @param {number} id - object id - * @param {Array|ObjectProps|string} keys - property's key - * @returns {ObjectProps} properties if id is valid or null - * @example - * var props = imageEditor.getObjectProperties(id, 'left'); - * console.log(props); + this.fire(events.TEXT_EDITING); + } + + /** + * Mousedown event handler in case of 'TEXT' drawing mode + * @param {fabric.Event} event - Current mousedown event object + * @private + */ + _onAddText(event) { + /** + * The event when 'TEXT' drawing mode is enabled and click non-object area. + * @event ImageEditor#addText + * @param {Object} pos + * @param {Object} pos.originPosition - Current position on origin canvas + * @param {Number} pos.originPosition.x - x + * @param {Number} pos.originPosition.y - y + * @param {Object} pos.clientPosition - Current position on client area + * @param {Number} pos.clientPosition.x - x + * @param {Number} pos.clientPosition.y - y * @example - * var props = imageEditor.getObjectProperties(id, ['left', 'top', 'width', 'height']); - * console.log(props); - * @example - * var props = imageEditor.getObjectProperties(id, { - * left: null, - * top: null, - * width: null, - * height: null, - * opacity: null + * imageEditor.on('addText', function(pos) { + * console.log('text position on canvas: ' + pos.originPosition); + * console.log('text position on brwoser: ' + pos.clientPosition); * }); - * console.log(props); - */ - getObjectProperties(id, keys) { - const object = this._graphics.getObject(id); - if (!object) { - return null; - } - - return this._graphics.getObjectProperties(id, keys); - } - - /** - * Get the canvas size - * @returns {Object} {{width: number, height: number}} canvas size - * @example - * var canvasSize = imageEditor.getCanvasSize(); - * console.log(canvasSize.width); - * console.height(canvasSize.height); */ - getCanvasSize() { - return this._graphics.getCanvasSize(); - } - - /** - * Get object position by originX, originY - * @param {number} id - object id - * @param {string} originX - can be 'left', 'center', 'right' - * @param {string} originY - can be 'top', 'center', 'bottom' - * @returns {Object} {{x:number, y: number}} position by origin if id is valid, or null + this.fire(events.ADD_TEXT, { + originPosition: event.originPosition, + clientPosition: event.clientPosition, + }); + } + + /** + * 'addObject' event handler + * @param {Object} objectProps added object properties + * @private + */ + _onAddObject(objectProps) { + const obj = this._graphics.getObject(objectProps.id); + this._pushAddObjectCommand(obj); + } + + /** + * 'objectAdded' event handler + * @param {Object} objectProps added object properties + * @private + */ + _onObjectAdded(objectProps) { + /** + * The event when object added + * @event ImageEditor#objectAdded + * @param {ObjectProps} props - object properties * @example - * var position = imageEditor.getObjectPosition(id, 'left', 'top'); - * console.log(position); + * imageEditor.on('objectAdded', function(props) { + * console.log(props); + * }); */ - getObjectPosition(id, originX, originY) { - return this._graphics.getObjectPosition(id, originX, originY); - } + this.fire(OBJECT_ADDED, objectProps); /** - * Set object position by originX, originY - * @param {number} id - object id - * @param {Object} posInfo - position object - * @param {number} posInfo.x - x position - * @param {number} posInfo.y - y position - * @param {string} posInfo.originX - can be 'left', 'center', 'right' - * @param {string} posInfo.originY - can be 'top', 'center', 'bottom' - * @returns {Promise} - * @example - * // align the object to 'left', 'top' - * imageEditor.setObjectPosition(id, { - * x: 0, - * y: 0, - * originX: 'left', - * originY: 'top' - * }); - * @example - * // align the object to 'right', 'top' - * var canvasSize = imageEditor.getCanvasSize(); - * imageEditor.setObjectPosition(id, { - * x: canvasSize.width, - * y: 0, - * originX: 'right', - * originY: 'top' - * }); - * @example - * // align the object to 'left', 'bottom' - * var canvasSize = imageEditor.getCanvasSize(); - * imageEditor.setObjectPosition(id, { - * x: 0, - * y: canvasSize.height, - * originX: 'left', - * originY: 'bottom' - * }); - * @example - * // align the object to 'right', 'bottom' - * var canvasSize = imageEditor.getCanvasSize(); - * imageEditor.setObjectPosition(id, { - * x: canvasSize.width, - * y: canvasSize.height, - * originX: 'right', - * originY: 'bottom' - * }); - */ - setObjectPosition(id, posInfo) { - return this.execute(commands.SET_OBJECT_POSITION, id, posInfo); - } + * The event when object added (deprecated) + * @event ImageEditor#addObjectAfter + * @param {ObjectProps} props - object properties + * @deprecated + */ + this.fire(ADD_OBJECT_AFTER, objectProps); + } + + /** + * 'objectModified' event handler + * @param {fabric.Object} obj - selection object + * @private + */ + _onObjectModified(obj) { + this._pushModifyObjectCommand(obj); + } + + /** + * 'selectionCleared' event handler + * @private + */ + _selectionCleared() { + this.fire(SELECTION_CLEARED); + } + + /** + * 'selectionCreated' event handler + * @param {Object} eventTarget - Fabric object + * @private + */ + _selectionCreated(eventTarget) { + this.fire(SELECTION_CREATED, eventTarget); + } + + /** + * Register custom icons + * @param {{iconType: string, pathValue: string}} infos - Infos to register icons + * @example + * imageEditor.registerIcons({ + * customIcon: 'M 0 0 L 20 20 L 10 10 Z', + * customArrow: 'M 60 0 L 120 60 H 90 L 75 45 V 180 H 45 V 45 L 30 60 H 0 Z' + * }); + */ + registerIcons(infos) { + this._graphics.registerPaths(infos); + } + + /** + * Change canvas cursor type + * @param {string} cursorType - cursor type + * @example + * imageEditor.changeCursor('crosshair'); + */ + changeCursor(cursorType) { + this._graphics.changeCursor(cursorType); + } + + /** + * Add icon on canvas + * @param {string} type - Icon type ('arrow', 'cancel', custom icon name) + * @param {Object} options - Icon options + * @param {string} [options.fill] - Icon foreground color + * @param {number} [options.left] - Icon x position + * @param {number} [options.top] - Icon y position + * @returns {Promise} + * @example + * imageEditor.addIcon('arrow'); // The position is center on canvas + * @example + * imageEditor.addIcon('arrow', { + * left: 100, + * top: 100 + * }).then(objectProps => { + * console.log(objectProps.id); + * }); + */ + addIcon(type, options) { + options = options || {}; + + this._setPositions(options); + + return this.execute(commands.ADD_ICON, type, options); + } + + /** + * Change icon color + * @param {number} id - object id + * @param {string} color - Color for icon + * @returns {Promise} + * @example + * imageEditor.changeIconColor(id, '#000000'); + */ + changeIconColor(id, color) { + return this.execute(commands.CHANGE_ICON_COLOR, id, color); + } + + /** + * Remove an object or group by id + * @param {number} id - object id + * @returns {Promise} + * @example + * imageEditor.removeObject(id); + */ + removeObject(id) { + return this.execute(commands.REMOVE_OBJECT, id); + } + + /** + * Whether it has the filter or not + * @param {string} type - Filter type + * @returns {boolean} true if it has the filter + */ + hasFilter(type) { + return this._graphics.hasFilter(type); + } + + /** + * Remove filter on canvas image + * @param {string} type - Filter type + * @returns {Promise} + * @example + * imageEditor.removeFilter('Grayscale').then(obj => { + * console.log('filterType: ', obj.type); + * console.log('actType: ', obj.action); + * }).catch(message => { + * console.log('error: ', message); + * }); + */ + removeFilter(type) { + return this.execute(commands.REMOVE_FILTER, type); + } + + /** + * Apply filter on canvas image + * @param {string} type - Filter type + * @param {Object} options - Options to apply filter + * @param {number} options.maskObjId - masking image object id + * @param {boolean} isSilent - is silent execution or not + * @returns {Promise} + * @example + * imageEditor.applyFilter('Grayscale'); + * @example + * imageEditor.applyFilter('mask', {maskObjId: id}).then(obj => { + * console.log('filterType: ', obj.type); + * console.log('actType: ', obj.action); + * }).catch(message => { + * console.log('error: ', message); + * });; + */ + applyFilter(type, options, isSilent) { + const executeMethodName = isSilent ? 'executeSilent' : 'execute'; + + return this[executeMethodName](commands.APPLY_FILTER, type, options); + } + + /** + * Get data url + * @param {Object} options - options for toDataURL + * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png" + * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg. + * @param {Number} [options.multiplier=1] Multiplier to scale by + * @param {Number} [options.left] Cropping left offset. Introduced in fabric v1.2.14 + * @param {Number} [options.top] Cropping top offset. Introduced in fabric v1.2.14 + * @param {Number} [options.width] Cropping width. Introduced in fabric v1.2.14 + * @param {Number} [options.height] Cropping height. Introduced in fabric v1.2.14 + * @returns {string} A DOMString containing the requested data URI + * @example + * imgEl.src = imageEditor.toDataURL(); + * + * imageEditor.loadImageFromURL(imageEditor.toDataURL(), 'FilterImage').then(() => { + * imageEditor.addImageObject(imgUrl); + * }); + */ + toDataURL(options) { + return this._graphics.toDataURL(options); + } + + /** + * Get image name + * @returns {string} image name + * @example + * console.log(imageEditor.getImageName()); + */ + getImageName() { + return this._graphics.getImageName(); + } + + /** + * Clear undoStack + * @example + * imageEditor.clearUndoStack(); + */ + clearUndoStack() { + this._invoker.clearUndoStack(); + } + + /** + * Clear redoStack + * @example + * imageEditor.clearRedoStack(); + */ + clearRedoStack() { + this._invoker.clearRedoStack(); + } + + /** + * Whehter the undo stack is empty or not + * @returns {boolean} + * imageEditor.isEmptyUndoStack(); + */ + isEmptyUndoStack() { + return this._invoker.isEmptyUndoStack(); + } + + /** + * Whehter the redo stack is empty or not + * @returns {boolean} + * imageEditor.isEmptyRedoStack(); + */ + isEmptyRedoStack() { + return this._invoker.isEmptyRedoStack(); + } + + /** + * Resize canvas dimension + * @param {{width: number, height: number}} dimension - Max width & height + * @returns {Promise} + */ + resizeCanvasDimension(dimension) { + if (!dimension) { + return Promise.reject(rejectMessages.invalidParameters); + } + + return this.execute(commands.RESIZE_CANVAS_DIMENSION, dimension); + } + + /** + * Destroy + */ + destroy() { + this.stopDrawingMode(); + this._detachDomEvents(); + this._graphics.destroy(); + this._graphics = null; + + if (this.ui) { + this.ui.destroy(); + } + + forEach( + this, + (value, key) => { + this[key] = null; + }, + this + ); + } + + /** + * Set position + * @param {Object} options - Position options (left or top) + * @private + */ + _setPositions(options) { + const centerPosition = this._graphics.getCenter(); + + if (isUndefined(options.left)) { + options.left = centerPosition.left; + } + + if (isUndefined(options.top)) { + options.top = centerPosition.top; + } + } + + /** + * Set properties of active object + * @param {number} id - object id + * @param {Object} keyValue - key & value + * @returns {Promise} + * @example + * imageEditor.setObjectProperties(id, { + * left:100, + * top:100, + * width: 200, + * height: 200, + * opacity: 0.5 + * }); + */ + setObjectProperties(id, keyValue) { + return this.execute(commands.SET_OBJECT_PROPERTIES, id, keyValue); + } + + /** + * Set properties of active object, Do not leave an invoke history. + * @param {number} id - object id + * @param {Object} keyValue - key & value + * @example + * imageEditor.setObjectPropertiesQuietly(id, { + * left:100, + * top:100, + * width: 200, + * height: 200, + * opacity: 0.5 + * }); + */ + setObjectPropertiesQuietly(id, keyValue) { + this._graphics.setObjectProperties(id, keyValue); + } + + /** + * Get properties of active object corresponding key + * @param {number} id - object id + * @param {Array|ObjectProps|string} keys - property's key + * @returns {ObjectProps} properties if id is valid or null + * @example + * var props = imageEditor.getObjectProperties(id, 'left'); + * console.log(props); + * @example + * var props = imageEditor.getObjectProperties(id, ['left', 'top', 'width', 'height']); + * console.log(props); + * @example + * var props = imageEditor.getObjectProperties(id, { + * left: null, + * top: null, + * width: null, + * height: null, + * opacity: null + * }); + * console.log(props); + */ + getObjectProperties(id, keys) { + const object = this._graphics.getObject(id); + if (!object) { + return null; + } + + return this._graphics.getObjectProperties(id, keys); + } + + /** + * Get the canvas size + * @returns {Object} {{width: number, height: number}} canvas size + * @example + * var canvasSize = imageEditor.getCanvasSize(); + * console.log(canvasSize.width); + * console.height(canvasSize.height); + */ + getCanvasSize() { + return this._graphics.getCanvasSize(); + } + + /** + * Get object position by originX, originY + * @param {number} id - object id + * @param {string} originX - can be 'left', 'center', 'right' + * @param {string} originY - can be 'top', 'center', 'bottom' + * @returns {Object} {{x:number, y: number}} position by origin if id is valid, or null + * @example + * var position = imageEditor.getObjectPosition(id, 'left', 'top'); + * console.log(position); + */ + getObjectPosition(id, originX, originY) { + return this._graphics.getObjectPosition(id, originX, originY); + } + + /** + * Set object position by originX, originY + * @param {number} id - object id + * @param {Object} posInfo - position object + * @param {number} posInfo.x - x position + * @param {number} posInfo.y - y position + * @param {string} posInfo.originX - can be 'left', 'center', 'right' + * @param {string} posInfo.originY - can be 'top', 'center', 'bottom' + * @returns {Promise} + * @example + * // align the object to 'left', 'top' + * imageEditor.setObjectPosition(id, { + * x: 0, + * y: 0, + * originX: 'left', + * originY: 'top' + * }); + * @example + * // align the object to 'right', 'top' + * var canvasSize = imageEditor.getCanvasSize(); + * imageEditor.setObjectPosition(id, { + * x: canvasSize.width, + * y: 0, + * originX: 'right', + * originY: 'top' + * }); + * @example + * // align the object to 'left', 'bottom' + * var canvasSize = imageEditor.getCanvasSize(); + * imageEditor.setObjectPosition(id, { + * x: 0, + * y: canvasSize.height, + * originX: 'left', + * originY: 'bottom' + * }); + * @example + * // align the object to 'right', 'bottom' + * var canvasSize = imageEditor.getCanvasSize(); + * imageEditor.setObjectPosition(id, { + * x: canvasSize.width, + * y: canvasSize.height, + * originX: 'right', + * originY: 'bottom' + * }); + */ + setObjectPosition(id, posInfo) { + return this.execute(commands.SET_OBJECT_POSITION, id, posInfo); + } } action.mixin(ImageEditor); diff --git a/src/js/interface/command.js b/src/js/interface/command.js index 89c2d8e8b..bd8d51e45 100644 --- a/src/js/interface/command.js +++ b/src/js/interface/command.js @@ -17,119 +17,119 @@ const errorTypes = errorMessage.types; * @ignore */ class Command { - constructor(actions, args) { - /** - * command name - * @type {string} - */ - this.name = actions.name; - - /** - * arguments - * @type {Array} - */ - this.args = args; - - /** - * Execute function - * @type {function} - */ - this.execute = actions.execute; - - /** - * Undo function - * @type {function} - */ - this.undo = actions.undo; - - /** - * executeCallback - * @type {function} - */ - this.executeCallback = actions.executeCallback || null; - - /** - * undoCallback - * @type {function} - */ - this.undoCallback = actions.undoCallback || null; - - /** - * data for undo - * @type {Object} - */ - this.undoData = {}; - } - + constructor(actions, args) { /** - * Execute action - * @param {Object.} compMap - Components injection - * @abstract + * command name + * @type {string} */ - execute() { - throw new Error(createMessage(errorTypes.UN_IMPLEMENTATION, 'execute')); - } + this.name = actions.name; /** - * Undo action - * @param {Object.} compMap - Components injection - * @abstract + * arguments + * @type {Array} */ - undo() { - throw new Error(createMessage(errorTypes.UN_IMPLEMENTATION, 'undo')); - } + this.args = args; /** - * command for redo if undoData exists - * @returns {boolean} isRedo + * Execute function + * @type {function} */ - get isRedo() { - return Object.keys(this.undoData).length; - } + this.execute = actions.execute; /** - * Set undoData action - * @param {Object} undoData - maked undo data - * @param {Object} cachedUndoDataForSilent - cached undo data - * @param {boolean} isSilent - is silent execution or not - * @returns {Object} cachedUndoDataForSilent + * Undo function + * @type {function} */ - setUndoData(undoData, cachedUndoDataForSilent, isSilent) { - if (cachedUndoDataForSilent) { - undoData = cachedUndoDataForSilent; - } - - if (!isSilent) { - snippet.extend(this.undoData, undoData); - cachedUndoDataForSilent = null; - } else if (!cachedUndoDataForSilent) { - cachedUndoDataForSilent = undoData; - } - - return cachedUndoDataForSilent; - } + this.undo = actions.undo; /** - * Attach execute callabck - * @param {function} callback - Callback after execution - * @returns {Command} this + * executeCallback + * @type {function} */ - setExecuteCallback(callback) { - this.executeCallback = callback; + this.executeCallback = actions.executeCallback || null; - return this; - } + /** + * undoCallback + * @type {function} + */ + this.undoCallback = actions.undoCallback || null; /** - * Attach undo callback - * @param {function} callback - Callback after undo - * @returns {Command} this + * data for undo + * @type {Object} */ - setUndoCallback(callback) { - this.undoCallback = callback; + this.undoData = {}; + } + + /** + * Execute action + * @param {Object.} compMap - Components injection + * @abstract + */ + execute() { + throw new Error(createMessage(errorTypes.UN_IMPLEMENTATION, 'execute')); + } + + /** + * Undo action + * @param {Object.} compMap - Components injection + * @abstract + */ + undo() { + throw new Error(createMessage(errorTypes.UN_IMPLEMENTATION, 'undo')); + } + + /** + * command for redo if undoData exists + * @returns {boolean} isRedo + */ + get isRedo() { + return Object.keys(this.undoData).length; + } + + /** + * Set undoData action + * @param {Object} undoData - maked undo data + * @param {Object} cachedUndoDataForSilent - cached undo data + * @param {boolean} isSilent - is silent execution or not + * @returns {Object} cachedUndoDataForSilent + */ + setUndoData(undoData, cachedUndoDataForSilent, isSilent) { + if (cachedUndoDataForSilent) { + undoData = cachedUndoDataForSilent; + } - return this; + if (!isSilent) { + snippet.extend(this.undoData, undoData); + cachedUndoDataForSilent = null; + } else if (!cachedUndoDataForSilent) { + cachedUndoDataForSilent = undoData; } + + return cachedUndoDataForSilent; + } + + /** + * Attach execute callabck + * @param {function} callback - Callback after execution + * @returns {Command} this + */ + setExecuteCallback(callback) { + this.executeCallback = callback; + + return this; + } + + /** + * Attach undo callback + * @param {function} callback - Callback after undo + * @returns {Command} this + */ + setUndoCallback(callback) { + this.undoCallback = callback; + + return this; + } } export default Command; diff --git a/src/js/interface/component.js b/src/js/interface/component.js index b902dcdb7..fd0ff29df 100644 --- a/src/js/interface/component.js +++ b/src/js/interface/component.js @@ -11,118 +11,118 @@ * @ignore */ class Component { - constructor(name, graphics) { - /** - * Component name - * @type {string} - */ - this.name = name; - - /** - * Graphics instance - * @type {Graphics} - */ - this.graphics = graphics; - } - - /** - * Fire Graphics event - * @returns {Object} return value - */ - fire(...args) { - const context = this.graphics; - - return this.graphics.fire.apply(context, args); - } - - /** - * Save image(background) of canvas - * @param {string} name - Name of image - * @param {fabric.Image} oImage - Fabric image instance - */ - setCanvasImage(name, oImage) { - this.graphics.setCanvasImage(name, oImage); - } - - /** - * Returns canvas element of fabric.Canvas[[lower-canvas]] - * @returns {HTMLCanvasElement} - */ - getCanvasElement() { - return this.graphics.getCanvasElement(); - } - - /** - * Get fabric.Canvas instance - * @returns {fabric.Canvas} - */ - getCanvas() { - return this.graphics.getCanvas(); - } - - /** - * Get canvasImage (fabric.Image instance) - * @returns {fabric.Image} - */ - getCanvasImage() { - return this.graphics.getCanvasImage(); - } - - /** - * Get image name - * @returns {string} - */ - getImageName() { - return this.graphics.getImageName(); - } - - /** - * Get image editor - * @returns {ImageEditor} - */ - getEditor() { - return this.graphics.getEditor(); - } - - /** - * Return component name - * @returns {string} - */ - getName() { - return this.name; - } - - /** - * Set image properties - * @param {Object} setting - Image properties - * @param {boolean} [withRendering] - If true, The changed image will be reflected in the canvas - */ - setImageProperties(setting, withRendering) { - this.graphics.setImageProperties(setting, withRendering); - } - - /** - * Set canvas dimension - css only - * @param {Object} dimension - Canvas css dimension - */ - setCanvasCssDimension(dimension) { - this.graphics.setCanvasCssDimension(dimension); - } - + constructor(name, graphics) { /** - * Set canvas dimension - css only - * @param {Object} dimension - Canvas backstore dimension + * Component name + * @type {string} */ - setCanvasBackstoreDimension(dimension) { - this.graphics.setCanvasBackstoreDimension(dimension); - } + this.name = name; /** - * Adjust canvas dimension with scaling image + * Graphics instance + * @type {Graphics} */ - adjustCanvasDimension() { - this.graphics.adjustCanvasDimension(); - } + this.graphics = graphics; + } + + /** + * Fire Graphics event + * @returns {Object} return value + */ + fire(...args) { + const context = this.graphics; + + return this.graphics.fire.apply(context, args); + } + + /** + * Save image(background) of canvas + * @param {string} name - Name of image + * @param {fabric.Image} oImage - Fabric image instance + */ + setCanvasImage(name, oImage) { + this.graphics.setCanvasImage(name, oImage); + } + + /** + * Returns canvas element of fabric.Canvas[[lower-canvas]] + * @returns {HTMLCanvasElement} + */ + getCanvasElement() { + return this.graphics.getCanvasElement(); + } + + /** + * Get fabric.Canvas instance + * @returns {fabric.Canvas} + */ + getCanvas() { + return this.graphics.getCanvas(); + } + + /** + * Get canvasImage (fabric.Image instance) + * @returns {fabric.Image} + */ + getCanvasImage() { + return this.graphics.getCanvasImage(); + } + + /** + * Get image name + * @returns {string} + */ + getImageName() { + return this.graphics.getImageName(); + } + + /** + * Get image editor + * @returns {ImageEditor} + */ + getEditor() { + return this.graphics.getEditor(); + } + + /** + * Return component name + * @returns {string} + */ + getName() { + return this.name; + } + + /** + * Set image properties + * @param {Object} setting - Image properties + * @param {boolean} [withRendering] - If true, The changed image will be reflected in the canvas + */ + setImageProperties(setting, withRendering) { + this.graphics.setImageProperties(setting, withRendering); + } + + /** + * Set canvas dimension - css only + * @param {Object} dimension - Canvas css dimension + */ + setCanvasCssDimension(dimension) { + this.graphics.setCanvasCssDimension(dimension); + } + + /** + * Set canvas dimension - css only + * @param {Object} dimension - Canvas backstore dimension + */ + setCanvasBackstoreDimension(dimension) { + this.graphics.setCanvasBackstoreDimension(dimension); + } + + /** + * Adjust canvas dimension with scaling image + */ + adjustCanvasDimension() { + this.graphics.adjustCanvasDimension(); + } } export default Component; diff --git a/src/js/interface/drawingMode.js b/src/js/interface/drawingMode.js index 33e489798..bba195a9f 100644 --- a/src/js/interface/drawingMode.js +++ b/src/js/interface/drawingMode.js @@ -14,38 +14,38 @@ const errorTypes = errorMessage.types; * @ignore */ class DrawingMode { - constructor(name) { - /** - * the name of drawing mode - * @type {string} - */ - this.name = name; - } - + constructor(name) { /** - * Get this drawing mode name; - * @returns {string} drawing mode name + * the name of drawing mode + * @type {string} */ - getName() { - return this.name; - } + this.name = name; + } - /** - * start this drawing mode - * @param {Object} options - drawing mode options - * @abstract - */ - start() { - throw new Error(createMessage(errorTypes.UN_IMPLEMENTATION, 'start')); - } + /** + * Get this drawing mode name; + * @returns {string} drawing mode name + */ + getName() { + return this.name; + } - /** - * stop this drawing mode - * @abstract - */ - stop() { - throw new Error(createMessage(errorTypes.UN_IMPLEMENTATION, 'stop')); - } + /** + * start this drawing mode + * @param {Object} options - drawing mode options + * @abstract + */ + start() { + throw new Error(createMessage(errorTypes.UN_IMPLEMENTATION, 'start')); + } + + /** + * stop this drawing mode + * @abstract + */ + stop() { + throw new Error(createMessage(errorTypes.UN_IMPLEMENTATION, 'stop')); + } } export default DrawingMode; diff --git a/src/js/invoker.js b/src/js/invoker.js index 72994acf6..dc3b34c50 100644 --- a/src/js/invoker.js +++ b/src/js/invoker.js @@ -3,11 +3,11 @@ * @fileoverview Invoker - invoke commands */ import snippet from 'tui-code-snippet'; -import {Promise} from './util'; +import { Promise } from './util'; import commandFactory from './factory/command'; -import {eventNames, rejectMessages} from './consts'; +import { eventNames, rejectMessages } from './consts'; -const {isFunction, isString, CustomEvents} = snippet; +const { isFunction, isString, CustomEvents } = snippet; /** * Invoker @@ -15,274 +15,277 @@ const {isFunction, isString, CustomEvents} = snippet; * @ignore */ class Invoker { - constructor() { - /** - * Undo stack - * @type {Array.} - * @private - */ - this._undoStack = []; - - /** - * Redo stack - * @type {Array.} - * @private - */ - this._redoStack = []; - - /** - * Lock-flag for executing command - * @type {boolean} - * @private - */ - this._isLocked = false; - - this._isSilent = false; - } - + constructor() { /** - * Invoke command execution - * @param {Command} command - Command - * @returns {Promise} + * Undo stack + * @type {Array.} * @private */ - _invokeExecution(command) { - this.lock(); - - let {args} = command; - if (!args) { - args = []; - } - - return command.execute(...args) - .then(value => { - if (!this._isSilent) { - this.pushUndoStack(command); - } - this.unlock(); - if (isFunction(command.executeCallback)) { - command.executeCallback(value); - } - - return value; - })['catch'](message => { - this.unlock(); - - return Promise.reject(message); - }); - } + this._undoStack = []; /** - * Invoke command undo - * @param {Command} command - Command - * @returns {Promise} + * Redo stack + * @type {Array.} * @private */ - _invokeUndo(command) { - this.lock(); - - let {args} = command; - if (!args) { - args = []; - } - - return command.undo(...args) - .then(value => { - this.pushRedoStack(command); - this.unlock(); - if (isFunction(command.undoCallback)) { - command.undoCallback(value); - } - - return value; - })['catch'](message => { - this.unlock(); - - return Promise.reject(message); - }); - } + this._redoStack = []; /** - * fire REDO_STACK_CHANGED event + * Lock-flag for executing command + * @type {boolean} * @private */ - _fireRedoStackChanged() { - this.fire(eventNames.REDO_STACK_CHANGED, this._redoStack.length); + this._isLocked = false; + + this._isSilent = false; + } + + /** + * Invoke command execution + * @param {Command} command - Command + * @returns {Promise} + * @private + */ + _invokeExecution(command) { + this.lock(); + + let { args } = command; + if (!args) { + args = []; } - /** - * fire UNDO_STACK_CHANGED event - * @private - */ - _fireUndoStackChanged() { - this.fire(eventNames.UNDO_STACK_CHANGED, this._undoStack.length); - } - - /** - * Lock this invoker - */ - lock() { - this._isLocked = true; - } - - /** - * Unlock this invoker - */ - unlock() { - this._isLocked = false; - } - - executeSilent(...args) { - this._isSilent = true; - - return this.execute(...args, this._isSilent).then(() => { - this._isSilent = false; - }); - } - - /** - * Invoke command - * Store the command to the undoStack - * Clear the redoStack - * @param {String} commandName - Command name - * @param {...*} args - Arguments for creating command - * @returns {Promise} - */ - execute(...args) { - if (this._isLocked) { - return Promise.reject(rejectMessages.isLock); + return command + .execute(...args) + .then((value) => { + if (!this._isSilent) { + this.pushUndoStack(command); } - - let [command] = args; - if (isString(command)) { - command = commandFactory.create(...args); + this.unlock(); + if (isFunction(command.executeCallback)) { + command.executeCallback(value); } - return this._invokeExecution(command) - .then(value => { - this.clearRedoStack(); - - return value; - }); + return value; + }) + ['catch']((message) => { + this.unlock(); + + return Promise.reject(message); + }); + } + + /** + * Invoke command undo + * @param {Command} command - Command + * @returns {Promise} + * @private + */ + _invokeUndo(command) { + this.lock(); + + let { args } = command; + if (!args) { + args = []; } - /** - * Undo command - * @returns {Promise} - */ - undo() { - let command = this._undoStack.pop(); - let promise; - let message = ''; - - if (command && this._isLocked) { - this.pushUndoStack(command, true); - command = null; - } - if (command) { - if (this.isEmptyUndoStack()) { - this._fireUndoStackChanged(); - } - promise = this._invokeUndo(command); - } else { - message = rejectMessages.undo; - if (this._isLocked) { - message = `${message} Because ${rejectMessages.isLock}`; - } - promise = Promise.reject(message); + return command + .undo(...args) + .then((value) => { + this.pushRedoStack(command); + this.unlock(); + if (isFunction(command.undoCallback)) { + command.undoCallback(value); } - return promise; + return value; + }) + ['catch']((message) => { + this.unlock(); + + return Promise.reject(message); + }); + } + + /** + * fire REDO_STACK_CHANGED event + * @private + */ + _fireRedoStackChanged() { + this.fire(eventNames.REDO_STACK_CHANGED, this._redoStack.length); + } + + /** + * fire UNDO_STACK_CHANGED event + * @private + */ + _fireUndoStackChanged() { + this.fire(eventNames.UNDO_STACK_CHANGED, this._undoStack.length); + } + + /** + * Lock this invoker + */ + lock() { + this._isLocked = true; + } + + /** + * Unlock this invoker + */ + unlock() { + this._isLocked = false; + } + + executeSilent(...args) { + this._isSilent = true; + + return this.execute(...args, this._isSilent).then(() => { + this._isSilent = false; + }); + } + + /** + * Invoke command + * Store the command to the undoStack + * Clear the redoStack + * @param {String} commandName - Command name + * @param {...*} args - Arguments for creating command + * @returns {Promise} + */ + execute(...args) { + if (this._isLocked) { + return Promise.reject(rejectMessages.isLock); } - /** - * Redo command - * @returns {Promise} - */ - redo() { - let command = this._redoStack.pop(); - let promise; - let message = ''; - - if (command && this._isLocked) { - this.pushRedoStack(command, true); - command = null; - } - if (command) { - if (this.isEmptyRedoStack()) { - this._fireRedoStackChanged(); - } - promise = this._invokeExecution(command); - } else { - message = rejectMessages.redo; - if (this._isLocked) { - message = `${message} Because ${rejectMessages.isLock}`; - } - promise = Promise.reject(message); - } - - return promise; + let [command] = args; + if (isString(command)) { + command = commandFactory.create(...args); } - /** - * Push undo stack - * @param {Command} command - command - * @param {boolean} [isSilent] - Fire event or not - */ - pushUndoStack(command, isSilent) { - this._undoStack.push(command); - if (!isSilent) { - this._fireUndoStackChanged(); - } + return this._invokeExecution(command).then((value) => { + this.clearRedoStack(); + + return value; + }); + } + + /** + * Undo command + * @returns {Promise} + */ + undo() { + let command = this._undoStack.pop(); + let promise; + let message = ''; + + if (command && this._isLocked) { + this.pushUndoStack(command, true); + command = null; } - - /** - * Push redo stack - * @param {Command} command - command - * @param {boolean} [isSilent] - Fire event or not - */ - pushRedoStack(command, isSilent) { - this._redoStack.push(command); - if (!isSilent) { - this._fireRedoStackChanged(); - } + if (command) { + if (this.isEmptyUndoStack()) { + this._fireUndoStackChanged(); + } + promise = this._invokeUndo(command); + } else { + message = rejectMessages.undo; + if (this._isLocked) { + message = `${message} Because ${rejectMessages.isLock}`; + } + promise = Promise.reject(message); } - /** - * Return whether the redoStack is empty - * @returns {boolean} - */ - isEmptyRedoStack() { - return this._redoStack.length === 0; + return promise; + } + + /** + * Redo command + * @returns {Promise} + */ + redo() { + let command = this._redoStack.pop(); + let promise; + let message = ''; + + if (command && this._isLocked) { + this.pushRedoStack(command, true); + command = null; } - - /** - * Return whether the undoStack is empty - * @returns {boolean} - */ - isEmptyUndoStack() { - return this._undoStack.length === 0; + if (command) { + if (this.isEmptyRedoStack()) { + this._fireRedoStackChanged(); + } + promise = this._invokeExecution(command); + } else { + message = rejectMessages.redo; + if (this._isLocked) { + message = `${message} Because ${rejectMessages.isLock}`; + } + promise = Promise.reject(message); } - /** - * Clear undoStack - */ - clearUndoStack() { - if (!this.isEmptyUndoStack()) { - this._undoStack = []; - this._fireUndoStackChanged(); - } + return promise; + } + + /** + * Push undo stack + * @param {Command} command - command + * @param {boolean} [isSilent] - Fire event or not + */ + pushUndoStack(command, isSilent) { + this._undoStack.push(command); + if (!isSilent) { + this._fireUndoStackChanged(); } - - /** - * Clear redoStack - */ - clearRedoStack() { - if (!this.isEmptyRedoStack()) { - this._redoStack = []; - this._fireRedoStackChanged(); - } + } + + /** + * Push redo stack + * @param {Command} command - command + * @param {boolean} [isSilent] - Fire event or not + */ + pushRedoStack(command, isSilent) { + this._redoStack.push(command); + if (!isSilent) { + this._fireRedoStackChanged(); + } + } + + /** + * Return whether the redoStack is empty + * @returns {boolean} + */ + isEmptyRedoStack() { + return this._redoStack.length === 0; + } + + /** + * Return whether the undoStack is empty + * @returns {boolean} + */ + isEmptyUndoStack() { + return this._undoStack.length === 0; + } + + /** + * Clear undoStack + */ + clearUndoStack() { + if (!this.isEmptyUndoStack()) { + this._undoStack = []; + this._fireUndoStackChanged(); + } + } + + /** + * Clear redoStack + */ + clearRedoStack() { + if (!this.isEmptyRedoStack()) { + this._redoStack = []; + this._fireRedoStackChanged(); } + } } CustomEvents.mixin(Invoker); diff --git a/src/js/polyfill.js b/src/js/polyfill.js index 6cd9d2efd..3bd1e9d32 100644 --- a/src/js/polyfill.js +++ b/src/js/polyfill.js @@ -1,20 +1,18 @@ // https://developer.mozilla.org/en-US/docs/Web/API/Element/closest // Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/if (!Element.prototype.matches) - Element.prototype.matches = Element.prototype.msMatchesSelector || - Element.prototype.webkitMatchesSelector; +Element.prototype.matches = + Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector; if (!Element.prototype.closest) - Element.prototype.closest = function(s) { - var el = this; - if (!document.documentElement.contains(el)) return null; - do { - if (el.matches(s)) return el; - el = el.parentElement || el.parentNode; - } while (el !== null && el.nodeType === 1); - return null; - }; - - + Element.prototype.closest = function (s) { + var el = this; + if (!document.documentElement.contains(el)) return null; + do { + if (el.matches(s)) return el; + el = el.parentElement || el.parentNode; + } while (el !== null && el.nodeType === 1); + return null; + }; /* * classList.js: Cross-browser full element.classList implementation. @@ -29,235 +27,212 @@ if (!Element.prototype.closest) /*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList.js */ -if ("document" in window.self) { - -// Full polyfill for browsers with no classList support -// Including IE < Edge missing SVGElement.classList -if (!("classList" in document.createElement("_")) - || document.createElementNS && !("classList" in document.createElementNS("http://www.w3.org/2000/svg","g"))) { - -(function (view) { - -"use strict"; - -if (!('Element' in view)) return; - -var - classListProp = "classList" - , protoProp = "prototype" - , elemCtrProto = view.Element[protoProp] - , objCtr = Object - , strTrim = String[protoProp].trim || function () { - return this.replace(/^\s+|\s+$/g, ""); - } - , arrIndexOf = Array[protoProp].indexOf || function (item) { - var - i = 0 - , len = this.length - ; - for (; i < len; i++) { - if (i in this && this[i] === item) { - return i; - } - } - return -1; - } - // Vendors: please allow content code to instantiate DOMExceptions - , DOMEx = function (type, message) { - this.name = type; - this.code = DOMException[type]; - this.message = message; - } - , checkTokenAndGetIndex = function (classList, token) { - if (token === "") { - throw new DOMEx( - "SYNTAX_ERR" - , "An invalid or illegal string was specified" - ); - } - if (/\s/.test(token)) { - throw new DOMEx( - "INVALID_CHARACTER_ERR" - , "String contains an invalid character" - ); - } - return arrIndexOf.call(classList, token); - } - , ClassList = function (elem) { - var - trimmedClasses = strTrim.call(elem.getAttribute("class") || "") - , classes = trimmedClasses ? trimmedClasses.split(/\s+/) : [] - , i = 0 - , len = classes.length - ; - for (; i < len; i++) { - this.push(classes[i]); - } - this._updateClassName = function () { - elem.setAttribute("class", this.toString()); - }; - } - , classListProto = ClassList[protoProp] = [] - , classListGetter = function () { - return new ClassList(this); - } -; -// Most DOMException implementations don't allow calling DOMException's toString() -// on non-DOMExceptions. Error's toString() is sufficient here. -DOMEx[protoProp] = Error[protoProp]; -classListProto.item = function (i) { - return this[i] || null; -}; -classListProto.contains = function (token) { - token += ""; - return checkTokenAndGetIndex(this, token) !== -1; -}; -classListProto.add = function () { - var - tokens = arguments - , i = 0 - , l = tokens.length - , token - , updated = false - ; - do { - token = tokens[i] + ""; - if (checkTokenAndGetIndex(this, token) === -1) { - this.push(token); - updated = true; - } - } - while (++i < l); - - if (updated) { - this._updateClassName(); - } -}; -classListProto.remove = function () { - var - tokens = arguments - , i = 0 - , l = tokens.length - , token - , updated = false - , index - ; - do { - token = tokens[i] + ""; - index = checkTokenAndGetIndex(this, token); - while (index !== -1) { - this.splice(index, 1); - updated = true; - index = checkTokenAndGetIndex(this, token); - } - } - while (++i < l); - - if (updated) { - this._updateClassName(); - } -}; -classListProto.toggle = function (token, force) { - token += ""; - - var - result = this.contains(token) - , method = result ? - force !== true && "remove" - : - force !== false && "add" - ; - - if (method) { - this[method](token); - } - - if (force === true || force === false) { - return force; - } else { - return !result; - } -}; -classListProto.toString = function () { - return this.join(" "); -}; - -if (objCtr.defineProperty) { - var classListPropDesc = { - get: classListGetter - , enumerable: true - , configurable: true - }; - try { - objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); - } catch (ex) { // IE 8 doesn't support enumerable:true - // adding undefined to fight this issue https://github.com/eligrey/classList.js/issues/36 - // modernie IE8-MSW7 machine has IE8 8.0.6001.18702 and is affected - if (ex.number === undefined || ex.number === -0x7FF5EC54) { - classListPropDesc.enumerable = false; - objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); - } - } -} else if (objCtr[protoProp].__defineGetter__) { - elemCtrProto.__defineGetter__(classListProp, classListGetter); -} - -}(window.self)); - -} - -// There is full or partial native classList support, so just check if we need -// to normalize the add/remove and toggle APIs. +if ('document' in window.self) { + // Full polyfill for browsers with no classList support + // Including IE < Edge missing SVGElement.classList + if ( + !('classList' in document.createElement('_')) || + (document.createElementNS && + !('classList' in document.createElementNS('http://www.w3.org/2000/svg', 'g'))) + ) { + (function (view) { + 'use strict'; + + if (!('Element' in view)) return; + + var classListProp = 'classList', + protoProp = 'prototype', + elemCtrProto = view.Element[protoProp], + objCtr = Object, + strTrim = + String[protoProp].trim || + function () { + return this.replace(/^\s+|\s+$/g, ''); + }, + arrIndexOf = + Array[protoProp].indexOf || + function (item) { + var i = 0, + len = this.length; + for (; i < len; i++) { + if (i in this && this[i] === item) { + return i; + } + } + return -1; + }, + // Vendors: please allow content code to instantiate DOMExceptions + DOMEx = function (type, message) { + this.name = type; + this.code = DOMException[type]; + this.message = message; + }, + checkTokenAndGetIndex = function (classList, token) { + if (token === '') { + throw new DOMEx('SYNTAX_ERR', 'An invalid or illegal string was specified'); + } + if (/\s/.test(token)) { + throw new DOMEx('INVALID_CHARACTER_ERR', 'String contains an invalid character'); + } + return arrIndexOf.call(classList, token); + }, + ClassList = function (elem) { + var trimmedClasses = strTrim.call(elem.getAttribute('class') || ''), + classes = trimmedClasses ? trimmedClasses.split(/\s+/) : [], + i = 0, + len = classes.length; + for (; i < len; i++) { + this.push(classes[i]); + } + this._updateClassName = function () { + elem.setAttribute('class', this.toString()); + }; + }, + classListProto = (ClassList[protoProp] = []), + classListGetter = function () { + return new ClassList(this); + }; + // Most DOMException implementations don't allow calling DOMException's toString() + // on non-DOMExceptions. Error's toString() is sufficient here. + DOMEx[protoProp] = Error[protoProp]; + classListProto.item = function (i) { + return this[i] || null; + }; + classListProto.contains = function (token) { + token += ''; + return checkTokenAndGetIndex(this, token) !== -1; + }; + classListProto.add = function () { + var tokens = arguments, + i = 0, + l = tokens.length, + token, + updated = false; + do { + token = tokens[i] + ''; + if (checkTokenAndGetIndex(this, token) === -1) { + this.push(token); + updated = true; + } + } while (++i < l); + + if (updated) { + this._updateClassName(); + } + }; + classListProto.remove = function () { + var tokens = arguments, + i = 0, + l = tokens.length, + token, + updated = false, + index; + do { + token = tokens[i] + ''; + index = checkTokenAndGetIndex(this, token); + while (index !== -1) { + this.splice(index, 1); + updated = true; + index = checkTokenAndGetIndex(this, token); + } + } while (++i < l); + + if (updated) { + this._updateClassName(); + } + }; + classListProto.toggle = function (token, force) { + token += ''; + + var result = this.contains(token), + method = result ? force !== true && 'remove' : force !== false && 'add'; + if (method) { + this[method](token); + } -(function () { - "use strict"; + if (force === true || force === false) { + return force; + } else { + return !result; + } + }; + classListProto.toString = function () { + return this.join(' '); + }; + + if (objCtr.defineProperty) { + var classListPropDesc = { + get: classListGetter, + enumerable: true, + configurable: true, + }; + try { + objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); + } catch (ex) { + // IE 8 doesn't support enumerable:true + // adding undefined to fight this issue https://github.com/eligrey/classList.js/issues/36 + // modernie IE8-MSW7 machine has IE8 8.0.6001.18702 and is affected + if (ex.number === undefined || ex.number === -0x7ff5ec54) { + classListPropDesc.enumerable = false; + objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); + } + } + } else if (objCtr[protoProp].__defineGetter__) { + elemCtrProto.__defineGetter__(classListProp, classListGetter); + } + })(window.self); + } - var testElement = document.createElement("_"); + // There is full or partial native classList support, so just check if we need + // to normalize the add/remove and toggle APIs. - testElement.classList.add("c1", "c2"); + (function () { + 'use strict'; - // Polyfill for IE 10/11 and Firefox <26, where classList.add and - // classList.remove exist but support only one argument at a time. - if (!testElement.classList.contains("c2")) { - var createMethod = function(method) { - var original = DOMTokenList.prototype[method]; + var testElement = document.createElement('_'); - DOMTokenList.prototype[method] = function(token) { - var i, len = arguments.length; + testElement.classList.add('c1', 'c2'); - for (i = 0; i < len; i++) { - token = arguments[i]; - original.call(this, token); - } - }; - }; - createMethod('add'); - createMethod('remove'); - } + // Polyfill for IE 10/11 and Firefox <26, where classList.add and + // classList.remove exist but support only one argument at a time. + if (!testElement.classList.contains('c2')) { + var createMethod = function (method) { + var original = DOMTokenList.prototype[method]; - testElement.classList.toggle("c3", false); + DOMTokenList.prototype[method] = function (token) { + var i, + len = arguments.length; - // Polyfill for IE 10 and Firefox <24, where classList.toggle does not - // support the second argument. - if (testElement.classList.contains("c3")) { - var _toggle = DOMTokenList.prototype.toggle; + for (i = 0; i < len; i++) { + token = arguments[i]; + original.call(this, token); + } + }; + }; + createMethod('add'); + createMethod('remove'); + } - DOMTokenList.prototype.toggle = function(token, force) { - if (1 in arguments && !this.contains(token) === !force) { - return force; - } else { - return _toggle.call(this, token); - } - }; + testElement.classList.toggle('c3', false); - } + // Polyfill for IE 10 and Firefox <24, where classList.toggle does not + // support the second argument. + if (testElement.classList.contains('c3')) { + var _toggle = DOMTokenList.prototype.toggle; - testElement = null; -}()); + DOMTokenList.prototype.toggle = function (token, force) { + if (1 in arguments && !this.contains(token) === !force) { + return force; + } else { + return _toggle.call(this, token); + } + }; + } + testElement = null; + })(); } - /*! * @copyright Copyright (c) 2017 IcoMoon.io * @license Licensed under MIT license @@ -267,226 +242,232 @@ if (objCtr.defineProperty) { /*jslint browser: true */ /*global XDomainRequest, MutationObserver, window */ (function () { - "use strict"; - if (typeof window !== "undefined" && window.addEventListener) { - var cache = Object.create(null); // holds xhr objects to prevent multiple requests - var checkUseElems; - var tid; // timeout id - var debouncedCheck = function () { - clearTimeout(tid); - tid = setTimeout(checkUseElems, 100); + 'use strict'; + if (typeof window !== 'undefined' && window.addEventListener) { + var cache = Object.create(null); // holds xhr objects to prevent multiple requests + var checkUseElems; + var tid; // timeout id + var debouncedCheck = function () { + clearTimeout(tid); + tid = setTimeout(checkUseElems, 100); + }; + var unobserveChanges = function () { + return; + }; + var observeChanges = function () { + var observer; + window.addEventListener('resize', debouncedCheck, false); + window.addEventListener('orientationchange', debouncedCheck, false); + if (window.MutationObserver) { + observer = new MutationObserver(debouncedCheck); + observer.observe(document.documentElement, { + childList: true, + subtree: true, + attributes: true, + }); + unobserveChanges = function () { + try { + observer.disconnect(); + window.removeEventListener('resize', debouncedCheck, false); + window.removeEventListener('orientationchange', debouncedCheck, false); + } catch (ignore) {} }; - var unobserveChanges = function () { - return; + } else { + document.documentElement.addEventListener('DOMSubtreeModified', debouncedCheck, false); + unobserveChanges = function () { + document.documentElement.removeEventListener('DOMSubtreeModified', debouncedCheck, false); + window.removeEventListener('resize', debouncedCheck, false); + window.removeEventListener('orientationchange', debouncedCheck, false); }; - var observeChanges = function () { - var observer; - window.addEventListener("resize", debouncedCheck, false); - window.addEventListener("orientationchange", debouncedCheck, false); - if (window.MutationObserver) { - observer = new MutationObserver(debouncedCheck); - observer.observe(document.documentElement, { - childList: true, - subtree: true, - attributes: true - }); - unobserveChanges = function () { - try { - observer.disconnect(); - window.removeEventListener("resize", debouncedCheck, false); - window.removeEventListener("orientationchange", debouncedCheck, false); - } catch (ignore) {} - }; - } else { - document.documentElement.addEventListener("DOMSubtreeModified", debouncedCheck, false); - unobserveChanges = function () { - document.documentElement.removeEventListener("DOMSubtreeModified", debouncedCheck, false); - window.removeEventListener("resize", debouncedCheck, false); - window.removeEventListener("orientationchange", debouncedCheck, false); - }; + } + }; + var createRequest = function (url) { + // In IE 9, cross origin requests can only be sent using XDomainRequest. + // XDomainRequest would fail if CORS headers are not set. + // Therefore, XDomainRequest should only be used with cross origin requests. + function getOrigin(loc) { + var a; + if (loc.protocol !== undefined) { + a = loc; + } else { + a = document.createElement('a'); + a.href = loc; + } + return a.protocol.replace(/:/g, '') + a.host; + } + var Request; + var origin; + var origin2; + if (window.XMLHttpRequest) { + Request = new XMLHttpRequest(); + origin = getOrigin(location); + origin2 = getOrigin(url); + if (Request.withCredentials === undefined && origin2 !== '' && origin2 !== origin) { + Request = XDomainRequest || undefined; + } else { + Request = XMLHttpRequest; + } + } + return Request; + }; + var xlinkNS = 'http://www.w3.org/1999/xlink'; + checkUseElems = function () { + var base; + var bcr; + var fallback = ''; // optional fallback URL in case no base path to SVG file was given and no symbol definition was found. + var hash; + var href; + var i; + var inProgressCount = 0; + var isHidden; + var Request; + var url; + var uses; + var xhr; + function observeIfDone() { + // If done with making changes, start watching for chagnes in DOM again + inProgressCount -= 1; + if (inProgressCount === 0) { + // if all xhrs were resolved + unobserveChanges(); // make sure to remove old handlers + observeChanges(); // watch for changes to DOM + } + } + function attrUpdateFunc(spec) { + return function () { + if (cache[spec.base] !== true) { + spec.useEl.setAttributeNS(xlinkNS, 'xlink:href', '#' + spec.hash); + if (spec.useEl.hasAttribute('href')) { + spec.useEl.setAttribute('href', '#' + spec.hash); } + } }; - var createRequest = function (url) { - // In IE 9, cross origin requests can only be sent using XDomainRequest. - // XDomainRequest would fail if CORS headers are not set. - // Therefore, XDomainRequest should only be used with cross origin requests. - function getOrigin(loc) { - var a; - if (loc.protocol !== undefined) { - a = loc; - } else { - a = document.createElement("a"); - a.href = loc; - } - return a.protocol.replace(/:/g, "") + a.host; - } - var Request; - var origin; - var origin2; - if (window.XMLHttpRequest) { - Request = new XMLHttpRequest(); - origin = getOrigin(location); - origin2 = getOrigin(url); - if (Request.withCredentials === undefined && origin2 !== "" && origin2 !== origin) { - Request = XDomainRequest || undefined; - } else { - Request = XMLHttpRequest; - } - } - return Request; + } + function onloadFunc(xhr) { + return function () { + var body = document.body; + var x = document.createElement('x'); + var svg; + xhr.onload = null; + x.innerHTML = xhr.responseText; + svg = x.getElementsByTagName('svg')[0]; + if (svg) { + svg.setAttribute('aria-hidden', 'true'); + svg.style.position = 'absolute'; + svg.style.width = 0; + svg.style.height = 0; + svg.style.overflow = 'hidden'; + body.insertBefore(svg, body.firstChild); + } + observeIfDone(); }; - var xlinkNS = "http://www.w3.org/1999/xlink"; - checkUseElems = function () { - var base; - var bcr; - var fallback = ""; // optional fallback URL in case no base path to SVG file was given and no symbol definition was found. - var hash; - var href; - var i; - var inProgressCount = 0; - var isHidden; - var Request; - var url; - var uses; - var xhr; - function observeIfDone() { - // If done with making changes, start watching for chagnes in DOM again - inProgressCount -= 1; - if (inProgressCount === 0) { // if all xhrs were resolved - unobserveChanges(); // make sure to remove old handlers - observeChanges(); // watch for changes to DOM - } - } - function attrUpdateFunc(spec) { - return function () { - if (cache[spec.base] !== true) { - spec.useEl.setAttributeNS(xlinkNS, "xlink:href", "#" + spec.hash); - if (spec.useEl.hasAttribute("href")) { - spec.useEl.setAttribute("href", "#" + spec.hash); - } - } - }; - } - function onloadFunc(xhr) { - return function () { - var body = document.body; - var x = document.createElement("x"); - var svg; - xhr.onload = null; - x.innerHTML = xhr.responseText; - svg = x.getElementsByTagName("svg")[0]; - if (svg) { - svg.setAttribute("aria-hidden", "true"); - svg.style.position = "absolute"; - svg.style.width = 0; - svg.style.height = 0; - svg.style.overflow = "hidden"; - body.insertBefore(svg, body.firstChild); - } - observeIfDone(); - }; - } - function onErrorTimeout(xhr) { - return function () { - xhr.onerror = null; - xhr.ontimeout = null; - observeIfDone(); - }; + } + function onErrorTimeout(xhr) { + return function () { + xhr.onerror = null; + xhr.ontimeout = null; + observeIfDone(); + }; + } + unobserveChanges(); // stop watching for changes to DOM + // find all use elements + uses = document.getElementsByTagName('use'); + for (i = 0; i < uses.length; i += 1) { + try { + bcr = uses[i].getBoundingClientRect(); + } catch (ignore) { + // failed to get bounding rectangle of the use element + bcr = false; + } + href = + uses[i].getAttribute('href') || + uses[i].getAttributeNS(xlinkNS, 'href') || + uses[i].getAttribute('xlink:href'); + if (href && href.split) { + url = href.split('#'); + } else { + url = ['', '']; + } + base = url[0]; + hash = url[1]; + isHidden = bcr && bcr.left === 0 && bcr.right === 0 && bcr.top === 0 && bcr.bottom === 0; + if (bcr && bcr.width === 0 && bcr.height === 0 && !isHidden) { + // the use element is empty + // if there is a reference to an external SVG, try to fetch it + // use the optional fallback URL if there is no reference to an external SVG + if (fallback && !base.length && hash && !document.getElementById(hash)) { + base = fallback; + } + if (uses[i].hasAttribute('href')) { + uses[i].setAttributeNS(xlinkNS, 'xlink:href', href); + } + if (base.length) { + // schedule updating xlink:href + xhr = cache[base]; + if (xhr !== true) { + // true signifies that prepending the SVG was not required + setTimeout( + attrUpdateFunc({ + useEl: uses[i], + base: base, + hash: hash, + }), + 0 + ); } - unobserveChanges(); // stop watching for changes to DOM - // find all use elements - uses = document.getElementsByTagName("use"); - for (i = 0; i < uses.length; i += 1) { - try { - bcr = uses[i].getBoundingClientRect(); - } catch (ignore) { - // failed to get bounding rectangle of the use element - bcr = false; - } - href = uses[i].getAttribute("href") - || uses[i].getAttributeNS(xlinkNS, "href") - || uses[i].getAttribute("xlink:href"); - if (href && href.split) { - url = href.split("#"); - } else { - url = ["", ""]; - } - base = url[0]; - hash = url[1]; - isHidden = bcr && bcr.left === 0 && bcr.right === 0 && bcr.top === 0 && bcr.bottom === 0; - if (bcr && bcr.width === 0 && bcr.height === 0 && !isHidden) { - // the use element is empty - // if there is a reference to an external SVG, try to fetch it - // use the optional fallback URL if there is no reference to an external SVG - if (fallback && !base.length && hash && !document.getElementById(hash)) { - base = fallback; - } - if (uses[i].hasAttribute("href")) { - uses[i].setAttributeNS(xlinkNS, "xlink:href", href); - } - if (base.length) { - // schedule updating xlink:href - xhr = cache[base]; - if (xhr !== true) { - // true signifies that prepending the SVG was not required - setTimeout(attrUpdateFunc({ - useEl: uses[i], - base: base, - hash: hash - }), 0); - } - if (xhr === undefined) { - Request = createRequest(base); - if (Request !== undefined) { - xhr = new Request(); - cache[base] = xhr; - xhr.onload = onloadFunc(xhr); - xhr.onerror = onErrorTimeout(xhr); - xhr.ontimeout = onErrorTimeout(xhr); - xhr.open("GET", base); - xhr.send(); - inProgressCount += 1; - } - } - } - } else { - if (!isHidden) { - if (cache[base] === undefined) { - // remember this URL if the use element was not empty and no request was sent - cache[base] = true; - } else if (cache[base].onload) { - // if it turns out that prepending the SVG is not necessary, - // abort the in-progress xhr. - cache[base].abort(); - delete cache[base].onload; - cache[base] = true; - } - } else if (base.length && cache[base]) { - setTimeout(attrUpdateFunc({ - useEl: uses[i], - base: base, - hash: hash - }), 0); - } - } + if (xhr === undefined) { + Request = createRequest(base); + if (Request !== undefined) { + xhr = new Request(); + cache[base] = xhr; + xhr.onload = onloadFunc(xhr); + xhr.onerror = onErrorTimeout(xhr); + xhr.ontimeout = onErrorTimeout(xhr); + xhr.open('GET', base); + xhr.send(); + inProgressCount += 1; + } } - uses = ""; - inProgressCount += 1; - observeIfDone(); - }; - var winLoad; - winLoad = function () { - window.removeEventListener("load", winLoad, false); // to prevent memory leaks - tid = setTimeout(checkUseElems, 0); - }; - if (document.readyState !== "complete") { - // The load event fires when all resources have finished loading, which allows detecting whether SVG use elements are empty. - window.addEventListener("load", winLoad, false); + } } else { - // No need to add a listener if the document is already loaded, initialize immediately. - winLoad(); + if (!isHidden) { + if (cache[base] === undefined) { + // remember this URL if the use element was not empty and no request was sent + cache[base] = true; + } else if (cache[base].onload) { + // if it turns out that prepending the SVG is not necessary, + // abort the in-progress xhr. + cache[base].abort(); + delete cache[base].onload; + cache[base] = true; + } + } else if (base.length && cache[base]) { + setTimeout( + attrUpdateFunc({ + useEl: uses[i], + base: base, + hash: hash, + }), + 0 + ); + } } + } + uses = ''; + inProgressCount += 1; + observeIfDone(); + }; + var winLoad; + winLoad = function () { + window.removeEventListener('load', winLoad, false); // to prevent memory leaks + tid = setTimeout(checkUseElems, 0); + }; + if (document.readyState !== 'complete') { + // The load event fires when all resources have finished loading, which allows detecting whether SVG use elements are empty. + window.addEventListener('load', winLoad, false); + } else { + // No need to add a listener if the document is already loaded, initialize immediately. + winLoad(); } -}()); - - + } +})(); diff --git a/src/js/ui.js b/src/js/ui.js index 87baf7c85..9adf9dc0c 100644 --- a/src/js/ui.js +++ b/src/js/ui.js @@ -1,6 +1,6 @@ import snippet from 'tui-code-snippet'; -import {HELP_MENUS} from './consts'; -import {getSelector, assignmentForDestroy, cls} from './util'; +import { HELP_MENUS } from './consts'; +import { getSelector, assignmentForDestroy, cls } from './util'; import mainContainer from './ui/template/mainContainer'; import controls from './ui/template/controls'; @@ -17,15 +17,15 @@ import Filter from './ui/filter'; import Locale from './ui/locale/locale'; const SUB_UI_COMPONENT = { - Shape, - Crop, - Flip, - Rotate, - Text, - Mask, - Icon, - Draw, - Filter + Shape, + Crop, + Flip, + Rotate, + Text, + Mask, + Icon, + Draw, + Filter, }; const BI_EXPRESSION_MINSIZE_WHEN_TOP_POSITION = '1300'; @@ -46,616 +46,633 @@ const BI_EXPRESSION_MINSIZE_WHEN_TOP_POSITION = '1300'; * @param {Object} actions - ui action instance */ class Ui { - constructor(element, options, actions) { - this.options = this._initializeOption(options); - this._actions = actions; - this.submenu = false; - this.imageSize = {}; - this.uiSize = {}; - this._locale = new Locale(this.options.locale); - this.theme = new Theme(this.options.theme); - this.eventHandler = {}; - this._submenuChangeTransection = false; - this._selectedElement = null; - this._mainElement = null; - this._editorElementWrap = null; - this._editorElement = null; - this._menuElement = null; - this._subMenuElement = null; - this._makeUiElement(element); - this._setUiSize(); - this._initMenuEvent = false; - - this._makeSubMenu(); - } - - /** - * Destroys the instance. - */ - destroy() { - this._removeUiEvent(); - this._destroyAllMenu(); - this._selectedElement.innerHTML = ''; - - assignmentForDestroy(this); - } - - /** - * Set Default Selection for includeUI - * @param {Object} option - imageEditor options - * @returns {Object} - extends selectionStyle option - * @ignore - */ - setUiDefaultSelectionStyle(option) { - return snippet.extend({ - applyCropSelectionStyle: true, - applyGroupSelectionStyle: true, - selectionStyle: { - cornerStyle: 'circle', - cornerSize: 16, - cornerColor: '#fff', - cornerStrokeColor: '#fff', - transparentCorners: false, - lineWidth: 2, - borderColor: '#fff' - } - }, option); - } - - /** - * Change editor size - * @param {Object} resizeInfo - ui & image size info - * @param {Object} [resizeInfo.uiSize] - image size dimension - * @param {string} resizeInfo.uiSize.width - ui width - * @param {string} resizeInfo.uiSize.height - ui height - * @param {Object} [resizeInfo.imageSize] - image size dimension - * @param {Number} resizeInfo.imageSize.oldWidth - old width - * @param {Number} resizeInfo.imageSize.oldHeight - old height - * @param {Number} resizeInfo.imageSize.newWidth - new width - * @param {Number} resizeInfo.imageSize.newHeight - new height - * @example - * // Change the image size and ui size, and change the affected ui state together. - * imageEditor.ui.resizeEditor({ - * imageSize: {oldWidth: 100, oldHeight: 100, newWidth: 700, newHeight: 700}, - * uiSize: {width: 1000, height: 1000} - * }); - * @example - * // Apply the ui state while preserving the previous attribute (for example, if responsive Ui) - * imageEditor.ui.resizeEditor(); - */ - resizeEditor({uiSize, imageSize = this.imageSize} = {}) { - if (imageSize !== this.imageSize) { - this.imageSize = imageSize; - } - if (uiSize) { - this._setUiSize(uiSize); - } - - const {width, height} = this._getCanvasMaxDimension(); - const editorElementStyle = this._editorElement.style; - const {menuBarPosition} = this.options; - - editorElementStyle.height = `${height}px`; - editorElementStyle.width = `${width}px`; - - this._setEditorPosition(menuBarPosition); - - this._editorElementWrap.style.bottom = `0px`; - this._editorElementWrap.style.top = `0px`; - this._editorElementWrap.style.left = `0px`; - this._editorElementWrap.style.width = `100%`; - - const selectElementClassList = this._selectedElement.classList; - - if (menuBarPosition === 'top' && this._selectedElement.offsetWidth < BI_EXPRESSION_MINSIZE_WHEN_TOP_POSITION) { - selectElementClassList.add('tui-image-editor-top-optimization'); - } else { - selectElementClassList.remove('tui-image-editor-top-optimization'); - } - } - - /** - * Change help button status - * @param {string} buttonType - target button type - * @param {Boolean} enableStatus - enabled status - * @ignore - */ - changeHelpButtonEnabled(buttonType, enableStatus) { - const buttonClassList = this._buttonElements[buttonType].classList; - - buttonClassList[enableStatus ? 'add' : 'remove']('enabled'); - } - - /** - * Change delete button status - * @param {Object} [options] - Ui setting options - * @param {object} [options.loadImage] - Init default load image - * @param {string} [options.initMenu] - Init start menu - * @param {string} [options.menuBarPosition=bottom] - Let - * @param {boolean} [options.applyCropSelectionStyle=false] - Let - * @param {boolean} [options.usageStatistics=false] - Send statistics ping or not - * @returns {Object} initialize option - * @private - */ - _initializeOption(options) { - return snippet.extend({ - loadImage: { - path: '', - name: '' - }, - locale: {}, - menuIconPath: '', - menu: ['crop', 'flip', 'rotate', 'draw', 'shape', 'icon', 'text', 'mask', 'filter'], - initMenu: '', - uiSize: { - width: '100%', - height: '100%' - }, - menuBarPosition: 'bottom' - }, options); - } - - /** - * Set ui container size - * @param {Object} uiSize - ui dimension - * @param {string} uiSize.width - css width property - * @param {string} uiSize.height - css height property - * @private - */ - _setUiSize(uiSize = this.options.uiSize) { - const elementDimension = this._selectedElement.style; - elementDimension.width = uiSize.width; - elementDimension.height = uiSize.height; - } - - /** - * Make submenu dom element - * @private - */ - _makeSubMenu() { - snippet.forEach(this.options.menu, menuName => { - const SubComponentClass = SUB_UI_COMPONENT[menuName.replace(/^[a-z]/, $0 => $0.toUpperCase())]; - - // make menu element - this._makeMenuElement(menuName); - - // menu btn element - this._buttonElements[menuName] = this._menuElement.querySelector(`.tie-btn-${menuName}`); - - // submenu ui instance - this[menuName] = new SubComponentClass(this._subMenuElement, { - locale: this._locale, - makeSvgIcon: this.theme.makeMenSvgIconSet.bind(this.theme), - menuBarPosition: this.options.menuBarPosition, - usageStatistics: this.options.usageStatistics - }); - }); - } - - /** - * Make primary ui dom element - * @param {string|HTMLElement} element - Wrapper's element or selector - * @private - */ - _makeUiElement(element) { - let selectedElement; - - window.snippet = snippet; - - if (element.nodeType) { - selectedElement = element; - } else { - selectedElement = document.querySelector(element); + constructor(element, options, actions) { + this.options = this._initializeOption(options); + this._actions = actions; + this.submenu = false; + this.imageSize = {}; + this.uiSize = {}; + this._locale = new Locale(this.options.locale); + this.theme = new Theme(this.options.theme); + this.eventHandler = {}; + this._submenuChangeTransection = false; + this._selectedElement = null; + this._mainElement = null; + this._editorElementWrap = null; + this._editorElement = null; + this._menuElement = null; + this._subMenuElement = null; + this._makeUiElement(element); + this._setUiSize(); + this._initMenuEvent = false; + + this._makeSubMenu(); + } + + /** + * Destroys the instance. + */ + destroy() { + this._removeUiEvent(); + this._destroyAllMenu(); + this._selectedElement.innerHTML = ''; + + assignmentForDestroy(this); + } + + /** + * Set Default Selection for includeUI + * @param {Object} option - imageEditor options + * @returns {Object} - extends selectionStyle option + * @ignore + */ + setUiDefaultSelectionStyle(option) { + return snippet.extend( + { + applyCropSelectionStyle: true, + applyGroupSelectionStyle: true, + selectionStyle: { + cornerStyle: 'circle', + cornerSize: 16, + cornerColor: '#fff', + cornerStrokeColor: '#fff', + transparentCorners: false, + lineWidth: 2, + borderColor: '#fff', + }, + }, + option + ); + } + + /** + * Change editor size + * @param {Object} resizeInfo - ui & image size info + * @param {Object} [resizeInfo.uiSize] - image size dimension + * @param {string} resizeInfo.uiSize.width - ui width + * @param {string} resizeInfo.uiSize.height - ui height + * @param {Object} [resizeInfo.imageSize] - image size dimension + * @param {Number} resizeInfo.imageSize.oldWidth - old width + * @param {Number} resizeInfo.imageSize.oldHeight - old height + * @param {Number} resizeInfo.imageSize.newWidth - new width + * @param {Number} resizeInfo.imageSize.newHeight - new height + * @example + * // Change the image size and ui size, and change the affected ui state together. + * imageEditor.ui.resizeEditor({ + * imageSize: {oldWidth: 100, oldHeight: 100, newWidth: 700, newHeight: 700}, + * uiSize: {width: 1000, height: 1000} + * }); + * @example + * // Apply the ui state while preserving the previous attribute (for example, if responsive Ui) + * imageEditor.ui.resizeEditor(); + */ + resizeEditor({ uiSize, imageSize = this.imageSize } = {}) { + if (imageSize !== this.imageSize) { + this.imageSize = imageSize; + } + if (uiSize) { + this._setUiSize(uiSize); + } + + const { width, height } = this._getCanvasMaxDimension(); + const editorElementStyle = this._editorElement.style; + const { menuBarPosition } = this.options; + + editorElementStyle.height = `${height}px`; + editorElementStyle.width = `${width}px`; + + this._setEditorPosition(menuBarPosition); + + this._editorElementWrap.style.bottom = `0px`; + this._editorElementWrap.style.top = `0px`; + this._editorElementWrap.style.left = `0px`; + this._editorElementWrap.style.width = `100%`; + + const selectElementClassList = this._selectedElement.classList; + + if ( + menuBarPosition === 'top' && + this._selectedElement.offsetWidth < BI_EXPRESSION_MINSIZE_WHEN_TOP_POSITION + ) { + selectElementClassList.add('tui-image-editor-top-optimization'); + } else { + selectElementClassList.remove('tui-image-editor-top-optimization'); + } + } + + /** + * Change help button status + * @param {string} buttonType - target button type + * @param {Boolean} enableStatus - enabled status + * @ignore + */ + changeHelpButtonEnabled(buttonType, enableStatus) { + const buttonClassList = this._buttonElements[buttonType].classList; + + buttonClassList[enableStatus ? 'add' : 'remove']('enabled'); + } + + /** + * Change delete button status + * @param {Object} [options] - Ui setting options + * @param {object} [options.loadImage] - Init default load image + * @param {string} [options.initMenu] - Init start menu + * @param {string} [options.menuBarPosition=bottom] - Let + * @param {boolean} [options.applyCropSelectionStyle=false] - Let + * @param {boolean} [options.usageStatistics=false] - Send statistics ping or not + * @returns {Object} initialize option + * @private + */ + _initializeOption(options) { + return snippet.extend( + { + loadImage: { + path: '', + name: '', + }, + locale: {}, + menuIconPath: '', + menu: ['crop', 'flip', 'rotate', 'draw', 'shape', 'icon', 'text', 'mask', 'filter'], + initMenu: '', + uiSize: { + width: '100%', + height: '100%', + }, + menuBarPosition: 'bottom', + }, + options + ); + } + + /** + * Set ui container size + * @param {Object} uiSize - ui dimension + * @param {string} uiSize.width - css width property + * @param {string} uiSize.height - css height property + * @private + */ + _setUiSize(uiSize = this.options.uiSize) { + const elementDimension = this._selectedElement.style; + elementDimension.width = uiSize.width; + elementDimension.height = uiSize.height; + } + + /** + * Make submenu dom element + * @private + */ + _makeSubMenu() { + snippet.forEach(this.options.menu, (menuName) => { + const SubComponentClass = + SUB_UI_COMPONENT[menuName.replace(/^[a-z]/, ($0) => $0.toUpperCase())]; + + // make menu element + this._makeMenuElement(menuName); + + // menu btn element + this._buttonElements[menuName] = this._menuElement.querySelector(`.tie-btn-${menuName}`); + + // submenu ui instance + this[menuName] = new SubComponentClass(this._subMenuElement, { + locale: this._locale, + makeSvgIcon: this.theme.makeMenSvgIconSet.bind(this.theme), + menuBarPosition: this.options.menuBarPosition, + usageStatistics: this.options.usageStatistics, + }); + }); + } + + /** + * Make primary ui dom element + * @param {string|HTMLElement} element - Wrapper's element or selector + * @private + */ + _makeUiElement(element) { + let selectedElement; + + window.snippet = snippet; + + if (element.nodeType) { + selectedElement = element; + } else { + selectedElement = document.querySelector(element); + } + const selector = getSelector(selectedElement); + + selectedElement.classList.add('tui-image-editor-container'); + selectedElement.innerHTML = + controls({ + locale: this._locale, + biImage: this.theme.getStyle('common.bi'), + loadButtonStyle: this.theme.getStyle('loadButton'), + downloadButtonStyle: this.theme.getStyle('downloadButton'), + }) + + mainContainer({ + locale: this._locale, + biImage: this.theme.getStyle('common.bi'), + commonStyle: this.theme.getStyle('common'), + headerStyle: this.theme.getStyle('header'), + loadButtonStyle: this.theme.getStyle('loadButton'), + downloadButtonStyle: this.theme.getStyle('downloadButton'), + submenuStyle: this.theme.getStyle('submenu'), + }); + + this._selectedElement = selectedElement; + this._selectedElement.classList.add(this.options.menuBarPosition); + + this._mainElement = selector('.tui-image-editor-main'); + this._editorElementWrap = selector('.tui-image-editor-wrap'); + this._editorElement = selector('.tui-image-editor'); + this._menuElement = selector('.tui-image-editor-menu'); + this._subMenuElement = selector('.tui-image-editor-submenu'); + this._buttonElements = { + download: this._selectedElement.querySelectorAll('.tui-image-editor-download-btn'), + load: this._selectedElement.querySelectorAll('.tui-image-editor-load-btn'), + }; + + this._addHelpMenus(); + } + + /** + * make array for help menu output, including partitions. + * @returns {Array} + * @private + */ + _makeHelpMenuWithPartition() { + const helpMenuWithPartition = [...HELP_MENUS, '']; + helpMenuWithPartition.splice(3, 0, ''); + + return helpMenuWithPartition; + } + + /** + * Add help menu + * @private + */ + _addHelpMenus() { + const helpMenuWithPartition = this._makeHelpMenuWithPartition(); + + snippet.forEach(helpMenuWithPartition, (menuName) => { + if (!menuName) { + this._makeMenuPartitionElement(); + } else { + this._makeMenuElement(menuName, ['normal', 'disabled', 'hover'], 'help'); + + if (menuName) { + this._buttonElements[menuName] = this._menuElement.querySelector(`.tie-btn-${menuName}`); } - const selector = getSelector(selectedElement); - - selectedElement.classList.add('tui-image-editor-container'); - selectedElement.innerHTML = controls({ - locale: this._locale, - biImage: this.theme.getStyle('common.bi'), - loadButtonStyle: this.theme.getStyle('loadButton'), - downloadButtonStyle: this.theme.getStyle('downloadButton') - }) + - mainContainer({ - locale: this._locale, - biImage: this.theme.getStyle('common.bi'), - commonStyle: this.theme.getStyle('common'), - headerStyle: this.theme.getStyle('header'), - loadButtonStyle: this.theme.getStyle('loadButton'), - downloadButtonStyle: this.theme.getStyle('downloadButton'), - submenuStyle: this.theme.getStyle('submenu') - }); - - this._selectedElement = selectedElement; - this._selectedElement.classList.add(this.options.menuBarPosition); - - this._mainElement = selector('.tui-image-editor-main'); - this._editorElementWrap = selector('.tui-image-editor-wrap'); - this._editorElement = selector('.tui-image-editor'); - this._menuElement = selector('.tui-image-editor-menu'); - this._subMenuElement = selector('.tui-image-editor-submenu'); - this._buttonElements = { - 'download': this._selectedElement.querySelectorAll('.tui-image-editor-download-btn'), - 'load': this._selectedElement.querySelectorAll('.tui-image-editor-load-btn') - }; - - this._addHelpMenus(); - } - - /** - * make array for help menu output, including partitions. - * @returns {Array} - * @private - */ - _makeHelpMenuWithPartition() { - const helpMenuWithPartition = [...HELP_MENUS, '']; - helpMenuWithPartition.splice(3, 0, ''); - - return helpMenuWithPartition; - } - - /** - * Add help menu - * @private - */ - _addHelpMenus() { - const helpMenuWithPartition = this._makeHelpMenuWithPartition(); - - snippet.forEach(helpMenuWithPartition, menuName => { - if (!menuName) { - this._makeMenuPartitionElement(); - } else { - this._makeMenuElement(menuName, ['normal', 'disabled', 'hover'], 'help'); - - if (menuName) { - this._buttonElements[menuName] = this._menuElement.querySelector(`.tie-btn-${menuName}`); - } - } - }); - } - - /** - * Make menu partition element - * @private - */ - _makeMenuPartitionElement() { - const partitionElement = document.createElement('li'); - const partitionInnerElement = document.createElement('div'); - partitionElement.className = cls('item'); - partitionInnerElement.className = cls('icpartition'); - partitionElement.appendChild(partitionInnerElement); - - this._menuElement.appendChild(partitionElement); - } - - /** - * Make menu button element - * @param {string} menuName - menu name - * @param {Array} useIconTypes - Possible values are \['normal', 'active', 'hover', 'disabled'\] - * @param {string} menuType - 'normal' or 'help' - * @private - */ - _makeMenuElement(menuName, useIconTypes = ['normal', 'active', 'hover'], menuType = 'normal') { - const btnElement = document.createElement('li'); - const menuItemHtml = this.theme.makeMenSvgIconSet(useIconTypes, menuName); - - this._addTooltipAttribute(btnElement, menuName); - btnElement.className = `tie-btn-${menuName} ${cls('item')} ${menuType}`; - btnElement.innerHTML = menuItemHtml; - - this._menuElement.appendChild(btnElement); - } - - /** - * Add help action event - * @private - */ - _addHelpActionEvent() { - snippet.forEach(HELP_MENUS, helpName => { - this.eventHandler[helpName] = () => this._actions.main[helpName](); - this._buttonElements[helpName].addEventListener('click', this.eventHandler[helpName]); - }); - } - - /** - * Remove help action event - * @private - */ - _removeHelpActionEvent() { - snippet.forEach(HELP_MENUS, helpName => { - this._buttonElements[helpName].removeEventListener('click', this.eventHandler[helpName]); - }); - } - - /** - * Add attribute for menu tooltip - * @param {HTMLElement} element - menu element - * @param {string} tooltipName - tooltipName - * @private - */ - _addTooltipAttribute(element, tooltipName) { - element.setAttribute('tooltip-content', this._locale.localize(tooltipName.replace(/^[a-z]/g, $0 => $0.toUpperCase()))); - } - - /** - * Add download event - * @private - */ - _addDownloadEvent() { - this.eventHandler.download = () => this._actions.main.download(); - snippet.forEach(this._buttonElements.download, element => { - element.addEventListener('click', this.eventHandler.download); - }); - } - - _removeDownloadEvent() { - snippet.forEach(this._buttonElements.download, element => { - element.removeEventListener('click', this.eventHandler.download); - }); - } - - /** - * Add load event - * @private - */ - _addLoadEvent() { - this.eventHandler.loadImage = event => this._actions.main.load(event.target.files[0]); - - snippet.forEach(this._buttonElements.load, element => { - element.addEventListener('change', this.eventHandler.loadImage); - }); - } - - /** - * Remmove load event - * @private - */ - _removeLoadEvent() { - snippet.forEach(this._buttonElements.load, element => { - element.removeEventListener('change', this.eventHandler.loadImage); - }); - } - - /** - * Add menu event - * @param {string} menuName - menu name - * @private - */ - _addMainMenuEvent(menuName) { - this.eventHandler[menuName] = () => this.changeMenu(menuName); - this._buttonElements[menuName].addEventListener('click', this.eventHandler[menuName]); - } - - /** - * Add menu event - * @param {string} menuName - menu name - * @private - */ - _addSubMenuEvent(menuName) { - this[menuName].addEvent(this._actions[menuName]); - } - - /** - * Add menu event - * @private - */ - _addMenuEvent() { - snippet.forEach(this.options.menu, menuName => { - this._addMainMenuEvent(menuName); - this._addSubMenuEvent(menuName); - }); - } - - /** - * Remove menu event - * @private - */ - _removeMainMenuEvent() { - snippet.forEach(this.options.menu, menuName => { - this._buttonElements[menuName].removeEventListener('click', this.eventHandler[menuName]); - }); - } - - /** - * Get editor area element - * @returns {HTMLElement} editor area html element - * @ignore - */ - getEditorArea() { - return this._editorElement; - } - - /** - * Add event for menu items - * @ignore - */ - activeMenuEvent() { - if (this._initMenuEvent) { - return; - } - - this._addHelpActionEvent(); - this._addDownloadEvent(); - this._addMenuEvent(); - this._initMenu(); - this._initMenuEvent = true; - } - - /** - * Remove ui event - * @private - */ - _removeUiEvent() { - this._removeHelpActionEvent(); - this._removeDownloadEvent(); - this._removeLoadEvent(); - this._removeMainMenuEvent(); - } - - /** - * Destroy all menu instance - * @private - */ - _destroyAllMenu() { - snippet.forEach(this.options.menu, menuName => { - this[menuName].destroy(); - }); - } - - /** - * Init canvas - * @ignore - */ - initCanvas() { - const loadImageInfo = this._getLoadImage(); - if (loadImageInfo.path) { - this._actions.main.initLoadImage(loadImageInfo.path, loadImageInfo.name).then(() => { - this.activeMenuEvent(); - }); - } - - this._addLoadEvent(); - - const gridVisual = document.createElement('div'); - - gridVisual.className = cls('grid-visual'); - const grid = ` + } + }); + } + + /** + * Make menu partition element + * @private + */ + _makeMenuPartitionElement() { + const partitionElement = document.createElement('li'); + const partitionInnerElement = document.createElement('div'); + partitionElement.className = cls('item'); + partitionInnerElement.className = cls('icpartition'); + partitionElement.appendChild(partitionInnerElement); + + this._menuElement.appendChild(partitionElement); + } + + /** + * Make menu button element + * @param {string} menuName - menu name + * @param {Array} useIconTypes - Possible values are \['normal', 'active', 'hover', 'disabled'\] + * @param {string} menuType - 'normal' or 'help' + * @private + */ + _makeMenuElement(menuName, useIconTypes = ['normal', 'active', 'hover'], menuType = 'normal') { + const btnElement = document.createElement('li'); + const menuItemHtml = this.theme.makeMenSvgIconSet(useIconTypes, menuName); + + this._addTooltipAttribute(btnElement, menuName); + btnElement.className = `tie-btn-${menuName} ${cls('item')} ${menuType}`; + btnElement.innerHTML = menuItemHtml; + + this._menuElement.appendChild(btnElement); + } + + /** + * Add help action event + * @private + */ + _addHelpActionEvent() { + snippet.forEach(HELP_MENUS, (helpName) => { + this.eventHandler[helpName] = () => this._actions.main[helpName](); + this._buttonElements[helpName].addEventListener('click', this.eventHandler[helpName]); + }); + } + + /** + * Remove help action event + * @private + */ + _removeHelpActionEvent() { + snippet.forEach(HELP_MENUS, (helpName) => { + this._buttonElements[helpName].removeEventListener('click', this.eventHandler[helpName]); + }); + } + + /** + * Add attribute for menu tooltip + * @param {HTMLElement} element - menu element + * @param {string} tooltipName - tooltipName + * @private + */ + _addTooltipAttribute(element, tooltipName) { + element.setAttribute( + 'tooltip-content', + this._locale.localize(tooltipName.replace(/^[a-z]/g, ($0) => $0.toUpperCase())) + ); + } + + /** + * Add download event + * @private + */ + _addDownloadEvent() { + this.eventHandler.download = () => this._actions.main.download(); + snippet.forEach(this._buttonElements.download, (element) => { + element.addEventListener('click', this.eventHandler.download); + }); + } + + _removeDownloadEvent() { + snippet.forEach(this._buttonElements.download, (element) => { + element.removeEventListener('click', this.eventHandler.download); + }); + } + + /** + * Add load event + * @private + */ + _addLoadEvent() { + this.eventHandler.loadImage = (event) => this._actions.main.load(event.target.files[0]); + + snippet.forEach(this._buttonElements.load, (element) => { + element.addEventListener('change', this.eventHandler.loadImage); + }); + } + + /** + * Remmove load event + * @private + */ + _removeLoadEvent() { + snippet.forEach(this._buttonElements.load, (element) => { + element.removeEventListener('change', this.eventHandler.loadImage); + }); + } + + /** + * Add menu event + * @param {string} menuName - menu name + * @private + */ + _addMainMenuEvent(menuName) { + this.eventHandler[menuName] = () => this.changeMenu(menuName); + this._buttonElements[menuName].addEventListener('click', this.eventHandler[menuName]); + } + + /** + * Add menu event + * @param {string} menuName - menu name + * @private + */ + _addSubMenuEvent(menuName) { + this[menuName].addEvent(this._actions[menuName]); + } + + /** + * Add menu event + * @private + */ + _addMenuEvent() { + snippet.forEach(this.options.menu, (menuName) => { + this._addMainMenuEvent(menuName); + this._addSubMenuEvent(menuName); + }); + } + + /** + * Remove menu event + * @private + */ + _removeMainMenuEvent() { + snippet.forEach(this.options.menu, (menuName) => { + this._buttonElements[menuName].removeEventListener('click', this.eventHandler[menuName]); + }); + } + + /** + * Get editor area element + * @returns {HTMLElement} editor area html element + * @ignore + */ + getEditorArea() { + return this._editorElement; + } + + /** + * Add event for menu items + * @ignore + */ + activeMenuEvent() { + if (this._initMenuEvent) { + return; + } + + this._addHelpActionEvent(); + this._addDownloadEvent(); + this._addMenuEvent(); + this._initMenu(); + this._initMenuEvent = true; + } + + /** + * Remove ui event + * @private + */ + _removeUiEvent() { + this._removeHelpActionEvent(); + this._removeDownloadEvent(); + this._removeLoadEvent(); + this._removeMainMenuEvent(); + } + + /** + * Destroy all menu instance + * @private + */ + _destroyAllMenu() { + snippet.forEach(this.options.menu, (menuName) => { + this[menuName].destroy(); + }); + } + + /** + * Init canvas + * @ignore + */ + initCanvas() { + const loadImageInfo = this._getLoadImage(); + if (loadImageInfo.path) { + this._actions.main.initLoadImage(loadImageInfo.path, loadImageInfo.name).then(() => { + this.activeMenuEvent(); + }); + } + + this._addLoadEvent(); + + const gridVisual = document.createElement('div'); + + gridVisual.className = cls('grid-visual'); + const grid = `
`; - gridVisual.innerHTML = grid; - this._editorContainerElement = this._editorElement.querySelector('.tui-image-editor-canvas-container'); - this._editorContainerElement.appendChild(gridVisual); - } - - /** - * get editor area element - * @returns {Object} load image option - * @private - */ - _getLoadImage() { - return this.options.loadImage; - } - - /** - * change menu - * @param {string} menuName - menu name - * @param {boolean} toggle - whether toogle or not - * @param {boolean} discardSelection - discard selection - * @ignore - */ - changeMenu(menuName, toggle = true, discardSelection = true) { - if (!this._submenuChangeTransection) { - this._submenuChangeTransection = true; - this._changeMenu(menuName, toggle, discardSelection); - this._submenuChangeTransection = false; - } - } - - /** - * change menu - * @param {string} menuName - menu name - * @param {boolean} toggle - whether toogle or not - * @param {boolean} discardSelection - discard selection - * @private - */ - _changeMenu(menuName, toggle, discardSelection) { - if (this.submenu) { - this._buttonElements[this.submenu].classList.remove('active'); - this._mainElement.classList.remove(`tui-image-editor-menu-${this.submenu}`); - if (discardSelection) { - this._actions.main.discardSelection(); - } - this._actions.main.changeSelectableAll(true); - this[this.submenu].changeStandbyMode(); - } - - if (this.submenu === menuName && toggle) { - this.submenu = null; + gridVisual.innerHTML = grid; + this._editorContainerElement = this._editorElement.querySelector( + '.tui-image-editor-canvas-container' + ); + this._editorContainerElement.appendChild(gridVisual); + } + + /** + * get editor area element + * @returns {Object} load image option + * @private + */ + _getLoadImage() { + return this.options.loadImage; + } + + /** + * change menu + * @param {string} menuName - menu name + * @param {boolean} toggle - whether toogle or not + * @param {boolean} discardSelection - discard selection + * @ignore + */ + changeMenu(menuName, toggle = true, discardSelection = true) { + if (!this._submenuChangeTransection) { + this._submenuChangeTransection = true; + this._changeMenu(menuName, toggle, discardSelection); + this._submenuChangeTransection = false; + } + } + + /** + * change menu + * @param {string} menuName - menu name + * @param {boolean} toggle - whether toogle or not + * @param {boolean} discardSelection - discard selection + * @private + */ + _changeMenu(menuName, toggle, discardSelection) { + if (this.submenu) { + this._buttonElements[this.submenu].classList.remove('active'); + this._mainElement.classList.remove(`tui-image-editor-menu-${this.submenu}`); + if (discardSelection) { + this._actions.main.discardSelection(); + } + this._actions.main.changeSelectableAll(true); + this[this.submenu].changeStandbyMode(); + } + + if (this.submenu === menuName && toggle) { + this.submenu = null; + } else { + this._buttonElements[menuName].classList.add('active'); + this._mainElement.classList.add(`tui-image-editor-menu-${menuName}`); + this.submenu = menuName; + this[this.submenu].changeStartMode(); + } + + this.resizeEditor(); + } + + /** + * Init menu + * @private + */ + _initMenu() { + if (this.options.initMenu) { + const evt = document.createEvent('MouseEvents'); + evt.initEvent('click', true, false); + this._buttonElements[this.options.initMenu].dispatchEvent(evt); + } + + if (this.icon) { + this.icon.registDefaultIcon(); + } + } + + /** + * Get canvas max Dimension + * @returns {Object} - width & height of editor + * @private + */ + _getCanvasMaxDimension() { + const { maxWidth, maxHeight } = this._editorContainerElement.style; + const width = parseFloat(maxWidth); + const height = parseFloat(maxHeight); + + return { + width, + height, + }; + } + + /** + * Set editor position + * @param {string} menuBarPosition - top or right or bottom or left + * @private + */ + _setEditorPosition(menuBarPosition) { + // eslint-disable-line complexity + const { width, height } = this._getCanvasMaxDimension(); + const editorElementStyle = this._editorElement.style; + let top = 0; + let left = 0; + + if (this.submenu) { + if (menuBarPosition === 'bottom') { + if (height > this._editorElementWrap.scrollHeight - 150) { + top = (height - this._editorElementWrap.scrollHeight) / 2; } else { - this._buttonElements[menuName].classList.add('active'); - this._mainElement.classList.add(`tui-image-editor-menu-${menuName}`); - this.submenu = menuName; - this[this.submenu].changeStartMode(); + top = (150 / 2) * -1; } - - this.resizeEditor(); - } - - /** - * Init menu - * @private - */ - _initMenu() { - if (this.options.initMenu) { - const evt = document.createEvent('MouseEvents'); - evt.initEvent('click', true, false); - this._buttonElements[this.options.initMenu].dispatchEvent(evt); + } else if (menuBarPosition === 'top') { + if (height > this._editorElementWrap.offsetHeight - 150) { + top = 150 / 2 - (height - (this._editorElementWrap.offsetHeight - 150)) / 2; + } else { + top = 150 / 2; } - - if (this.icon) { - this.icon.registDefaultIcon(); + } else if (menuBarPosition === 'left') { + if (width > this._editorElementWrap.offsetWidth - 248) { + left = 248 / 2 - (width - (this._editorElementWrap.offsetWidth - 248)) / 2; + } else { + left = 248 / 2; } - } - - /** - * Get canvas max Dimension - * @returns {Object} - width & height of editor - * @private - */ - _getCanvasMaxDimension() { - const {maxWidth, maxHeight} = this._editorContainerElement.style; - const width = parseFloat(maxWidth); - const height = parseFloat(maxHeight); - - return { - width, - height - }; - } - - /** - * Set editor position - * @param {string} menuBarPosition - top or right or bottom or left - * @private - */ - _setEditorPosition(menuBarPosition) { // eslint-disable-line complexity - const {width, height} = this._getCanvasMaxDimension(); - const editorElementStyle = this._editorElement.style; - let top = 0; - let left = 0; - - if (this.submenu) { - if (menuBarPosition === 'bottom') { - if (height > this._editorElementWrap.scrollHeight - 150) { - top = (height - this._editorElementWrap.scrollHeight) / 2; - } else { - top = (150 / 2) * -1; - } - } else if (menuBarPosition === 'top') { - if (height > this._editorElementWrap.offsetHeight - 150) { - top = (150 / 2) - ((height - (this._editorElementWrap.offsetHeight - 150)) / 2); - } else { - top = 150 / 2; - } - } else if (menuBarPosition === 'left') { - if (width > this._editorElementWrap.offsetWidth - 248) { - left = (248 / 2) - ((width - (this._editorElementWrap.offsetWidth - 248)) / 2); - } else { - left = 248 / 2; - } - } else if (menuBarPosition === 'right') { - if (width > this._editorElementWrap.scrollWidth - 248) { - left = (width - this._editorElementWrap.scrollWidth) / 2; - } else { - left = (248 / 2) * -1; - } - } + } else if (menuBarPosition === 'right') { + if (width > this._editorElementWrap.scrollWidth - 248) { + left = (width - this._editorElementWrap.scrollWidth) / 2; + } else { + left = (248 / 2) * -1; } - editorElementStyle.top = `${top}px`; - editorElementStyle.left = `${left}px`; + } } + editorElementStyle.top = `${top}px`; + editorElementStyle.left = `${left}px`; + } } export default Ui; diff --git a/src/js/ui/crop.js b/src/js/ui/crop.js index 3f78be462..538048092 100644 --- a/src/js/ui/crop.js +++ b/src/js/ui/crop.js @@ -1,6 +1,6 @@ import snippet from 'tui-code-snippet'; import Submenu from './submenuBase'; -import {assignmentForDestroy} from '../util'; +import { assignmentForDestroy } from '../util'; import templateHtml from './template/submenu/crop'; /** @@ -9,131 +9,131 @@ import templateHtml from './template/submenu/crop'; * @ignore */ class Crop extends Submenu { - constructor(subMenuElement, {locale, makeSvgIcon, menuBarPosition, usageStatistics}) { - super(subMenuElement, { - locale, - name: 'crop', - makeSvgIcon, - menuBarPosition, - templateHtml, - usageStatistics - }); - - this.status = 'active'; - - this._els = { - apply: this.selector('.tie-crop-button .apply'), - cancel: this.selector('.tie-crop-button .cancel'), - preset: this.selector('.tie-crop-preset-button') - }; - - this.defaultPresetButton = this._els.preset.querySelector('.preset-none'); + constructor(subMenuElement, { locale, makeSvgIcon, menuBarPosition, usageStatistics }) { + super(subMenuElement, { + locale, + name: 'crop', + makeSvgIcon, + menuBarPosition, + templateHtml, + usageStatistics, + }); + + this.status = 'active'; + + this._els = { + apply: this.selector('.tie-crop-button .apply'), + cancel: this.selector('.tie-crop-button .cancel'), + preset: this.selector('.tie-crop-preset-button'), + }; + + this.defaultPresetButton = this._els.preset.querySelector('.preset-none'); + } + + /** + * Destroys the instance. + */ + destroy() { + this._removeEvent(); + + assignmentForDestroy(this); + } + + /** + * Add event for crop + * @param {Object} actions - actions for crop + * @param {Function} actions.crop - crop action + * @param {Function} actions.cancel - cancel action + * @param {Function} actions.preset - draw rectzone at a predefined ratio + */ + addEvent(actions) { + const apply = this._applyEventHandler.bind(this); + const cancel = this._cancelEventHandler.bind(this); + const cropzonePreset = this._cropzonePresetEventHandler.bind(this); + + this.eventHandler = { + apply, + cancel, + cropzonePreset, + }; + + this.actions = actions; + this._els.apply.addEventListener('click', apply); + this._els.cancel.addEventListener('click', cancel); + this._els.preset.addEventListener('click', cropzonePreset); + } + + /** + * Remove event + * @private + */ + _removeEvent() { + this._els.apply.removeEventListener('click', this.eventHandler.apply); + this._els.cancel.removeEventListener('click', this.eventHandler.cancel); + this._els.preset.removeEventListener('click', this.eventHandler.cropzonePreset); + } + + _applyEventHandler() { + this.actions.crop(); + this._els.apply.classList.remove('active'); + } + + _cancelEventHandler() { + this.actions.cancel(); + this._els.apply.classList.remove('active'); + } + + _cropzonePresetEventHandler(event) { + const button = event.target.closest('.tui-image-editor-button.preset'); + if (button) { + const [presetType] = button.className.match(/preset-[^\s]+/); + + this._setPresetButtonActive(button); + this.actions.preset(presetType); } - - /** - * Destroys the instance. - */ - destroy() { - this._removeEvent(); - - assignmentForDestroy(this); - } - - /** - * Add event for crop - * @param {Object} actions - actions for crop - * @param {Function} actions.crop - crop action - * @param {Function} actions.cancel - cancel action - * @param {Function} actions.preset - draw rectzone at a predefined ratio - */ - addEvent(actions) { - const apply = this._applyEventHandler.bind(this); - const cancel = this._cancelEventHandler.bind(this); - const cropzonePreset = this._cropzonePresetEventHandler.bind(this); - - this.eventHandler = { - apply, - cancel, - cropzonePreset - }; - - this.actions = actions; - this._els.apply.addEventListener('click', apply); - this._els.cancel.addEventListener('click', cancel); - this._els.preset.addEventListener('click', cropzonePreset); - } - - /** - * Remove event - * @private - */ - _removeEvent() { - this._els.apply.removeEventListener('click', this.eventHandler.apply); - this._els.cancel.removeEventListener('click', this.eventHandler.cancel); - this._els.preset.removeEventListener('click', this.eventHandler.cropzonePreset); - } - - _applyEventHandler() { - this.actions.crop(); - this._els.apply.classList.remove('active'); - } - - _cancelEventHandler() { - this.actions.cancel(); - this._els.apply.classList.remove('active'); + } + + /** + * Executed when the menu starts. + */ + changeStartMode() { + this.actions.modeChange('crop'); + } + + /** + * Returns the menu to its default state. + */ + changeStandbyMode() { + this.actions.stopDrawingMode(); + this._setPresetButtonActive(); + } + + /** + * Change apply button status + * @param {Boolean} enableStatus - apply button status + */ + changeApplyButtonStatus(enableStatus) { + if (enableStatus) { + this._els.apply.classList.add('active'); + } else { + this._els.apply.classList.remove('active'); } - - _cropzonePresetEventHandler(event) { - const button = event.target.closest('.tui-image-editor-button.preset'); - if (button) { - const [presetType] = button.className.match(/preset-[^\s]+/); - - this._setPresetButtonActive(button); - this.actions.preset(presetType); - } - } - - /** - * Executed when the menu starts. - */ - changeStartMode() { - this.actions.modeChange('crop'); - } - - /** - * Returns the menu to its default state. - */ - changeStandbyMode() { - this.actions.stopDrawingMode(); - this._setPresetButtonActive(); - } - - /** - * Change apply button status - * @param {Boolean} enableStatus - apply button status - */ - changeApplyButtonStatus(enableStatus) { - if (enableStatus) { - this._els.apply.classList.add('active'); - } else { - this._els.apply.classList.remove('active'); - } - } - - /** - * Set preset button to active status - * @param {HTMLElement} button - event target element - * @private - */ - _setPresetButtonActive(button = this.defaultPresetButton) { - snippet.forEach([].slice.call(this._els.preset.querySelectorAll('.preset')), presetButton => { - presetButton.classList.remove('active'); - }); - - if (button) { - button.classList.add('active'); - } + } + + /** + * Set preset button to active status + * @param {HTMLElement} button - event target element + * @private + */ + _setPresetButtonActive(button = this.defaultPresetButton) { + snippet.forEach([].slice.call(this._els.preset.querySelectorAll('.preset')), (presetButton) => { + presetButton.classList.remove('active'); + }); + + if (button) { + button.classList.add('active'); } + } } export default Crop; diff --git a/src/js/ui/draw.js b/src/js/ui/draw.js index 818763e43..13223cd1a 100644 --- a/src/js/ui/draw.js +++ b/src/js/ui/draw.js @@ -1,9 +1,9 @@ -import {assignmentForDestroy, getRgb} from '../util'; +import { assignmentForDestroy, getRgb } from '../util'; import Colorpicker from './tools/colorpicker'; import Range from './tools/range'; import Submenu from './submenuBase'; import templateHtml from './template/submenu/draw'; -import {defaultDrawRangeValus} from '../consts'; +import { defaultDrawRangeValus } from '../consts'; const DRAW_OPACITY = 0.7; /** @@ -12,148 +12,154 @@ const DRAW_OPACITY = 0.7; * @ignore */ class Draw extends Submenu { - constructor(subMenuElement, {locale, makeSvgIcon, menuBarPosition, usageStatistics}) { - super(subMenuElement, { - locale, - name: 'draw', - makeSvgIcon, - menuBarPosition, - templateHtml, - usageStatistics - }); - - this._els = { - lineSelectButton: this.selector('.tie-draw-line-select-button'), - drawColorPicker: new Colorpicker( - this.selector('.tie-draw-color'), '#00a9ff', this.toggleDirection, this.usageStatistics - ), - drawRange: new Range({ - slider: this.selector('.tie-draw-range'), - input: this.selector('.tie-draw-range-value') - }, defaultDrawRangeValus) - }; - - this.type = null; - this.color = this._els.drawColorPicker.color; - this.width = this._els.drawRange.value; + constructor(subMenuElement, { locale, makeSvgIcon, menuBarPosition, usageStatistics }) { + super(subMenuElement, { + locale, + name: 'draw', + makeSvgIcon, + menuBarPosition, + templateHtml, + usageStatistics, + }); + + this._els = { + lineSelectButton: this.selector('.tie-draw-line-select-button'), + drawColorPicker: new Colorpicker( + this.selector('.tie-draw-color'), + '#00a9ff', + this.toggleDirection, + this.usageStatistics + ), + drawRange: new Range( + { + slider: this.selector('.tie-draw-range'), + input: this.selector('.tie-draw-range-value'), + }, + defaultDrawRangeValus + ), + }; + + this.type = null; + this.color = this._els.drawColorPicker.color; + this.width = this._els.drawRange.value; + } + + /** + * Destroys the instance. + */ + destroy() { + this._removeEvent(); + this._els.drawColorPicker.destroy(); + this._els.drawRange.destroy(); + + assignmentForDestroy(this); + } + + /** + * Add event for draw + * @param {Object} actions - actions for crop + * @param {Function} actions.setDrawMode - set draw mode + */ + addEvent(actions) { + this.eventHandler.changeDrawType = this._changeDrawType.bind(this); + + this.actions = actions; + this._els.lineSelectButton.addEventListener('click', this.eventHandler.changeDrawType); + this._els.drawColorPicker.on('change', this._changeDrawColor.bind(this)); + this._els.drawRange.on('change', this._changeDrawRange.bind(this)); + } + + /** + * Remove event + * @private + */ + _removeEvent() { + this._els.lineSelectButton.removeEventListener('click', this.eventHandler.changeDrawType); + this._els.drawColorPicker.off(); + this._els.drawRange.off(); + } + + /** + * set draw mode - action runner + */ + setDrawMode() { + this.actions.setDrawMode(this.type, { + width: this.width, + color: getRgb(this.color, DRAW_OPACITY), + }); + } + + /** + * Returns the menu to its default state. + */ + changeStandbyMode() { + this.type = null; + this.actions.stopDrawingMode(); + this.actions.changeSelectableAll(true); + this._els.lineSelectButton.classList.remove('free'); + this._els.lineSelectButton.classList.remove('line'); + } + + /** + * Executed when the menu starts. + */ + changeStartMode() { + this.type = 'free'; + this._els.lineSelectButton.classList.add('free'); + this.setDrawMode(); + } + + /** + * Change draw type event + * @param {object} event - line select event + * @private + */ + _changeDrawType(event) { + const button = event.target.closest('.tui-image-editor-button'); + if (button) { + const lineType = this.getButtonType(button, ['free', 'line']); + this.actions.discardSelection(); + + if (this.type === lineType) { + this.changeStandbyMode(); + + return; + } + + this.changeStandbyMode(); + this.type = lineType; + this._els.lineSelectButton.classList.add(lineType); + this.setDrawMode(); } - - /** - * Destroys the instance. - */ - destroy() { - this._removeEvent(); - this._els.drawColorPicker.destroy(); - this._els.drawRange.destroy(); - - assignmentForDestroy(this); + } + + /** + * Change drawing color + * @param {string} color - select drawing color + * @private + */ + _changeDrawColor(color) { + this.color = color || 'transparent'; + if (!this.type) { + this.changeStartMode(); + } else { + this.setDrawMode(); } - - /** - * Add event for draw - * @param {Object} actions - actions for crop - * @param {Function} actions.setDrawMode - set draw mode - */ - addEvent(actions) { - this.eventHandler.changeDrawType = this._changeDrawType.bind(this); - - this.actions = actions; - this._els.lineSelectButton.addEventListener('click', this.eventHandler.changeDrawType); - this._els.drawColorPicker.on('change', this._changeDrawColor.bind(this)); - this._els.drawRange.on('change', this._changeDrawRange.bind(this)); - } - - /** - * Remove event - * @private - */ - _removeEvent() { - this._els.lineSelectButton.removeEventListener('click', this.eventHandler.changeDrawType); - this._els.drawColorPicker.off(); - this._els.drawRange.off(); - } - - /** - * set draw mode - action runner - */ - setDrawMode() { - this.actions.setDrawMode(this.type, { - width: this.width, - color: getRgb(this.color, DRAW_OPACITY) - }); - } - - /** - * Returns the menu to its default state. - */ - changeStandbyMode() { - this.type = null; - this.actions.stopDrawingMode(); - this.actions.changeSelectableAll(true); - this._els.lineSelectButton.classList.remove('free'); - this._els.lineSelectButton.classList.remove('line'); - } - - /** - * Executed when the menu starts. - */ - changeStartMode() { - this.type = 'free'; - this._els.lineSelectButton.classList.add('free'); - this.setDrawMode(); - } - - /** - * Change draw type event - * @param {object} event - line select event - * @private - */ - _changeDrawType(event) { - const button = event.target.closest('.tui-image-editor-button'); - if (button) { - const lineType = this.getButtonType(button, ['free', 'line']); - this.actions.discardSelection(); - - if (this.type === lineType) { - this.changeStandbyMode(); - - return; - } - - this.changeStandbyMode(); - this.type = lineType; - this._els.lineSelectButton.classList.add(lineType); - this.setDrawMode(); - } - } - - /** - * Change drawing color - * @param {string} color - select drawing color - * @private - */ - _changeDrawColor(color) { - this.color = color || 'transparent'; - if (!this.type) { - this.changeStartMode(); - } else { - this.setDrawMode(); - } - } - - /** - * Change drawing Range - * @param {number} value - select drawing range - * @private - */ - _changeDrawRange(value) { - this.width = value; - if (!this.type) { - this.changeStartMode(); - } else { - this.setDrawMode(); - } + } + + /** + * Change drawing Range + * @param {number} value - select drawing range + * @private + */ + _changeDrawRange(value) { + this.width = value; + if (!this.type) { + this.changeStartMode(); + } else { + this.setDrawMode(); } + } } export default Draw; diff --git a/src/js/ui/filter.js b/src/js/ui/filter.js index 351bc8d6c..3ca188710 100644 --- a/src/js/ui/filter.js +++ b/src/js/ui/filter.js @@ -3,65 +3,61 @@ import Colorpicker from './tools/colorpicker'; import Range from './tools/range'; import Submenu from './submenuBase'; import templateHtml from './template/submenu/filter'; -import {toInteger, toCamelCase, assignmentForDestroy} from '../util'; -import {defaultFilterRangeValus as FILTER_RANGE} from '../consts'; +import { toInteger, toCamelCase, assignmentForDestroy } from '../util'; +import { defaultFilterRangeValus as FILTER_RANGE } from '../consts'; const PICKER_CONTROL_HEIGHT = '130px'; const BLEND_OPTIONS = ['add', 'diff', 'subtract', 'multiply', 'screen', 'lighten', 'darken']; const FILTER_OPTIONS = [ - 'grayscale', - 'invert', - 'sepia', - 'vintage', - 'blur', - 'sharpen', - 'emboss', - 'remove-white', - 'brightness', - 'noise', - 'pixelate', - 'color-filter', - 'tint', - 'multiply', - 'blend' + 'grayscale', + 'invert', + 'sepia', + 'vintage', + 'blur', + 'sharpen', + 'emboss', + 'remove-white', + 'brightness', + 'noise', + 'pixelate', + 'color-filter', + 'tint', + 'multiply', + 'blend', ]; const filterNameMap = { - grayscale: 'grayscale', - invert: 'invert', - sepia: 'sepia', - blur: 'blur', - sharpen: 'sharpen', - emboss: 'emboss', - removeWhite: 'removeColor', - brightness: 'brightness', - contrast: 'contrast', - saturation: 'saturation', - vintage: 'vintage', - polaroid: 'polaroid', - noise: 'noise', - pixelate: 'pixelate', - colorFilter: 'removeColor', - tint: 'blendColor', - multiply: 'blendColor', - blend: 'blendColor', - hue: 'hue', - gamma: 'gamma' + grayscale: 'grayscale', + invert: 'invert', + sepia: 'sepia', + blur: 'blur', + sharpen: 'sharpen', + emboss: 'emboss', + removeWhite: 'removeColor', + brightness: 'brightness', + contrast: 'contrast', + saturation: 'saturation', + vintage: 'vintage', + polaroid: 'polaroid', + noise: 'noise', + pixelate: 'pixelate', + colorFilter: 'removeColor', + tint: 'blendColor', + multiply: 'blendColor', + blend: 'blendColor', + hue: 'hue', + gamma: 'gamma', }; const RANGE_INSTANCE_NAMES = [ - 'removewhiteDistanceRange', - 'colorfilterThresholeRange', - 'pixelateRange', - 'noiseRange', - 'brightnessRange', - 'tintOpacity' -]; -const COLORPICKER_INSTANCE_NAMES = [ - 'filterBlendColor', - 'filterMultiplyColor', - 'filterTintColor' + 'removewhiteDistanceRange', + 'colorfilterThresholeRange', + 'pixelateRange', + 'noiseRange', + 'brightnessRange', + 'tintOpacity', ]; +const COLORPICKER_INSTANCE_NAMES = ['filterBlendColor', 'filterMultiplyColor', 'filterTintColor']; /** * Filter ui class @@ -69,392 +65,401 @@ const COLORPICKER_INSTANCE_NAMES = [ * @ignore */ class Filter extends Submenu { - constructor(subMenuElement, {locale, menuBarPosition, usageStatistics}) { - super(subMenuElement, { - locale, - name: 'filter', - menuBarPosition, - templateHtml, - usageStatistics - }); - - this.selectBoxShow = false; - - this.checkedMap = {}; - this._makeControlElement(); - } - - /** - * Destroys the instance. - */ - destroy() { - this._removeEvent(); - this._destroyToolInstance(); - - assignmentForDestroy(this); - } - - /** - * Remove event for filter - */ - _removeEvent() { - snippet.forEach(FILTER_OPTIONS, filter => { - const filterCheckElement = this.selector(`.tie-${filter}`); - const filterNameCamelCase = toCamelCase(filter); - - filterCheckElement.removeEventListener('change', this.eventHandler[filterNameCamelCase]); - }); - - snippet.forEach([...RANGE_INSTANCE_NAMES, ...COLORPICKER_INSTANCE_NAMES], instanceName => { - this._els[instanceName].off(); - }); - - this._els.blendType.removeEventListener('change', this.eventHandler.changeBlendFilter); - this._els.blendType.removeEventListener('click', this.eventHandler.changeBlendFilter); - } - - _destroyToolInstance() { - snippet.forEach([...RANGE_INSTANCE_NAMES, ...COLORPICKER_INSTANCE_NAMES], instanceName => { - this._els[instanceName].destroy(); - }); - } - - /** - * Add event for filter - * @param {Object} actions - actions for crop - * @param {Function} actions.applyFilter - apply filter option - */ - addEvent({applyFilter}) { - const changeFilterState = filterName => this._changeFilterState.bind(this, applyFilter, filterName); - const changeFilterStateForRange = - filterName => (value, isLast) => this._changeFilterState(applyFilter, filterName, isLast); - - this.eventHandler = { - changeBlendFilter: changeFilterState('blend'), - blandTypeClick: event => event.stopPropagation() - }; - - snippet.forEach(FILTER_OPTIONS, filter => { - const filterCheckElement = this.selector(`.tie-${filter}`); - const filterNameCamelCase = toCamelCase(filter); - this.checkedMap[filterNameCamelCase] = filterCheckElement; - this.eventHandler[filterNameCamelCase] = changeFilterState(filterNameCamelCase); - - filterCheckElement.addEventListener('change', this.eventHandler[filterNameCamelCase]); - }); - - this._els.removewhiteDistanceRange.on('change', changeFilterStateForRange('removeWhite')); - this._els.colorfilterThresholeRange.on('change', changeFilterStateForRange('colorFilter')); - this._els.pixelateRange.on('change', changeFilterStateForRange('pixelate')); - this._els.noiseRange.on('change', changeFilterStateForRange('noise')); - this._els.brightnessRange.on('change', changeFilterStateForRange('brightness')); - - this._els.filterBlendColor.on('change', this.eventHandler.changeBlendFilter); - this._els.filterMultiplyColor.on('change', changeFilterState('multiply')); - this._els.filterTintColor.on('change', changeFilterState('tint')); - this._els.tintOpacity.on('change', changeFilterStateForRange('tint')); - this._els.filterMultiplyColor.on('changeShow', this.colorPickerChangeShow.bind(this)); - this._els.filterTintColor.on('changeShow', this.colorPickerChangeShow.bind(this)); - this._els.filterBlendColor.on('changeShow', this.colorPickerChangeShow.bind(this)); - - this._els.blendType.addEventListener('change', this.eventHandler.changeBlendFilter); - this._els.blendType.addEventListener('click', this.eventHandler.blandTypeClick); - } - - /** - * Set filter for undo changed - * @param {Object} chagedFilterInfos - changed command infos - * @param {string} type - filter type - * @param {string} action - add or remove - * @param {Object} options - filter options - */ - setFilterState(chagedFilterInfos) { - const {type, options, action} = chagedFilterInfos; - const filterName = this._getFilterNameFromOptions(type, options); - const isRemove = action === 'remove'; - - if (!isRemove) { - this._setFilterState(filterName, options); - } - - this.checkedMap[filterName].checked = !isRemove; - } - - /** - * Set filter for undo changed - * @param {string} filterName - filter name - * @param {Object} options - filter options - * @private - */ - _setFilterState(filterName, options) { // eslint-disable-line - if (filterName === 'colorFilter') { - this._els.colorfilterThresholeRange.value = options.distance; - } else if (filterName === 'removeWhite') { - this._els.removewhiteDistanceRange.value = options.distance; - } else if (filterName === 'pixelate') { - this._els.pixelateRange.value = options.blocksize; - } else if (filterName === 'brightness') { - this._els.brightnessRange.value = options.brightness; - } else if (filterName === 'noise') { - this._els.noiseRange.value = options.noise; - } else if (filterName === 'tint') { - this._els.tintOpacity.value = options.alpha; - this._els.filterTintColor.color = options.color; - } else if (filterName === 'blend') { - this._els.filterBlendColor.color = options.color; - } else if (filterName === 'multiply') { - this._els.filterMultiplyColor.color = options.color; - } + constructor(subMenuElement, { locale, menuBarPosition, usageStatistics }) { + super(subMenuElement, { + locale, + name: 'filter', + menuBarPosition, + templateHtml, + usageStatistics, + }); + + this.selectBoxShow = false; + + this.checkedMap = {}; + this._makeControlElement(); + } + + /** + * Destroys the instance. + */ + destroy() { + this._removeEvent(); + this._destroyToolInstance(); + + assignmentForDestroy(this); + } + + /** + * Remove event for filter + */ + _removeEvent() { + snippet.forEach(FILTER_OPTIONS, (filter) => { + const filterCheckElement = this.selector(`.tie-${filter}`); + const filterNameCamelCase = toCamelCase(filter); + + filterCheckElement.removeEventListener('change', this.eventHandler[filterNameCamelCase]); + }); + + snippet.forEach([...RANGE_INSTANCE_NAMES, ...COLORPICKER_INSTANCE_NAMES], (instanceName) => { + this._els[instanceName].off(); + }); + + this._els.blendType.removeEventListener('change', this.eventHandler.changeBlendFilter); + this._els.blendType.removeEventListener('click', this.eventHandler.changeBlendFilter); + } + + _destroyToolInstance() { + snippet.forEach([...RANGE_INSTANCE_NAMES, ...COLORPICKER_INSTANCE_NAMES], (instanceName) => { + this._els[instanceName].destroy(); + }); + } + + /** + * Add event for filter + * @param {Object} actions - actions for crop + * @param {Function} actions.applyFilter - apply filter option + */ + addEvent({ applyFilter }) { + const changeFilterState = (filterName) => + this._changeFilterState.bind(this, applyFilter, filterName); + const changeFilterStateForRange = (filterName) => (value, isLast) => + this._changeFilterState(applyFilter, filterName, isLast); + + this.eventHandler = { + changeBlendFilter: changeFilterState('blend'), + blandTypeClick: (event) => event.stopPropagation(), + }; + + snippet.forEach(FILTER_OPTIONS, (filter) => { + const filterCheckElement = this.selector(`.tie-${filter}`); + const filterNameCamelCase = toCamelCase(filter); + this.checkedMap[filterNameCamelCase] = filterCheckElement; + this.eventHandler[filterNameCamelCase] = changeFilterState(filterNameCamelCase); + + filterCheckElement.addEventListener('change', this.eventHandler[filterNameCamelCase]); + }); + + this._els.removewhiteDistanceRange.on('change', changeFilterStateForRange('removeWhite')); + this._els.colorfilterThresholeRange.on('change', changeFilterStateForRange('colorFilter')); + this._els.pixelateRange.on('change', changeFilterStateForRange('pixelate')); + this._els.noiseRange.on('change', changeFilterStateForRange('noise')); + this._els.brightnessRange.on('change', changeFilterStateForRange('brightness')); + + this._els.filterBlendColor.on('change', this.eventHandler.changeBlendFilter); + this._els.filterMultiplyColor.on('change', changeFilterState('multiply')); + this._els.filterTintColor.on('change', changeFilterState('tint')); + this._els.tintOpacity.on('change', changeFilterStateForRange('tint')); + this._els.filterMultiplyColor.on('changeShow', this.colorPickerChangeShow.bind(this)); + this._els.filterTintColor.on('changeShow', this.colorPickerChangeShow.bind(this)); + this._els.filterBlendColor.on('changeShow', this.colorPickerChangeShow.bind(this)); + + this._els.blendType.addEventListener('change', this.eventHandler.changeBlendFilter); + this._els.blendType.addEventListener('click', this.eventHandler.blandTypeClick); + } + + /** + * Set filter for undo changed + * @param {Object} chagedFilterInfos - changed command infos + * @param {string} type - filter type + * @param {string} action - add or remove + * @param {Object} options - filter options + */ + setFilterState(chagedFilterInfos) { + const { type, options, action } = chagedFilterInfos; + const filterName = this._getFilterNameFromOptions(type, options); + const isRemove = action === 'remove'; + + if (!isRemove) { + this._setFilterState(filterName, options); } - /** - * Get filter name - * @param {string} type - filter type - * @param {Object} options - filter options - * @returns {string} filter name - * @private - */ - _getFilterNameFromOptions(type, options) { - let filterName = type; - - if (type === 'removeColor') { - filterName = snippet.isExisty(options.useAlpha) ? 'removeWhite' : 'colorFilter'; - } else if (type === 'blendColor') { - filterName = { - add: 'blend', - multiply: 'multiply', - tint: 'tint' - }[options.mode]; - } - - return filterName; + this.checkedMap[filterName].checked = !isRemove; + } + + /** + * Set filter for undo changed + * @param {string} filterName - filter name + * @param {Object} options - filter options + * @private + */ + _setFilterState(filterName, options) { + // eslint-disable-line + if (filterName === 'colorFilter') { + this._els.colorfilterThresholeRange.value = options.distance; + } else if (filterName === 'removeWhite') { + this._els.removewhiteDistanceRange.value = options.distance; + } else if (filterName === 'pixelate') { + this._els.pixelateRange.value = options.blocksize; + } else if (filterName === 'brightness') { + this._els.brightnessRange.value = options.brightness; + } else if (filterName === 'noise') { + this._els.noiseRange.value = options.noise; + } else if (filterName === 'tint') { + this._els.tintOpacity.value = options.alpha; + this._els.filterTintColor.color = options.color; + } else if (filterName === 'blend') { + this._els.filterBlendColor.color = options.color; + } else if (filterName === 'multiply') { + this._els.filterMultiplyColor.color = options.color; } - - /** - * Add event for filter - * @param {Function} applyFilter - actions for firter - * @param {string} filterName - filter name - * @param {boolean} [isLast] - Is last change - */ - _changeFilterState(applyFilter, filterName, isLast = true) { - const apply = this.checkedMap[filterName].checked; - const type = filterNameMap[filterName]; - - const checkboxGroup = this.checkedMap[filterName].closest('.tui-image-editor-checkbox-group'); - if (checkboxGroup) { - if (apply) { - checkboxGroup.classList.remove('tui-image-editor-disabled'); - } else { - checkboxGroup.classList.add('tui-image-editor-disabled'); - } - } - applyFilter(apply, type, this._getFilterOption(filterName), !isLast); + } + + /** + * Get filter name + * @param {string} type - filter type + * @param {Object} options - filter options + * @returns {string} filter name + * @private + */ + _getFilterNameFromOptions(type, options) { + let filterName = type; + + if (type === 'removeColor') { + filterName = snippet.isExisty(options.useAlpha) ? 'removeWhite' : 'colorFilter'; + } else if (type === 'blendColor') { + filterName = { + add: 'blend', + multiply: 'multiply', + tint: 'tint', + }[options.mode]; } - /** - * Get filter option - * @param {String} type - filter type - * @returns {Object} filter option object - * @private - */ - _getFilterOption(type) { // eslint-disable-line - const option = {}; - switch (type) { - case 'removeWhite': - option.color = '#FFFFFF'; - option.useAlpha = false; - option.distance = parseFloat(this._els.removewhiteDistanceRange.value); - break; - case 'colorFilter': - option.color = '#FFFFFF'; - option.distance = parseFloat(this._els.colorfilterThresholeRange.value); - break; - case 'pixelate': - option.blocksize = toInteger(this._els.pixelateRange.value); - break; - case 'noise': - option.noise = toInteger(this._els.noiseRange.value); - break; - case 'brightness': - option.brightness = parseFloat(this._els.brightnessRange.value); - break; - case 'blend': - option.mode = 'add'; - option.color = this._els.filterBlendColor.color; - option.mode = this._els.blendType.value; - break; - case 'multiply': - option.mode = 'multiply'; - option.color = this._els.filterMultiplyColor.color; - break; - case 'tint': - option.mode = 'tint'; - option.color = this._els.filterTintColor.color; - option.alpha = this._els.tintOpacity.value; - break; - case 'blur': - option.blur = this._els.blurRange.value; - break; - default: - break; - } - - return option; + return filterName; + } + + /** + * Add event for filter + * @param {Function} applyFilter - actions for firter + * @param {string} filterName - filter name + * @param {boolean} [isLast] - Is last change + */ + _changeFilterState(applyFilter, filterName, isLast = true) { + const apply = this.checkedMap[filterName].checked; + const type = filterNameMap[filterName]; + + const checkboxGroup = this.checkedMap[filterName].closest('.tui-image-editor-checkbox-group'); + if (checkboxGroup) { + if (apply) { + checkboxGroup.classList.remove('tui-image-editor-disabled'); + } else { + checkboxGroup.classList.add('tui-image-editor-disabled'); + } } - - /** - * Make submenu range and colorpicker control - * @private - */ - _makeControlElement() { - this._els = { - removewhiteDistanceRange: new Range( - {slider: this.selector('.tie-removewhite-distance-range')}, - FILTER_RANGE.removewhiteDistanceRange - ), - brightnessRange: new Range( - {slider: this.selector('.tie-brightness-range')}, - FILTER_RANGE.brightnessRange - ), - noiseRange: new Range( - {slider: this.selector('.tie-noise-range')}, - FILTER_RANGE.noiseRange - ), - pixelateRange: new Range( - {slider: this.selector('.tie-pixelate-range')}, - FILTER_RANGE.pixelateRange - ), - colorfilterThresholeRange: new Range( - {slider: this.selector('.tie-colorfilter-threshole-range')}, - FILTER_RANGE.colorfilterThresholeRange - ), - filterTintColor: new Colorpicker( - this.selector('.tie-filter-tint-color'), '#03bd9e', this.toggleDirection, this.usageStatistics - ), - filterMultiplyColor: new Colorpicker( - this.selector('.tie-filter-multiply-color'), '#515ce6', this.toggleDirection, this.usageStatistics - ), - filterBlendColor: new Colorpicker( - this.selector('.tie-filter-blend-color'), '#ffbb3b', this.toggleDirection, this.usageStatistics - ), - blurRange: FILTER_RANGE.blurFilterRange - }; - - this._els.tintOpacity = this._pickerWithRange(this._els.filterTintColor.pickerControl); - this._els.blendType = this._pickerWithSelectbox(this._els.filterBlendColor.pickerControl); - - this.colorPickerControls.push(this._els.filterTintColor); - this.colorPickerControls.push(this._els.filterMultiplyColor); - this.colorPickerControls.push(this._els.filterBlendColor); + applyFilter(apply, type, this._getFilterOption(filterName), !isLast); + } + + /** + * Get filter option + * @param {String} type - filter type + * @returns {Object} filter option object + * @private + */ + _getFilterOption(type) { + // eslint-disable-line + const option = {}; + switch (type) { + case 'removeWhite': + option.color = '#FFFFFF'; + option.useAlpha = false; + option.distance = parseFloat(this._els.removewhiteDistanceRange.value); + break; + case 'colorFilter': + option.color = '#FFFFFF'; + option.distance = parseFloat(this._els.colorfilterThresholeRange.value); + break; + case 'pixelate': + option.blocksize = toInteger(this._els.pixelateRange.value); + break; + case 'noise': + option.noise = toInteger(this._els.noiseRange.value); + break; + case 'brightness': + option.brightness = parseFloat(this._els.brightnessRange.value); + break; + case 'blend': + option.mode = 'add'; + option.color = this._els.filterBlendColor.color; + option.mode = this._els.blendType.value; + break; + case 'multiply': + option.mode = 'multiply'; + option.color = this._els.filterMultiplyColor.color; + break; + case 'tint': + option.mode = 'tint'; + option.color = this._els.filterTintColor.color; + option.alpha = this._els.tintOpacity.value; + break; + case 'blur': + option.blur = this._els.blurRange.value; + break; + default: + break; } - /** - * Make submenu control for picker & range mixin - * @param {HTMLElement} pickerControl - pickerControl dom element - * @returns {Range} - * @private - */ - _pickerWithRange(pickerControl) { - const rangeWrap = document.createElement('div'); - const rangelabel = document.createElement('label'); - const slider = document.createElement('div'); - - slider.id = 'tie-filter-tint-opacity'; - rangelabel.innerHTML = 'Opacity'; - rangeWrap.appendChild(rangelabel); - rangeWrap.appendChild(slider); - pickerControl.appendChild(rangeWrap); - pickerControl.style.height = PICKER_CONTROL_HEIGHT; - - return new Range({slider}, FILTER_RANGE.tintOpacityRange); - } - - /** - * Make submenu control for picker & selectbox - * @param {HTMLElement} pickerControl - pickerControl dom element - * @returns {HTMLElement} - * @private - */ - _pickerWithSelectbox(pickerControl) { - const selectlistWrap = document.createElement('div'); - const selectlist = document.createElement('select'); - const optionlist = document.createElement('ul'); - - selectlistWrap.className = 'tui-image-editor-selectlist-wrap'; - optionlist.className = 'tui-image-editor-selectlist'; - - selectlistWrap.appendChild(selectlist); - selectlistWrap.appendChild(optionlist); - - this._makeSelectOptionList(selectlist); - - pickerControl.appendChild(selectlistWrap); - pickerControl.style.height = PICKER_CONTROL_HEIGHT; - - this._drawSelectOptionList(selectlist, optionlist); - this._pickerWithSelectboxForAddEvent(selectlist, optionlist); - - return selectlist; - } - - /** - * Make selectbox option list custom style - * @param {HTMLElement} selectlist - selectbox element - * @param {HTMLElement} optionlist - custom option list item element - * @private - */ - _drawSelectOptionList(selectlist, optionlist) { - const options = selectlist.querySelectorAll('option'); - snippet.forEach(options, option => { - const optionElement = document.createElement('li'); - optionElement.innerHTML = option.innerHTML; - optionElement.setAttribute('data-item', option.value); - optionlist.appendChild(optionElement); - }); - } - - /** - * custome selectbox custom event - * @param {HTMLElement} selectlist - selectbox element - * @param {HTMLElement} optionlist - custom option list item element - * @private - */ - _pickerWithSelectboxForAddEvent(selectlist, optionlist) { - optionlist.addEventListener('click', event => { - const optionValue = event.target.getAttribute('data-item'); - const fireEvent = document.createEvent('HTMLEvents'); - - selectlist.querySelector(`[value="${optionValue}"]`).selected = true; - fireEvent.initEvent('change', true, true); - - selectlist.dispatchEvent(fireEvent); - - this.selectBoxShow = false; - optionlist.style.display = 'none'; - }); - - selectlist.addEventListener('mousedown', event => { - event.preventDefault(); - this.selectBoxShow = !this.selectBoxShow; - optionlist.style.display = this.selectBoxShow ? 'block' : 'none'; - optionlist.setAttribute('data-selectitem', selectlist.value); - optionlist.querySelector(`[data-item='${selectlist.value}']`).classList.add('active'); - }); - } - - /** - * Make option list for select control - * @param {HTMLElement} selectlist - blend option select list element - * @private - */ - _makeSelectOptionList(selectlist) { - snippet.forEach(BLEND_OPTIONS, option => { - const selectOption = document.createElement('option'); - selectOption.setAttribute('value', option); - selectOption.innerHTML = option.replace(/^[a-z]/, $0 => $0.toUpperCase()); - selectlist.appendChild(selectOption); - }); - } + return option; + } + + /** + * Make submenu range and colorpicker control + * @private + */ + _makeControlElement() { + this._els = { + removewhiteDistanceRange: new Range( + { slider: this.selector('.tie-removewhite-distance-range') }, + FILTER_RANGE.removewhiteDistanceRange + ), + brightnessRange: new Range( + { slider: this.selector('.tie-brightness-range') }, + FILTER_RANGE.brightnessRange + ), + noiseRange: new Range({ slider: this.selector('.tie-noise-range') }, FILTER_RANGE.noiseRange), + pixelateRange: new Range( + { slider: this.selector('.tie-pixelate-range') }, + FILTER_RANGE.pixelateRange + ), + colorfilterThresholeRange: new Range( + { slider: this.selector('.tie-colorfilter-threshole-range') }, + FILTER_RANGE.colorfilterThresholeRange + ), + filterTintColor: new Colorpicker( + this.selector('.tie-filter-tint-color'), + '#03bd9e', + this.toggleDirection, + this.usageStatistics + ), + filterMultiplyColor: new Colorpicker( + this.selector('.tie-filter-multiply-color'), + '#515ce6', + this.toggleDirection, + this.usageStatistics + ), + filterBlendColor: new Colorpicker( + this.selector('.tie-filter-blend-color'), + '#ffbb3b', + this.toggleDirection, + this.usageStatistics + ), + blurRange: FILTER_RANGE.blurFilterRange, + }; + + this._els.tintOpacity = this._pickerWithRange(this._els.filterTintColor.pickerControl); + this._els.blendType = this._pickerWithSelectbox(this._els.filterBlendColor.pickerControl); + + this.colorPickerControls.push(this._els.filterTintColor); + this.colorPickerControls.push(this._els.filterMultiplyColor); + this.colorPickerControls.push(this._els.filterBlendColor); + } + + /** + * Make submenu control for picker & range mixin + * @param {HTMLElement} pickerControl - pickerControl dom element + * @returns {Range} + * @private + */ + _pickerWithRange(pickerControl) { + const rangeWrap = document.createElement('div'); + const rangelabel = document.createElement('label'); + const slider = document.createElement('div'); + + slider.id = 'tie-filter-tint-opacity'; + rangelabel.innerHTML = 'Opacity'; + rangeWrap.appendChild(rangelabel); + rangeWrap.appendChild(slider); + pickerControl.appendChild(rangeWrap); + pickerControl.style.height = PICKER_CONTROL_HEIGHT; + + return new Range({ slider }, FILTER_RANGE.tintOpacityRange); + } + + /** + * Make submenu control for picker & selectbox + * @param {HTMLElement} pickerControl - pickerControl dom element + * @returns {HTMLElement} + * @private + */ + _pickerWithSelectbox(pickerControl) { + const selectlistWrap = document.createElement('div'); + const selectlist = document.createElement('select'); + const optionlist = document.createElement('ul'); + + selectlistWrap.className = 'tui-image-editor-selectlist-wrap'; + optionlist.className = 'tui-image-editor-selectlist'; + + selectlistWrap.appendChild(selectlist); + selectlistWrap.appendChild(optionlist); + + this._makeSelectOptionList(selectlist); + + pickerControl.appendChild(selectlistWrap); + pickerControl.style.height = PICKER_CONTROL_HEIGHT; + + this._drawSelectOptionList(selectlist, optionlist); + this._pickerWithSelectboxForAddEvent(selectlist, optionlist); + + return selectlist; + } + + /** + * Make selectbox option list custom style + * @param {HTMLElement} selectlist - selectbox element + * @param {HTMLElement} optionlist - custom option list item element + * @private + */ + _drawSelectOptionList(selectlist, optionlist) { + const options = selectlist.querySelectorAll('option'); + snippet.forEach(options, (option) => { + const optionElement = document.createElement('li'); + optionElement.innerHTML = option.innerHTML; + optionElement.setAttribute('data-item', option.value); + optionlist.appendChild(optionElement); + }); + } + + /** + * custome selectbox custom event + * @param {HTMLElement} selectlist - selectbox element + * @param {HTMLElement} optionlist - custom option list item element + * @private + */ + _pickerWithSelectboxForAddEvent(selectlist, optionlist) { + optionlist.addEventListener('click', (event) => { + const optionValue = event.target.getAttribute('data-item'); + const fireEvent = document.createEvent('HTMLEvents'); + + selectlist.querySelector(`[value="${optionValue}"]`).selected = true; + fireEvent.initEvent('change', true, true); + + selectlist.dispatchEvent(fireEvent); + + this.selectBoxShow = false; + optionlist.style.display = 'none'; + }); + + selectlist.addEventListener('mousedown', (event) => { + event.preventDefault(); + this.selectBoxShow = !this.selectBoxShow; + optionlist.style.display = this.selectBoxShow ? 'block' : 'none'; + optionlist.setAttribute('data-selectitem', selectlist.value); + optionlist.querySelector(`[data-item='${selectlist.value}']`).classList.add('active'); + }); + } + + /** + * Make option list for select control + * @param {HTMLElement} selectlist - blend option select list element + * @private + */ + _makeSelectOptionList(selectlist) { + snippet.forEach(BLEND_OPTIONS, (option) => { + const selectOption = document.createElement('option'); + selectOption.setAttribute('value', option); + selectOption.innerHTML = option.replace(/^[a-z]/, ($0) => $0.toUpperCase()); + selectlist.appendChild(selectOption); + }); + } } export default Filter; diff --git a/src/js/ui/flip.js b/src/js/ui/flip.js index 23d6974c8..089b2c693 100644 --- a/src/js/ui/flip.js +++ b/src/js/ui/flip.js @@ -1,5 +1,5 @@ import snippet from 'tui-code-snippet'; -import {assignmentForDestroy} from '../util'; +import { assignmentForDestroy } from '../util'; import Submenu from './submenuBase'; import templateHtml from './template/submenu/flip'; @@ -9,79 +9,79 @@ import templateHtml from './template/submenu/flip'; * @ignore */ class Flip extends Submenu { - constructor(subMenuElement, {locale, makeSvgIcon, menuBarPosition, usageStatistics}) { - super(subMenuElement, { - locale, - name: 'flip', - makeSvgIcon, - menuBarPosition, - templateHtml, - usageStatistics - }); - this.flipStatus = false; + constructor(subMenuElement, { locale, makeSvgIcon, menuBarPosition, usageStatistics }) { + super(subMenuElement, { + locale, + name: 'flip', + makeSvgIcon, + menuBarPosition, + templateHtml, + usageStatistics, + }); + this.flipStatus = false; - this._els = { - flipButton: this.selector('.tie-flip-button') - }; - } + this._els = { + flipButton: this.selector('.tie-flip-button'), + }; + } - /** - * Destroys the instance. - */ - destroy() { - this._removeEvent(); + /** + * Destroys the instance. + */ + destroy() { + this._removeEvent(); - assignmentForDestroy(this); - } + assignmentForDestroy(this); + } - /** - * Add event for flip - * @param {Object} actions - actions for flip - * @param {Function} actions.flip - flip action - */ - addEvent(actions) { - this.eventHandler.changeFlip = this._changeFlip.bind(this); - this._actions = actions; - this._els.flipButton.addEventListener('click', this.eventHandler.changeFlip); - } + /** + * Add event for flip + * @param {Object} actions - actions for flip + * @param {Function} actions.flip - flip action + */ + addEvent(actions) { + this.eventHandler.changeFlip = this._changeFlip.bind(this); + this._actions = actions; + this._els.flipButton.addEventListener('click', this.eventHandler.changeFlip); + } - /** - * Remove event - * @private - */ - _removeEvent() { - this._els.flipButton.removeEventListener('click', this.eventHandler.changeFlip); - } + /** + * Remove event + * @private + */ + _removeEvent() { + this._els.flipButton.removeEventListener('click', this.eventHandler.changeFlip); + } - /** - * change Flip status - * @param {object} event - change event - * @private - */ - _changeFlip(event) { - const button = event.target.closest('.tui-image-editor-button'); - if (button) { - const flipType = this.getButtonType(button, ['flipX', 'flipY', 'resetFlip']); - if (!this.flipStatus && flipType === 'resetFlip') { - return; - } + /** + * change Flip status + * @param {object} event - change event + * @private + */ + _changeFlip(event) { + const button = event.target.closest('.tui-image-editor-button'); + if (button) { + const flipType = this.getButtonType(button, ['flipX', 'flipY', 'resetFlip']); + if (!this.flipStatus && flipType === 'resetFlip') { + return; + } - this._actions.flip(flipType).then(flipStatus => { - const flipClassList = this._els.flipButton.classList; - this.flipStatus = false; + this._actions.flip(flipType).then((flipStatus) => { + const flipClassList = this._els.flipButton.classList; + this.flipStatus = false; - flipClassList.remove('resetFlip'); - snippet.forEach(['flipX', 'flipY'], type => { - flipClassList.remove(type); - if (flipStatus[type]) { - flipClassList.add(type); - flipClassList.add('resetFlip'); - this.flipStatus = true; - } - }); - }); - } + flipClassList.remove('resetFlip'); + snippet.forEach(['flipX', 'flipY'], (type) => { + flipClassList.remove(type); + if (flipStatus[type]) { + flipClassList.add(type); + flipClassList.add('resetFlip'); + this.flipStatus = true; + } + }); + }); } + } } export default Flip; diff --git a/src/js/ui/icon.js b/src/js/ui/icon.js index e94e5298f..8e2ec37bb 100644 --- a/src/js/ui/icon.js +++ b/src/js/ui/icon.js @@ -2,8 +2,8 @@ import snippet from 'tui-code-snippet'; import Colorpicker from './tools/colorpicker'; import Submenu from './submenuBase'; import templateHtml from './template/submenu/icon'; -import {isSupportFileApi, assignmentForDestroy} from '../util'; -import {defaultIconPath} from '../consts'; +import { isSupportFileApi, assignmentForDestroy } from '../util'; +import { defaultIconPath } from '../consts'; /** * Icon ui class @@ -11,157 +11,160 @@ import {defaultIconPath} from '../consts'; * @ignore */ class Icon extends Submenu { - constructor(subMenuElement, {locale, makeSvgIcon, menuBarPosition, usageStatistics}) { - super(subMenuElement, { - locale, - name: 'icon', - makeSvgIcon, - menuBarPosition, - templateHtml, - usageStatistics - }); - - this.iconType = null; - this._iconMap = {}; - - this._els = { - registrIconButton: this.selector('.tie-icon-image-file'), - addIconButton: this.selector('.tie-icon-add-button'), - iconColorpicker: new Colorpicker( - this.selector('.tie-icon-color'), '#ffbb3b', this.toggleDirection, this.usageStatistics - ) - }; + constructor(subMenuElement, { locale, makeSvgIcon, menuBarPosition, usageStatistics }) { + super(subMenuElement, { + locale, + name: 'icon', + makeSvgIcon, + menuBarPosition, + templateHtml, + usageStatistics, + }); + + this.iconType = null; + this._iconMap = {}; + + this._els = { + registrIconButton: this.selector('.tie-icon-image-file'), + addIconButton: this.selector('.tie-icon-add-button'), + iconColorpicker: new Colorpicker( + this.selector('.tie-icon-color'), + '#ffbb3b', + this.toggleDirection, + this.usageStatistics + ), + }; + } + + /** + * Destroys the instance. + */ + destroy() { + this._removeEvent(); + this._els.iconColorpicker.destroy(); + + assignmentForDestroy(this); + } + + /** + * Add event for icon + * @param {Object} actions - actions for icon + * @param {Function} actions.registCustomIcon - register icon + * @param {Function} actions.addIcon - add icon + * @param {Function} actions.changeColor - change icon color + */ + addEvent(actions) { + const registerIcon = this._registerIconHandler.bind(this); + const addIcon = this._addIconHandler.bind(this); + + this.eventHandler = { + registerIcon, + addIcon, + }; + + this.actions = actions; + this._els.iconColorpicker.on('change', this._changeColorHandler.bind(this)); + this._els.registrIconButton.addEventListener('change', registerIcon); + this._els.addIconButton.addEventListener('click', addIcon); + } + + /** + * Remove event + * @private + */ + _removeEvent() { + this._els.iconColorpicker.off(); + this._els.registrIconButton.removeEventListener('change', this.eventHandler.registerIcon); + this._els.addIconButton.removeEventListener('click', this.eventHandler.addIcon); + } + + /** + * Clear icon type + */ + clearIconType() { + this._els.addIconButton.classList.remove(this.iconType); + this.iconType = null; + } + + /** + * Register default icon + */ + registDefaultIcon() { + snippet.forEach(defaultIconPath, (path, type) => { + this.actions.registDefalutIcons(type, path); + }); + } + + /** + * Set icon picker color + * @param {string} iconColor - rgb color string + */ + setIconPickerColor(iconColor) { + this._els.iconColorpicker.color = iconColor; + } + + /** + * Returns the menu to its default state. + */ + changeStandbyMode() { + this.clearIconType(); + this.actions.cancelAddIcon(); + } + + /** + * Change icon color + * @param {string} color - color for change + * @private + */ + _changeColorHandler(color) { + color = color || 'transparent'; + this.actions.changeColor(color); + } + + /** + * Change icon color + * @param {object} event - add button event object + * @private + */ + _addIconHandler(event) { + const button = event.target.closest('.tui-image-editor-button'); + + if (button) { + const iconType = button.getAttribute('data-icontype'); + const iconColor = this._els.iconColorpicker.color; + this.actions.discardSelection(); + this.actions.changeSelectableAll(false); + this._els.addIconButton.classList.remove(this.iconType); + this._els.addIconButton.classList.add(iconType); + + if (this.iconType === iconType) { + this.changeStandbyMode(); + } else { + this.actions.addIcon(iconType, iconColor); + this.iconType = iconType; + } } - - /** - * Destroys the instance. - */ - destroy() { - this._removeEvent(); - this._els.iconColorpicker.destroy(); - - assignmentForDestroy(this); - } - - /** - * Add event for icon - * @param {Object} actions - actions for icon - * @param {Function} actions.registCustomIcon - register icon - * @param {Function} actions.addIcon - add icon - * @param {Function} actions.changeColor - change icon color - */ - addEvent(actions) { - const registerIcon = this._registerIconHandler.bind(this); - const addIcon = this._addIconHandler.bind(this); - - this.eventHandler = { - registerIcon, - addIcon - }; - - this.actions = actions; - this._els.iconColorpicker.on('change', this._changeColorHandler.bind(this)); - this._els.registrIconButton.addEventListener('change', registerIcon); - this._els.addIconButton.addEventListener('click', addIcon); - } - - /** - * Remove event - * @private - */ - _removeEvent() { - this._els.iconColorpicker.off(); - this._els.registrIconButton.removeEventListener('change', this.eventHandler.registerIcon); - this._els.addIconButton.removeEventListener('click', this.eventHandler.addIcon); - } - - /** - * Clear icon type - */ - clearIconType() { - this._els.addIconButton.classList.remove(this.iconType); - this.iconType = null; - } - - /** - * Register default icon - */ - registDefaultIcon() { - snippet.forEach(defaultIconPath, (path, type) => { - this.actions.registDefalutIcons(type, path); - }); + } + + /** + * register icon + * @param {object} event - file change event object + * @private + */ + _registerIconHandler(event) { + let imgUrl; + + if (!isSupportFileApi) { + alert('This browser does not support file-api'); } - /** - * Set icon picker color - * @param {string} iconColor - rgb color string - */ - setIconPickerColor(iconColor) { - this._els.iconColorpicker.color = iconColor; - } - - /** - * Returns the menu to its default state. - */ - changeStandbyMode() { - this.clearIconType(); - this.actions.cancelAddIcon(); - } - - /** - * Change icon color - * @param {string} color - color for change - * @private - */ - _changeColorHandler(color) { - color = color || 'transparent'; - this.actions.changeColor(color); - } - - /** - * Change icon color - * @param {object} event - add button event object - * @private - */ - _addIconHandler(event) { - const button = event.target.closest('.tui-image-editor-button'); - - if (button) { - const iconType = button.getAttribute('data-icontype'); - const iconColor = this._els.iconColorpicker.color; - this.actions.discardSelection(); - this.actions.changeSelectableAll(false); - this._els.addIconButton.classList.remove(this.iconType); - this._els.addIconButton.classList.add(iconType); - - if (this.iconType === iconType) { - this.changeStandbyMode(); - } else { - this.actions.addIcon(iconType, iconColor); - this.iconType = iconType; - } - } - } - - /** - * register icon - * @param {object} event - file change event object - * @private - */ - _registerIconHandler(event) { - let imgUrl; - - if (!isSupportFileApi) { - alert('This browser does not support file-api'); - } - - const [file] = event.target.files; + const [file] = event.target.files; - if (file) { - imgUrl = URL.createObjectURL(file); - this.actions.registCustomIcon(imgUrl, file); - } + if (file) { + imgUrl = URL.createObjectURL(file); + this.actions.registCustomIcon(imgUrl, file); } + } } export default Icon; diff --git a/src/js/ui/locale/locale.js b/src/js/ui/locale/locale.js index 7f7c9e1f3..63ca849a0 100644 --- a/src/js/ui/locale/locale.js +++ b/src/js/ui/locale/locale.js @@ -2,18 +2,18 @@ * Translate messages */ class Locale { - constructor(locale) { - this._locale = locale; - } + constructor(locale) { + this._locale = locale; + } - /** - * localize message - * @param {string} message - message who will be localized - * @returns {string} - */ - localize(message) { - return this._locale[message] || message; - } + /** + * localize message + * @param {string} message - message who will be localized + * @returns {string} + */ + localize(message) { + return this._locale[message] || message; + } } export default Locale; diff --git a/src/js/ui/mask.js b/src/js/ui/mask.js index c9adf6799..a5be239e5 100644 --- a/src/js/ui/mask.js +++ b/src/js/ui/mask.js @@ -1,5 +1,5 @@ import Submenu from './submenuBase'; -import {assignmentForDestroy, isSupportFileApi} from '../util'; +import { assignmentForDestroy, isSupportFileApi } from '../util'; import templateHtml from './template/submenu/mask'; /** @@ -8,89 +8,89 @@ import templateHtml from './template/submenu/mask'; * @ignore */ class Mask extends Submenu { - constructor(subMenuElement, {locale, makeSvgIcon, menuBarPosition, usageStatistics}) { - super(subMenuElement, { - locale, - name: 'mask', - makeSvgIcon, - menuBarPosition, - templateHtml, - usageStatistics - }); + constructor(subMenuElement, { locale, makeSvgIcon, menuBarPosition, usageStatistics }) { + super(subMenuElement, { + locale, + name: 'mask', + makeSvgIcon, + menuBarPosition, + templateHtml, + usageStatistics, + }); - this._els = { - applyButton: this.selector('.tie-mask-apply'), - maskImageButton: this.selector('.tie-mask-image-file') - }; - } + this._els = { + applyButton: this.selector('.tie-mask-apply'), + maskImageButton: this.selector('.tie-mask-image-file'), + }; + } - /** - * Destroys the instance. - */ - destroy() { - this._removeEvent(); + /** + * Destroys the instance. + */ + destroy() { + this._removeEvent(); - assignmentForDestroy(this); - } + assignmentForDestroy(this); + } - /** - * Add event for mask - * @param {Object} actions - actions for crop - * @param {Function} actions.loadImageFromURL - load image action - * @param {Function} actions.applyFilter - apply filter action - */ - addEvent(actions) { - const loadMaskFile = this._loadMaskFile.bind(this); - const applyMask = this._applyMask.bind(this); + /** + * Add event for mask + * @param {Object} actions - actions for crop + * @param {Function} actions.loadImageFromURL - load image action + * @param {Function} actions.applyFilter - apply filter action + */ + addEvent(actions) { + const loadMaskFile = this._loadMaskFile.bind(this); + const applyMask = this._applyMask.bind(this); - this.eventHandler = { - loadMaskFile, - applyMask - }; + this.eventHandler = { + loadMaskFile, + applyMask, + }; - this.actions = actions; - this._els.maskImageButton.addEventListener('change', loadMaskFile); - this._els.applyButton.addEventListener('click', applyMask); - } + this.actions = actions; + this._els.maskImageButton.addEventListener('change', loadMaskFile); + this._els.applyButton.addEventListener('click', applyMask); + } - /** - * Remove event - * @private - */ - _removeEvent() { - this._els.maskImageButton.removeEventListener('change', this.eventHandler.loadMaskFile); - this._els.applyButton.removeEventListener('click', this.eventHandler.applyMask); - } + /** + * Remove event + * @private + */ + _removeEvent() { + this._els.maskImageButton.removeEventListener('change', this.eventHandler.loadMaskFile); + this._els.applyButton.removeEventListener('click', this.eventHandler.applyMask); + } - /** - * Apply mask - * @private - */ - _applyMask() { - this.actions.applyFilter(); - this._els.applyButton.classList.remove('active'); - } + /** + * Apply mask + * @private + */ + _applyMask() { + this.actions.applyFilter(); + this._els.applyButton.classList.remove('active'); + } - /** - * Load mask file - * @param {object} event - File change event object - * @private - */ - _loadMaskFile(event) { - let imgUrl; + /** + * Load mask file + * @param {object} event - File change event object + * @private + */ + _loadMaskFile(event) { + let imgUrl; - if (!isSupportFileApi()) { - alert('This browser does not support file-api'); - } + if (!isSupportFileApi()) { + alert('This browser does not support file-api'); + } - const [file] = event.target.files; + const [file] = event.target.files; - if (file) { - imgUrl = URL.createObjectURL(file); - this.actions.loadImageFromURL(imgUrl, file); - this._els.applyButton.classList.add('active'); - } + if (file) { + imgUrl = URL.createObjectURL(file); + this.actions.loadImageFromURL(imgUrl, file); + this._els.applyButton.classList.add('active'); } + } } export default Mask; diff --git a/src/js/ui/rotate.js b/src/js/ui/rotate.js index 15ad1bacf..9a9e7109b 100644 --- a/src/js/ui/rotate.js +++ b/src/js/ui/rotate.js @@ -1,8 +1,8 @@ import Range from './tools/range'; import Submenu from './submenuBase'; import templateHtml from './template/submenu/rotate'; -import {toInteger, assignmentForDestroy} from '../util'; -import {defaultRotateRangeValus} from '../consts'; +import { toInteger, assignmentForDestroy } from '../util'; +import { defaultRotateRangeValus } from '../consts'; const CLOCKWISE = 30; const COUNTERCLOCKWISE = -30; @@ -13,108 +13,111 @@ const COUNTERCLOCKWISE = -30; * @ignore */ class Rotate extends Submenu { - constructor(subMenuElement, {locale, makeSvgIcon, menuBarPosition, usageStatistics}) { - super(subMenuElement, { - locale, - name: 'rotate', - makeSvgIcon, - menuBarPosition, - templateHtml, - usageStatistics - }); - this._value = 0; - - this._els = { - rotateButton: this.selector('.tie-retate-button'), - rotateRange: new Range({ - slider: this.selector('.tie-rotate-range'), - input: this.selector('.tie-ratate-range-value') - }, defaultRotateRangeValus) - }; + constructor(subMenuElement, { locale, makeSvgIcon, menuBarPosition, usageStatistics }) { + super(subMenuElement, { + locale, + name: 'rotate', + makeSvgIcon, + menuBarPosition, + templateHtml, + usageStatistics, + }); + this._value = 0; + + this._els = { + rotateButton: this.selector('.tie-retate-button'), + rotateRange: new Range( + { + slider: this.selector('.tie-rotate-range'), + input: this.selector('.tie-ratate-range-value'), + }, + defaultRotateRangeValus + ), + }; + } + + /** + * Destroys the instance. + */ + destroy() { + this._removeEvent(); + this._els.rotateRange.destroy(); + + assignmentForDestroy(this); + } + + setRangeBarAngle(type, angle) { + let resultAngle = angle; + + if (type === 'rotate') { + resultAngle = parseInt(this._els.rotateRange.value, 10) + angle; } - /** - * Destroys the instance. - */ - destroy() { - this._removeEvent(); - this._els.rotateRange.destroy(); - - assignmentForDestroy(this); - } - - setRangeBarAngle(type, angle) { - let resultAngle = angle; - - if (type === 'rotate') { - resultAngle = parseInt(this._els.rotateRange.value, 10) + angle; - } - - this._setRangeBarRatio(resultAngle); - } - - _setRangeBarRatio(angle) { - this._els.rotateRange.value = angle; - } - - /** - * Add event for rotate - * @param {Object} actions - actions for crop - * @param {Function} actions.rotate - rotate action - * @param {Function} actions.setAngle - set angle action - */ - addEvent(actions) { - this.eventHandler.rotationAngleChanged = this._changeRotateForButton.bind(this); - - // {rotate, setAngle} - this.actions = actions; - this._els.rotateButton.addEventListener('click', this.eventHandler.rotationAngleChanged); - this._els.rotateRange.on('change', this._changeRotateForRange.bind(this)); - } - - /** - * Remove event - * @private - */ - _removeEvent() { - this._els.rotateButton.removeEventListener('click', this.eventHandler.rotationAngleChanged); - this._els.rotateRange.off(); - } - - /** - * Change rotate for range - * @param {number} value - angle value - * @param {boolean} isLast - Is last change - * @private - */ - _changeRotateForRange(value, isLast) { - const angle = toInteger(value); - this.actions.setAngle(angle, !isLast); - this._value = angle; - } - - /** - * Change rotate for button - * @param {object} event - add button event object - * @private - */ - _changeRotateForButton(event) { - const button = event.target.closest('.tui-image-editor-button'); - const angle = this._els.rotateRange.value; - - if (button) { - const rotateType = this.getButtonType(button, ['counterclockwise', 'clockwise']); - const rotateAngle = { - clockwise: CLOCKWISE, - counterclockwise: COUNTERCLOCKWISE - }[rotateType]; - const newAngle = parseInt(angle, 10) + rotateAngle; - const isRotatable = newAngle >= -360 && newAngle <= 360; - if (isRotatable) { - this.actions.rotate(rotateAngle); - } - } + this._setRangeBarRatio(resultAngle); + } + + _setRangeBarRatio(angle) { + this._els.rotateRange.value = angle; + } + + /** + * Add event for rotate + * @param {Object} actions - actions for crop + * @param {Function} actions.rotate - rotate action + * @param {Function} actions.setAngle - set angle action + */ + addEvent(actions) { + this.eventHandler.rotationAngleChanged = this._changeRotateForButton.bind(this); + + // {rotate, setAngle} + this.actions = actions; + this._els.rotateButton.addEventListener('click', this.eventHandler.rotationAngleChanged); + this._els.rotateRange.on('change', this._changeRotateForRange.bind(this)); + } + + /** + * Remove event + * @private + */ + _removeEvent() { + this._els.rotateButton.removeEventListener('click', this.eventHandler.rotationAngleChanged); + this._els.rotateRange.off(); + } + + /** + * Change rotate for range + * @param {number} value - angle value + * @param {boolean} isLast - Is last change + * @private + */ + _changeRotateForRange(value, isLast) { + const angle = toInteger(value); + this.actions.setAngle(angle, !isLast); + this._value = angle; + } + + /** + * Change rotate for button + * @param {object} event - add button event object + * @private + */ + _changeRotateForButton(event) { + const button = event.target.closest('.tui-image-editor-button'); + const angle = this._els.rotateRange.value; + + if (button) { + const rotateType = this.getButtonType(button, ['counterclockwise', 'clockwise']); + const rotateAngle = { + clockwise: CLOCKWISE, + counterclockwise: COUNTERCLOCKWISE, + }[rotateType]; + const newAngle = parseInt(angle, 10) + rotateAngle; + const isRotatable = newAngle >= -360 && newAngle <= 360; + if (isRotatable) { + this.actions.rotate(rotateAngle); + } } + } } export default Rotate; diff --git a/src/js/ui/shape.js b/src/js/ui/shape.js index b7dd3334f..baf5b2cb1 100644 --- a/src/js/ui/shape.js +++ b/src/js/ui/shape.js @@ -2,13 +2,13 @@ import Colorpicker from './tools/colorpicker'; import Range from './tools/range'; import Submenu from './submenuBase'; import templateHtml from './template/submenu/shape'; -import {toInteger, assignmentForDestroy} from '../util'; -import {defaultShapeStrokeValus} from '../consts'; +import { toInteger, assignmentForDestroy } from '../util'; +import { defaultShapeStrokeValus } from '../consts'; const SHAPE_DEFAULT_OPTION = { - stroke: '#ffbb3b', - fill: '', - strokeWidth: 3 + stroke: '#ffbb3b', + fill: '', + strokeWidth: 3, }; /** @@ -17,208 +17,220 @@ const SHAPE_DEFAULT_OPTION = { * @ignore */ class Shape extends Submenu { - constructor(subMenuElement, {locale, makeSvgIcon, menuBarPosition, usageStatistics}) { - super(subMenuElement, { - locale, - name: 'shape', - makeSvgIcon, - menuBarPosition, - templateHtml, - usageStatistics - }); - this.type = null; - this.options = SHAPE_DEFAULT_OPTION; - - this._els = { - shapeSelectButton: this.selector('.tie-shape-button'), - shapeColorButton: this.selector('.tie-shape-color-button'), - strokeRange: new Range({ - slider: this.selector('.tie-stroke-range'), - input: this.selector('.tie-stroke-range-value') - }, defaultShapeStrokeValus), - fillColorpicker: new Colorpicker( - this.selector('.tie-color-fill'), '', this.toggleDirection, this.usageStatistics - ), - strokeColorpicker: new Colorpicker( - this.selector('.tie-color-stroke'), '#ffbb3b', this.toggleDirection, this.usageStatistics - ) - }; - - this.colorPickerControls.push(this._els.fillColorpicker); - this.colorPickerControls.push(this._els.strokeColorpicker); + constructor(subMenuElement, { locale, makeSvgIcon, menuBarPosition, usageStatistics }) { + super(subMenuElement, { + locale, + name: 'shape', + makeSvgIcon, + menuBarPosition, + templateHtml, + usageStatistics, + }); + this.type = null; + this.options = SHAPE_DEFAULT_OPTION; + + this._els = { + shapeSelectButton: this.selector('.tie-shape-button'), + shapeColorButton: this.selector('.tie-shape-color-button'), + strokeRange: new Range( + { + slider: this.selector('.tie-stroke-range'), + input: this.selector('.tie-stroke-range-value'), + }, + defaultShapeStrokeValus + ), + fillColorpicker: new Colorpicker( + this.selector('.tie-color-fill'), + '', + this.toggleDirection, + this.usageStatistics + ), + strokeColorpicker: new Colorpicker( + this.selector('.tie-color-stroke'), + '#ffbb3b', + this.toggleDirection, + this.usageStatistics + ), + }; + + this.colorPickerControls.push(this._els.fillColorpicker); + this.colorPickerControls.push(this._els.strokeColorpicker); + } + + /** + * Destroys the instance. + */ + destroy() { + this._removeEvent(); + this._els.strokeRange.destroy(); + this._els.fillColorpicker.destroy(); + this._els.strokeColorpicker.destroy(); + + assignmentForDestroy(this); + } + + /** + * Add event for shape + * @param {Object} actions - actions for shape + * @param {Function} actions.changeShape - change shape mode + * @param {Function} actions.setDrawingShape - set dreawing shape + */ + addEvent(actions) { + this.eventHandler.shapeTypeSelected = this._changeShapeHandler.bind(this); + this.actions = actions; + + this._els.shapeSelectButton.addEventListener('click', this.eventHandler.shapeTypeSelected); + this._els.strokeRange.on('change', this._changeStrokeRangeHandler.bind(this)); + this._els.fillColorpicker.on('change', this._changeFillColorHandler.bind(this)); + this._els.strokeColorpicker.on('change', this._changeStrokeColorHandler.bind(this)); + this._els.fillColorpicker.on('changeShow', this.colorPickerChangeShow.bind(this)); + this._els.strokeColorpicker.on('changeShow', this.colorPickerChangeShow.bind(this)); + } + + /** + * Remove event + * @private + */ + _removeEvent() { + this._els.shapeSelectButton.removeEventListener('click', this.eventHandler.shapeTypeSelected); + this._els.strokeRange.off(); + this._els.fillColorpicker.off(); + this._els.strokeColorpicker.off(); + } + + /** + * Set Shape status + * @param {Object} options - options of shape status + * @param {string} strokeWidth - stroke width + * @param {string} strokeColor - stroke color + * @param {string} fillColor - fill color + */ + setShapeStatus({ strokeWidth, strokeColor, fillColor }) { + this._els.strokeRange.value = strokeWidth; + this._els.strokeColorpicker.color = strokeColor; + this._els.fillColorpicker.color = fillColor; + this.options.stroke = strokeColor; + this.options.fill = fillColor; + this.options.strokeWidth = strokeWidth; + + this.actions.setDrawingShape(this.type, { strokeWidth }); + } + + /** + * Executed when the menu starts. + */ + changeStartMode() { + this.actions.stopDrawingMode(); + } + + /** + * Returns the menu to its default state. + */ + changeStandbyMode() { + this.type = null; + this.actions.changeSelectableAll(true); + this._els.shapeSelectButton.classList.remove('circle'); + this._els.shapeSelectButton.classList.remove('triangle'); + this._els.shapeSelectButton.classList.remove('rect'); + } + + /** + * set range stroke max value + * @param {number} maxValue - expect max value for change + */ + setMaxStrokeValue(maxValue) { + let strokeMaxValue = maxValue; + if (strokeMaxValue <= 0) { + strokeMaxValue = defaultShapeStrokeValus.max; } - - /** - * Destroys the instance. - */ - destroy() { - this._removeEvent(); - this._els.strokeRange.destroy(); - this._els.fillColorpicker.destroy(); - this._els.strokeColorpicker.destroy(); - - assignmentForDestroy(this); - } - - /** - * Add event for shape - * @param {Object} actions - actions for shape - * @param {Function} actions.changeShape - change shape mode - * @param {Function} actions.setDrawingShape - set dreawing shape - */ - addEvent(actions) { - this.eventHandler.shapeTypeSelected = this._changeShapeHandler.bind(this); - this.actions = actions; - - this._els.shapeSelectButton.addEventListener('click', this.eventHandler.shapeTypeSelected); - this._els.strokeRange.on('change', this._changeStrokeRangeHandler.bind(this)); - this._els.fillColorpicker.on('change', this._changeFillColorHandler.bind(this)); - this._els.strokeColorpicker.on('change', this._changeStrokeColorHandler.bind(this)); - this._els.fillColorpicker.on('changeShow', this.colorPickerChangeShow.bind(this)); - this._els.strokeColorpicker.on('changeShow', this.colorPickerChangeShow.bind(this)); - } - - /** - * Remove event - * @private - */ - _removeEvent() { - this._els.shapeSelectButton.removeEventListener('click', this.eventHandler.shapeTypeSelected); - this._els.strokeRange.off(); - this._els.fillColorpicker.off(); - this._els.strokeColorpicker.off(); - } - - /** - * Set Shape status - * @param {Object} options - options of shape status - * @param {string} strokeWidth - stroke width - * @param {string} strokeColor - stroke color - * @param {string} fillColor - fill color - */ - setShapeStatus({strokeWidth, strokeColor, fillColor}) { - this._els.strokeRange.value = strokeWidth; - this._els.strokeColorpicker.color = strokeColor; - this._els.fillColorpicker.color = fillColor; - this.options.stroke = strokeColor; - this.options.fill = fillColor; - this.options.strokeWidth = strokeWidth; - - this.actions.setDrawingShape(this.type, {strokeWidth}); - } - - /** - * Executed when the menu starts. - */ - changeStartMode() { - this.actions.stopDrawingMode(); - } - - /** - * Returns the menu to its default state. - */ - changeStandbyMode() { - this.type = null; - this.actions.changeSelectableAll(true); - this._els.shapeSelectButton.classList.remove('circle'); - this._els.shapeSelectButton.classList.remove('triangle'); - this._els.shapeSelectButton.classList.remove('rect'); - } - - /** - * set range stroke max value - * @param {number} maxValue - expect max value for change - */ - setMaxStrokeValue(maxValue) { - let strokeMaxValue = maxValue; - if (strokeMaxValue <= 0) { - strokeMaxValue = defaultShapeStrokeValus.max; - } - this._els.strokeRange.max = strokeMaxValue; - } - - /** - * Set stroke value - * @param {number} value - expect value for strokeRange change - */ - setStrokeValue(value) { - this._els.strokeRange.value = value; - this._els.strokeRange.trigger('change'); - } - - /** - * Get stroke value - * @returns {number} - stroke range value - */ - getStrokeValue() { - return this._els.strokeRange.value; - } - - /** - * Change icon color - * @param {object} event - add button event object - * @private - */ - _changeShapeHandler(event) { - const button = event.target.closest('.tui-image-editor-button'); - if (button) { - this.actions.stopDrawingMode(); - this.actions.discardSelection(); - const shapeType = this.getButtonType(button, ['circle', 'triangle', 'rect']); - - if (this.type === shapeType) { - this.changeStandbyMode(); - - return; - } - this.changeStandbyMode(); - this.type = shapeType; - event.currentTarget.classList.add(shapeType); - this.actions.changeSelectableAll(false); - this.actions.modeChange('shape'); - } - } - - /** - * Change stroke range - * @param {number} value - stroke range value - * @param {boolean} isLast - Is last change - * @private - */ - _changeStrokeRangeHandler(value, isLast) { - this.options.strokeWidth = toInteger(value); - this.actions.changeShape({ - strokeWidth: value - }, !isLast); - - this.actions.setDrawingShape(this.type, this.options); - } - - /** - * Change shape color - * @param {string} color - fill color - * @private - */ - _changeFillColorHandler(color) { - color = color || 'transparent'; - this.options.fill = color; - this.actions.changeShape({ - fill: color - }); - } - - /** - * Change shape stroke color - * @param {string} color - fill color - * @private - */ - _changeStrokeColorHandler(color) { - color = color || 'transparent'; - this.options.stroke = color; - this.actions.changeShape({ - stroke: color - }); + this._els.strokeRange.max = strokeMaxValue; + } + + /** + * Set stroke value + * @param {number} value - expect value for strokeRange change + */ + setStrokeValue(value) { + this._els.strokeRange.value = value; + this._els.strokeRange.trigger('change'); + } + + /** + * Get stroke value + * @returns {number} - stroke range value + */ + getStrokeValue() { + return this._els.strokeRange.value; + } + + /** + * Change icon color + * @param {object} event - add button event object + * @private + */ + _changeShapeHandler(event) { + const button = event.target.closest('.tui-image-editor-button'); + if (button) { + this.actions.stopDrawingMode(); + this.actions.discardSelection(); + const shapeType = this.getButtonType(button, ['circle', 'triangle', 'rect']); + + if (this.type === shapeType) { + this.changeStandbyMode(); + + return; + } + this.changeStandbyMode(); + this.type = shapeType; + event.currentTarget.classList.add(shapeType); + this.actions.changeSelectableAll(false); + this.actions.modeChange('shape'); } + } + + /** + * Change stroke range + * @param {number} value - stroke range value + * @param {boolean} isLast - Is last change + * @private + */ + _changeStrokeRangeHandler(value, isLast) { + this.options.strokeWidth = toInteger(value); + this.actions.changeShape( + { + strokeWidth: value, + }, + !isLast + ); + + this.actions.setDrawingShape(this.type, this.options); + } + + /** + * Change shape color + * @param {string} color - fill color + * @private + */ + _changeFillColorHandler(color) { + color = color || 'transparent'; + this.options.fill = color; + this.actions.changeShape({ + fill: color, + }); + } + + /** + * Change shape stroke color + * @param {string} color - fill color + * @private + */ + _changeStrokeColorHandler(color) { + color = color || 'transparent'; + this.options.stroke = color; + this.actions.changeShape({ + stroke: color, + }); + } } export default Shape; diff --git a/src/js/ui/submenuBase.js b/src/js/ui/submenuBase.js index 8b5552007..40e651b2f 100644 --- a/src/js/ui/submenuBase.js +++ b/src/js/ui/submenuBase.js @@ -4,103 +4,106 @@ * @ignore */ class Submenu { - /** - * @param {HTMLElement} subMenuElement - submenu dom element - * @param {Locale} locale - translate text - * @param {string} name - name of sub menu - * @param {Object} iconStyle - style of icon - * @param {string} menuBarPosition - position of menu - * @param {*} templateHtml - template for SubMenuElement - * @param {boolean} [usageStatistics=false] - template for SubMenuElement - */ - constructor(subMenuElement, {locale, name, makeSvgIcon, menuBarPosition, templateHtml, usageStatistics}) { - this.subMenuElement = subMenuElement; - this.menuBarPosition = menuBarPosition; - this.toggleDirection = menuBarPosition === 'top' ? 'down' : 'up'; - this.colorPickerControls = []; - this.usageStatistics = usageStatistics; - this.eventHandler = {}; - this._makeSubMenuElement({ - locale, - name, - makeSvgIcon, - templateHtml - }); - } + /** + * @param {HTMLElement} subMenuElement - submenu dom element + * @param {Locale} locale - translate text + * @param {string} name - name of sub menu + * @param {Object} iconStyle - style of icon + * @param {string} menuBarPosition - position of menu + * @param {*} templateHtml - template for SubMenuElement + * @param {boolean} [usageStatistics=false] - template for SubMenuElement + */ + constructor( + subMenuElement, + { locale, name, makeSvgIcon, menuBarPosition, templateHtml, usageStatistics } + ) { + this.subMenuElement = subMenuElement; + this.menuBarPosition = menuBarPosition; + this.toggleDirection = menuBarPosition === 'top' ? 'down' : 'up'; + this.colorPickerControls = []; + this.usageStatistics = usageStatistics; + this.eventHandler = {}; + this._makeSubMenuElement({ + locale, + name, + makeSvgIcon, + templateHtml, + }); + } - /** - * editor dom ui query selector - * @param {string} selectName - query selector string name - * @returns {HTMLElement} - */ - selector(selectName) { - return this.subMenuElement.querySelector(selectName); - } + /** + * editor dom ui query selector + * @param {string} selectName - query selector string name + * @returns {HTMLElement} + */ + selector(selectName) { + return this.subMenuElement.querySelector(selectName); + } - /** - * change show state change for colorpicker instance - * @param {Colorpicker} occurredControl - target Colorpicker Instance - */ - colorPickerChangeShow(occurredControl) { - this.colorPickerControls.forEach(pickerControl => { - if (occurredControl !== pickerControl) { - pickerControl.hide(); - } - }); - } + /** + * change show state change for colorpicker instance + * @param {Colorpicker} occurredControl - target Colorpicker Instance + */ + colorPickerChangeShow(occurredControl) { + this.colorPickerControls.forEach((pickerControl) => { + if (occurredControl !== pickerControl) { + pickerControl.hide(); + } + }); + } - /** - * Get butten type - * @param {HTMLElement} button - event target element - * @param {array} buttonNames - Array of button names - * @returns {string} - button type - */ - getButtonType(button, buttonNames) { - return button.className.match(RegExp(`(${buttonNames.join('|')})`))[0]; - } + /** + * Get butten type + * @param {HTMLElement} button - event target element + * @param {array} buttonNames - Array of button names + * @returns {string} - button type + */ + getButtonType(button, buttonNames) { + return button.className.match(RegExp(`(${buttonNames.join('|')})`))[0]; + } - /** - * Get butten type - * @param {HTMLElement} target - event target element - * @param {string} removeClass - remove class name - * @param {string} addClass - add class name - */ - changeClass(target, removeClass, addClass) { - target.classList.remove(removeClass); - target.classList.add(addClass); - } + /** + * Get butten type + * @param {HTMLElement} target - event target element + * @param {string} removeClass - remove class name + * @param {string} addClass - add class name + */ + changeClass(target, removeClass, addClass) { + target.classList.remove(removeClass); + target.classList.add(addClass); + } - /** - * Interface method whose implementation is optional. - * Returns the menu to its default state. - */ - changeStandbyMode() {} + /** + * Interface method whose implementation is optional. + * Returns the menu to its default state. + */ + changeStandbyMode() {} - /** - * Interface method whose implementation is optional. - * Executed when the menu starts. - */ - changeStartMode() {} + /** + * Interface method whose implementation is optional. + * Executed when the menu starts. + */ + changeStartMode() {} - /** - * Make submenu dom element - * @param {Locale} locale - translate text - * @param {string} name - submenu name - * @param {Object} iconStyle - icon style - * @param {*} templateHtml - template for SubMenuElement - * @private - */ - _makeSubMenuElement({locale, name, iconStyle, makeSvgIcon, templateHtml}) { - const iconSubMenu = document.createElement('div'); - iconSubMenu.className = `tui-image-editor-menu-${name}`; - iconSubMenu.innerHTML = templateHtml({ - locale, - iconStyle, - makeSvgIcon - }); + /** + * Make submenu dom element + * @param {Locale} locale - translate text + * @param {string} name - submenu name + * @param {Object} iconStyle - icon style + * @param {*} templateHtml - template for SubMenuElement + * @private + */ + _makeSubMenuElement({ locale, name, iconStyle, makeSvgIcon, templateHtml }) { + const iconSubMenu = document.createElement('div'); + iconSubMenu.className = `tui-image-editor-menu-${name}`; + iconSubMenu.innerHTML = templateHtml({ + locale, + iconStyle, + makeSvgIcon, + }); - this.subMenuElement.appendChild(iconSubMenu); - } + this.subMenuElement.appendChild(iconSubMenu); + } } export default Submenu; diff --git a/src/js/ui/template/controls.js b/src/js/ui/template/controls.js index 71682d213..68ebf57f8 100644 --- a/src/js/ui/template/controls.js +++ b/src/js/ui/template/controls.js @@ -1,4 +1,4 @@ -export default ({locale, biImage, loadButtonStyle, downloadButtonStyle}) => (` +export default ({ locale, biImage, loadButtonStyle, downloadButtonStyle }) => `
-`); +`; diff --git a/src/js/ui/template/mainContainer.js b/src/js/ui/template/mainContainer.js index 993f275b0..5c2f38624 100644 --- a/src/js/ui/template/mainContainer.js +++ b/src/js/ui/template/mainContainer.js @@ -1,4 +1,12 @@ -export default ({locale, biImage, commonStyle, headerStyle, loadButtonStyle, downloadButtonStyle, submenuStyle}) => (` +export default ({ + locale, + biImage, + commonStyle, + headerStyle, + loadButtonStyle, + downloadButtonStyle, + submenuStyle, +}) => `
-`); +`; diff --git a/src/js/ui/template/style.js b/src/js/ui/template/style.js index 9c96b8c59..efe0b2cdc 100644 --- a/src/js/ui/template/style.js +++ b/src/js/ui/template/style.js @@ -1,25 +1,25 @@ export default ({ - subMenuLabelActive, - subMenuLabelNormal, - subMenuRangeTitle, - submenuPartitionVertical, - submenuPartitionHorizontal, - submenuCheckbox, - submenuRangePointer, - submenuRangeValue, - submenuColorpickerTitle, - submenuColorpickerButton, - submenuRangeBar, - submenuRangeSubbar, - submenuDisabledRangePointer, - submenuDisabledRangeBar, - submenuDisabledRangeSubbar, - submenuIconSize, - menuIconSize, - biSize, - menuIconStyle, - submenuIconStyle -}) => (` + subMenuLabelActive, + subMenuLabelNormal, + subMenuRangeTitle, + submenuPartitionVertical, + submenuPartitionHorizontal, + submenuCheckbox, + submenuRangePointer, + submenuRangeValue, + submenuColorpickerTitle, + submenuColorpickerButton, + submenuRangeBar, + submenuRangeSubbar, + submenuDisabledRangePointer, + submenuDisabledRangeBar, + submenuDisabledRangeSubbar, + submenuIconSize, + menuIconSize, + biSize, + menuIconStyle, + submenuIconStyle, +}) => ` .tie-icon-add-button.icon-bubble .tui-image-editor-button[data-icontype="icon-bubble"] label, .tie-icon-add-button.icon-heart .tui-image-editor-button[data-icontype="icon-heart"] label, .tie-icon-add-button.icon-location .tui-image-editor-button[data-icontype="icon-location"] label, @@ -137,4 +137,4 @@ export default ({ fill: ${submenuIconStyle.active.color}; stroke: ${submenuIconStyle.active.color}; } -`); +`; diff --git a/src/js/ui/template/submenu/crop.js b/src/js/ui/template/submenu/crop.js index 9b2ee3a5e..42afb144c 100644 --- a/src/js/ui/template/submenu/crop.js +++ b/src/js/ui/template/submenu/crop.js @@ -4,7 +4,7 @@ * @param {Function} makeSvgIcon - svg icon generator * @returns {string} */ -export default ({locale, makeSvgIcon}) => (` +export default ({ locale, makeSvgIcon }) => `
  • @@ -70,4 +70,4 @@ export default ({locale, makeSvgIcon}) => (`
-`); +`; diff --git a/src/js/ui/template/submenu/draw.js b/src/js/ui/template/submenu/draw.js index 4708c1701..cb2e9f9a1 100644 --- a/src/js/ui/template/submenu/draw.js +++ b/src/js/ui/template/submenu/draw.js @@ -4,7 +4,7 @@ * @param {Function} makeSvgIcon - svg icon generator * @returns {string} */ -export default ({locale, makeSvgIcon}) => (` +export default ({ locale, makeSvgIcon }) => `
  • @@ -39,4 +39,4 @@ export default ({locale, makeSvgIcon}) => (`
-`); +`; diff --git a/src/js/ui/template/submenu/filter.js b/src/js/ui/template/submenu/filter.js index 0326ab701..3e69d02f6 100644 --- a/src/js/ui/template/submenu/filter.js +++ b/src/js/ui/template/submenu/filter.js @@ -2,7 +2,7 @@ * @param {Locale} locale - Translate text * @returns {string} */ -export default ({locale}) => (` +export default ({ locale }) => `
  • @@ -154,4 +154,4 @@ export default ({locale}) => (`
-`); +`; diff --git a/src/js/ui/template/submenu/flip.js b/src/js/ui/template/submenu/flip.js index 6f0350a74..16f27debe 100644 --- a/src/js/ui/template/submenu/flip.js +++ b/src/js/ui/template/submenu/flip.js @@ -4,7 +4,7 @@ * @param {Function} makeSvgIcon - svg icon generator * @returns {string} */ -export default ({locale, makeSvgIcon}) => (` +export default ({ locale, makeSvgIcon }) => `
  • @@ -38,4 +38,4 @@ export default ({locale, makeSvgIcon}) => (`
-`); +`; diff --git a/src/js/ui/template/submenu/icon.js b/src/js/ui/template/submenu/icon.js index 23f7f9c32..0a8fe47e3 100644 --- a/src/js/ui/template/submenu/icon.js +++ b/src/js/ui/template/submenu/icon.js @@ -4,7 +4,7 @@ * @param {Function} makeSvgIcon - svg icon generator * @returns {string} */ -export default ({locale, makeSvgIcon}) => (` +export default ({ locale, makeSvgIcon }) => `
  • @@ -105,4 +105,4 @@ export default ({locale, makeSvgIcon}) => (`
-`); +`; diff --git a/src/js/ui/template/submenu/mask.js b/src/js/ui/template/submenu/mask.js index 766512789..86d0f6106 100644 --- a/src/js/ui/template/submenu/mask.js +++ b/src/js/ui/template/submenu/mask.js @@ -4,7 +4,7 @@ * @param {Function} makeSvgIcon - svg icon generator * @returns {string} */ -export default ({locale, makeSvgIcon}) => (` +export default ({ locale, makeSvgIcon }) => `
  • @@ -27,4 +27,4 @@ export default ({locale, makeSvgIcon}) => (`
-`); +`; diff --git a/src/js/ui/template/submenu/rotate.js b/src/js/ui/template/submenu/rotate.js index e877bba30..2ba297029 100644 --- a/src/js/ui/template/submenu/rotate.js +++ b/src/js/ui/template/submenu/rotate.js @@ -4,7 +4,7 @@ * @param {Function} makeSvgIcon - svg icon generator * @returns {string} */ -export default ({locale, makeSvgIcon}) => (` +export default ({ locale, makeSvgIcon }) => `
  • @@ -29,4 +29,4 @@ export default ({locale, makeSvgIcon}) => (`
-`); +`; diff --git a/src/js/ui/template/submenu/shape.js b/src/js/ui/template/submenu/shape.js index 4f0d6a935..3b61c1f13 100644 --- a/src/js/ui/template/submenu/shape.js +++ b/src/js/ui/template/submenu/shape.js @@ -4,7 +4,7 @@ * @param {Function} makeSvgIcon - svg icon generator * @returns {string} */ -export default ({locale, makeSvgIcon}) => (` +export default ({ locale, makeSvgIcon }) => `
  • @@ -42,4 +42,4 @@ export default ({locale, makeSvgIcon}) => (`
-`); +`; diff --git a/src/js/ui/template/submenu/text.js b/src/js/ui/template/submenu/text.js index f712eb485..beb44f2e3 100644 --- a/src/js/ui/template/submenu/text.js +++ b/src/js/ui/template/submenu/text.js @@ -4,7 +4,7 @@ * @param {Function} makeSvgIcon - svg icon generator * @returns {string} */ -export default ({locale, makeSvgIcon}) => (` +export default ({ locale, makeSvgIcon }) => `
  • @@ -64,4 +64,4 @@ export default ({locale, makeSvgIcon}) => (`
-`); +`; diff --git a/src/js/ui/text.js b/src/js/ui/text.js index 89071d8f3..4bbb9a784 100644 --- a/src/js/ui/text.js +++ b/src/js/ui/text.js @@ -1,9 +1,9 @@ -import {assignmentForDestroy} from '../util'; +import { assignmentForDestroy } from '../util'; import Range from './tools/range'; import Colorpicker from './tools/colorpicker'; import Submenu from './submenuBase'; import templateHtml from './template/submenu/text'; -import {defaultTextRangeValus} from '../consts'; +import { defaultTextRangeValus } from '../consts'; /** * Crop ui class @@ -11,232 +11,243 @@ import {defaultTextRangeValus} from '../consts'; * @ignore */ export default class Text extends Submenu { - constructor(subMenuElement, {locale, makeSvgIcon, menuBarPosition, usageStatistics}) { - super(subMenuElement, { - locale, - name: 'text', - makeSvgIcon, - menuBarPosition, - templateHtml, - usageStatistics - }); - this.effect = { - bold: false, - italic: false, - underline: false - }; - this.align = 'left'; - this._els = { - textEffectButton: this.selector('.tie-text-effect-button'), - textAlignButton: this.selector('.tie-text-align-button'), - textColorpicker: new Colorpicker( - this.selector('.tie-text-color'), '#ffbb3b', this.toggleDirection, this.usageStatistics - ), - textRange: new Range({ - slider: this.selector('.tie-text-range'), - input: this.selector('.tie-text-range-value') - }, defaultTextRangeValus) - }; - } - - /** - * Destroys the instance. - */ - destroy() { - this._removeEvent(); - this._els.textColorpicker.destroy(); - this._els.textRange.destroy(); - - assignmentForDestroy(this); - } - - /** - * Add event for text - * @param {Object} actions - actions for text - * @param {Function} actions.changeTextStyle - change text style - */ - addEvent(actions) { - const setTextEffect = this._setTextEffectHandler.bind(this); - const setTextAlign = this._setTextAlignHandler.bind(this); - - this.eventHandler = { - setTextEffect, - setTextAlign - }; - - this.actions = actions; - this._els.textEffectButton.addEventListener('click', setTextEffect); - this._els.textAlignButton.addEventListener('click', setTextAlign); - this._els.textRange.on('change', this._changeTextRnageHandler.bind(this)); - this._els.textColorpicker.on('change', this._changeColorHandler.bind(this)); - } - - /** - * Remove event - * @private - */ - _removeEvent() { - const {setTextEffect, setTextAlign} = this.eventHandler; - - this._els.textEffectButton.removeEventListener('click', setTextEffect); - this._els.textAlignButton.removeEventListener('click', setTextAlign); - this._els.textRange.off(); - this._els.textColorpicker.off(); - } - - /** - * Returns the menu to its default state. - */ - changeStandbyMode() { - this.actions.stopDrawingMode(); - } - - /** - * Executed when the menu starts. - */ - changeStartMode() { - this.actions.modeChange('text'); - } - - set textColor(color) { - this._els.textColorpicker.color = color; - } - - /** - * Get text color - * @returns {string} - text color - */ - get textColor() { - return this._els.textColorpicker.color; - } - - /** - * Get text size - * @returns {string} - text size - */ - get fontSize() { - return this._els.textRange.value; - } - - /** - * Set text size - * @param {Number} value - text size - */ - set fontSize(value) { - this._els.textRange.value = value; - } - - /** - * get font style - * @returns {string} - font style - */ - get fontStyle() { - return this.effect.italic ? 'italic' : 'normal'; - } - - /** - * get font weight - * @returns {string} - font weight - */ - get fontWeight() { - return this.effect.bold ? 'bold' : 'normal'; - } - - /** - * get text underline text underline - * @returns {boolean} - true or false - */ - get underline() { - return this.effect.underline; - } - - setTextStyleStateOnAction(textStyle = {}) { - const {fill, fontSize, fontStyle, fontWeight, textDecoration, textAlign} = textStyle; - - this.textColor = fill; - this.fontSize = fontSize; - this.setEffactState('italic', fontStyle); - this.setEffactState('bold', fontWeight); - this.setEffactState('underline', textDecoration); - this.setAlignState(textAlign); - } - - setEffactState(effactName, value) { - const effactValue = value === 'italic' || value === 'bold' || value === 'underline'; - const button = this._els.textEffectButton.querySelector(`.tui-image-editor-button.${effactName}`); - - this.effect[effactName] = effactValue; - - button.classList[effactValue ? 'add' : 'remove']('active'); - } - - setAlignState(value) { - const button = this._els.textAlignButton; - button.classList.remove(this.align); - button.classList.add(value); - this.align = value; - } - - /** - * text effect set handler - * @param {object} event - add button event object - * @private - */ - _setTextEffectHandler(event) { - const button = event.target.closest('.tui-image-editor-button'); - const [styleType] = button.className.match(/(bold|italic|underline)/); - const styleObj = { - 'bold': {fontWeight: 'bold'}, - 'italic': {fontStyle: 'italic'}, - 'underline': {textDecoration: 'underline'} - }[styleType]; - - this.effect[styleType] = !this.effect[styleType]; - button.classList.toggle('active'); - this.actions.changeTextStyle(styleObj); - } - - /** - * text effect set handler - * @param {object} event - add button event object - * @private - */ - _setTextAlignHandler(event) { - const button = event.target.closest('.tui-image-editor-button'); - if (button) { - const styleType = this.getButtonType(button, ['left', 'center', 'right']); - - event.currentTarget.classList.remove(this.align); - if (this.align !== styleType) { - event.currentTarget.classList.add(styleType); - } - this.actions.changeTextStyle({textAlign: styleType}); - - this.align = styleType; - } - } - - /** - * text align set handler - * @param {number} value - range value - * @param {boolean} isLast - Is last change - * @private - */ - _changeTextRnageHandler(value, isLast) { - this.actions.changeTextStyle({ - fontSize: value - }, !isLast); - } - - /** - * change color handler - * @param {string} color - change color string - * @private - */ - _changeColorHandler(color) { - color = color || 'transparent'; - this.actions.changeTextStyle({ - 'fill': color - }); - } + constructor(subMenuElement, { locale, makeSvgIcon, menuBarPosition, usageStatistics }) { + super(subMenuElement, { + locale, + name: 'text', + makeSvgIcon, + menuBarPosition, + templateHtml, + usageStatistics, + }); + this.effect = { + bold: false, + italic: false, + underline: false, + }; + this.align = 'left'; + this._els = { + textEffectButton: this.selector('.tie-text-effect-button'), + textAlignButton: this.selector('.tie-text-align-button'), + textColorpicker: new Colorpicker( + this.selector('.tie-text-color'), + '#ffbb3b', + this.toggleDirection, + this.usageStatistics + ), + textRange: new Range( + { + slider: this.selector('.tie-text-range'), + input: this.selector('.tie-text-range-value'), + }, + defaultTextRangeValus + ), + }; + } + + /** + * Destroys the instance. + */ + destroy() { + this._removeEvent(); + this._els.textColorpicker.destroy(); + this._els.textRange.destroy(); + + assignmentForDestroy(this); + } + + /** + * Add event for text + * @param {Object} actions - actions for text + * @param {Function} actions.changeTextStyle - change text style + */ + addEvent(actions) { + const setTextEffect = this._setTextEffectHandler.bind(this); + const setTextAlign = this._setTextAlignHandler.bind(this); + + this.eventHandler = { + setTextEffect, + setTextAlign, + }; + + this.actions = actions; + this._els.textEffectButton.addEventListener('click', setTextEffect); + this._els.textAlignButton.addEventListener('click', setTextAlign); + this._els.textRange.on('change', this._changeTextRnageHandler.bind(this)); + this._els.textColorpicker.on('change', this._changeColorHandler.bind(this)); + } + + /** + * Remove event + * @private + */ + _removeEvent() { + const { setTextEffect, setTextAlign } = this.eventHandler; + + this._els.textEffectButton.removeEventListener('click', setTextEffect); + this._els.textAlignButton.removeEventListener('click', setTextAlign); + this._els.textRange.off(); + this._els.textColorpicker.off(); + } + + /** + * Returns the menu to its default state. + */ + changeStandbyMode() { + this.actions.stopDrawingMode(); + } + + /** + * Executed when the menu starts. + */ + changeStartMode() { + this.actions.modeChange('text'); + } + + set textColor(color) { + this._els.textColorpicker.color = color; + } + + /** + * Get text color + * @returns {string} - text color + */ + get textColor() { + return this._els.textColorpicker.color; + } + + /** + * Get text size + * @returns {string} - text size + */ + get fontSize() { + return this._els.textRange.value; + } + + /** + * Set text size + * @param {Number} value - text size + */ + set fontSize(value) { + this._els.textRange.value = value; + } + + /** + * get font style + * @returns {string} - font style + */ + get fontStyle() { + return this.effect.italic ? 'italic' : 'normal'; + } + + /** + * get font weight + * @returns {string} - font weight + */ + get fontWeight() { + return this.effect.bold ? 'bold' : 'normal'; + } + + /** + * get text underline text underline + * @returns {boolean} - true or false + */ + get underline() { + return this.effect.underline; + } + + setTextStyleStateOnAction(textStyle = {}) { + const { fill, fontSize, fontStyle, fontWeight, textDecoration, textAlign } = textStyle; + + this.textColor = fill; + this.fontSize = fontSize; + this.setEffactState('italic', fontStyle); + this.setEffactState('bold', fontWeight); + this.setEffactState('underline', textDecoration); + this.setAlignState(textAlign); + } + + setEffactState(effactName, value) { + const effactValue = value === 'italic' || value === 'bold' || value === 'underline'; + const button = this._els.textEffectButton.querySelector( + `.tui-image-editor-button.${effactName}` + ); + + this.effect[effactName] = effactValue; + + button.classList[effactValue ? 'add' : 'remove']('active'); + } + + setAlignState(value) { + const button = this._els.textAlignButton; + button.classList.remove(this.align); + button.classList.add(value); + this.align = value; + } + + /** + * text effect set handler + * @param {object} event - add button event object + * @private + */ + _setTextEffectHandler(event) { + const button = event.target.closest('.tui-image-editor-button'); + const [styleType] = button.className.match(/(bold|italic|underline)/); + const styleObj = { + bold: { fontWeight: 'bold' }, + italic: { fontStyle: 'italic' }, + underline: { textDecoration: 'underline' }, + }[styleType]; + + this.effect[styleType] = !this.effect[styleType]; + button.classList.toggle('active'); + this.actions.changeTextStyle(styleObj); + } + + /** + * text effect set handler + * @param {object} event - add button event object + * @private + */ + _setTextAlignHandler(event) { + const button = event.target.closest('.tui-image-editor-button'); + if (button) { + const styleType = this.getButtonType(button, ['left', 'center', 'right']); + + event.currentTarget.classList.remove(this.align); + if (this.align !== styleType) { + event.currentTarget.classList.add(styleType); + } + this.actions.changeTextStyle({ textAlign: styleType }); + + this.align = styleType; + } + } + + /** + * text align set handler + * @param {number} value - range value + * @param {boolean} isLast - Is last change + * @private + */ + _changeTextRnageHandler(value, isLast) { + this.actions.changeTextStyle( + { + fontSize: value, + }, + !isLast + ); + } + + /** + * change color handler + * @param {string} color - change color string + * @private + */ + _changeColorHandler(color) { + color = color || 'transparent'; + this.actions.changeTextStyle({ + fill: color, + }); + } } diff --git a/src/js/ui/theme/standard.js b/src/js/ui/theme/standard.js index dcd2941a5..8e2e262be 100644 --- a/src/js/ui/theme/standard.js +++ b/src/js/ui/theme/standard.js @@ -146,80 +146,80 @@ }; */ export default { - 'common.bi.image': 'https://uicdn.toast.com/toastui/img/tui-image-editor-bi.png', - 'common.bisize.width': '251px', - 'common.bisize.height': '21px', - 'common.backgroundImage': 'none', - 'common.backgroundColor': '#1e1e1e', - 'common.border': '0px', - - // header - 'header.backgroundImage': 'none', - 'header.backgroundColor': 'transparent', - 'header.border': '0px', - - // load button - 'loadButton.backgroundColor': '#fff', - 'loadButton.border': '1px solid #ddd', - 'loadButton.color': '#222', - 'loadButton.fontFamily': '\'Noto Sans\', sans-serif', - 'loadButton.fontSize': '12px', - - // download button - 'downloadButton.backgroundColor': '#fdba3b', - 'downloadButton.border': '1px solid #fdba3b', - 'downloadButton.color': '#fff', - 'downloadButton.fontFamily': '\'Noto Sans\', sans-serif', - 'downloadButton.fontSize': '12px', - - // main icons - 'menu.normalIcon.color': '#8a8a8a', - 'menu.activeIcon.color': '#555555', - 'menu.disabledIcon.color': '#434343', - 'menu.hoverIcon.color': '#e9e9e9', - - // submenu icons - 'submenu.normalIcon.color': '#8a8a8a', - 'submenu.activeIcon.color': '#e9e9e9', - - 'menu.iconSize.width': '24px', - 'menu.iconSize.height': '24px', - - 'submenu.iconSize.width': '32px', - 'submenu.iconSize.height': '32px', - - // submenu primary color - 'submenu.backgroundColor': '#1e1e1e', - 'submenu.partition.color': '#3c3c3c', - - // submenu labels - 'submenu.normalLabel.color': '#8a8a8a', - 'submenu.normalLabel.fontWeight': 'lighter', - 'submenu.activeLabel.color': '#fff', - 'submenu.activeLabel.fontWeight': 'lighter', - - // checkbox style - 'checkbox.border': '0px', - 'checkbox.backgroundColor': '#fff', - - // range style - 'range.pointer.color': '#fff', - 'range.bar.color': '#666', - 'range.subbar.color': '#d1d1d1', - - 'range.disabledPointer.color': '#414141', - 'range.disabledBar.color': '#282828', - 'range.disabledSubbar.color': '#414141', - - 'range.value.color': '#fff', - 'range.value.fontWeight': 'lighter', - 'range.value.fontSize': '11px', - 'range.value.border': '1px solid #353535', - 'range.value.backgroundColor': '#151515', - 'range.title.color': '#fff', - 'range.title.fontWeight': 'lighter', - - // colorpicker style - 'colorpicker.button.border': '1px solid #1e1e1e', - 'colorpicker.title.color': '#fff' + 'common.bi.image': 'https://uicdn.toast.com/toastui/img/tui-image-editor-bi.png', + 'common.bisize.width': '251px', + 'common.bisize.height': '21px', + 'common.backgroundImage': 'none', + 'common.backgroundColor': '#1e1e1e', + 'common.border': '0px', + + // header + 'header.backgroundImage': 'none', + 'header.backgroundColor': 'transparent', + 'header.border': '0px', + + // load button + 'loadButton.backgroundColor': '#fff', + 'loadButton.border': '1px solid #ddd', + 'loadButton.color': '#222', + 'loadButton.fontFamily': "'Noto Sans', sans-serif", + 'loadButton.fontSize': '12px', + + // download button + 'downloadButton.backgroundColor': '#fdba3b', + 'downloadButton.border': '1px solid #fdba3b', + 'downloadButton.color': '#fff', + 'downloadButton.fontFamily': "'Noto Sans', sans-serif", + 'downloadButton.fontSize': '12px', + + // main icons + 'menu.normalIcon.color': '#8a8a8a', + 'menu.activeIcon.color': '#555555', + 'menu.disabledIcon.color': '#434343', + 'menu.hoverIcon.color': '#e9e9e9', + + // submenu icons + 'submenu.normalIcon.color': '#8a8a8a', + 'submenu.activeIcon.color': '#e9e9e9', + + 'menu.iconSize.width': '24px', + 'menu.iconSize.height': '24px', + + 'submenu.iconSize.width': '32px', + 'submenu.iconSize.height': '32px', + + // submenu primary color + 'submenu.backgroundColor': '#1e1e1e', + 'submenu.partition.color': '#3c3c3c', + + // submenu labels + 'submenu.normalLabel.color': '#8a8a8a', + 'submenu.normalLabel.fontWeight': 'lighter', + 'submenu.activeLabel.color': '#fff', + 'submenu.activeLabel.fontWeight': 'lighter', + + // checkbox style + 'checkbox.border': '0px', + 'checkbox.backgroundColor': '#fff', + + // range style + 'range.pointer.color': '#fff', + 'range.bar.color': '#666', + 'range.subbar.color': '#d1d1d1', + + 'range.disabledPointer.color': '#414141', + 'range.disabledBar.color': '#282828', + 'range.disabledSubbar.color': '#414141', + + 'range.value.color': '#fff', + 'range.value.fontWeight': 'lighter', + 'range.value.fontSize': '11px', + 'range.value.border': '1px solid #353535', + 'range.value.backgroundColor': '#151515', + 'range.title.color': '#fff', + 'range.title.fontWeight': 'lighter', + + // colorpicker style + 'colorpicker.button.border': '1px solid #1e1e1e', + 'colorpicker.title.color': '#fff', }; diff --git a/src/js/ui/theme/theme.js b/src/js/ui/theme/theme.js index d7cc1a78f..f9d769b4b 100644 --- a/src/js/ui/theme/theme.js +++ b/src/js/ui/theme/theme.js @@ -1,5 +1,5 @@ -import {extend, forEach, map} from 'tui-code-snippet'; -import {styleLoad} from '../../util'; +import { extend, forEach, map } from 'tui-code-snippet'; +import { styleLoad } from '../../util'; import style from '../template/style'; import standardTheme from './standard'; import icon from '../../../svg/default.svg'; @@ -11,225 +11,234 @@ import icon from '../../../svg/default.svg'; * @ignore */ class Theme { - constructor(customTheme) { - this.styles = this._changeToObject(extend({}, standardTheme, customTheme)); - styleLoad(this._styleMaker()); - - this._loadDefaultSvgIcon(); - } - - /** - * Get a Style cssText or StyleObject - * @param {string} type - style type - * @returns {string|object} - cssText or StyleObject - */ - getStyle(type) { // eslint-disable-line - let result = null; - const firstProperty = type.replace(/\..+$/, ''); - const option = this.styles[type]; - switch (type) { - case 'common.bi': - result = this.styles[type].image; - break; - case 'menu.icon': - result = { - active: this.styles[`${firstProperty}.activeIcon`], - normal: this.styles[`${firstProperty}.normalIcon`], - hover: this.styles[`${firstProperty}.hoverIcon`], - disabled: this.styles[`${firstProperty}.disabledIcon`] - }; - break; - case 'submenu.icon': - result = { - active: this.styles[`${firstProperty}.activeIcon`], - normal: this.styles[`${firstProperty}.normalIcon`] - }; - break; - case 'submenu.label': - result = { - active: this._makeCssText(this.styles[`${firstProperty}.activeLabel`]), - normal: this._makeCssText(this.styles[`${firstProperty}.normalLabel`]) - }; - break; - case 'submenu.partition': - result = { - vertical: this._makeCssText(extend({}, option, {borderLeft: `1px solid ${option.color}`})), - horizontal: this._makeCssText(extend({}, option, {borderBottom: `1px solid ${option.color}`})) - }; - break; - - case 'range.disabledPointer': - case 'range.disabledBar': - case 'range.disabledSubbar': - case 'range.pointer': - case 'range.bar': - case 'range.subbar': - option.backgroundColor = option.color; - result = this._makeCssText(option); - break; - default: - result = this._makeCssText(option); - break; - } - - return result; - } - - /** - * Make css resource - * @returns {string} - serialized css text - * @private - */ - _styleMaker() { - const submenuLabelStyle = this.getStyle('submenu.label'); - const submenuPartitionStyle = this.getStyle('submenu.partition'); - - return style({ - subMenuLabelActive: submenuLabelStyle.active, - subMenuLabelNormal: submenuLabelStyle.normal, - submenuPartitionVertical: submenuPartitionStyle.vertical, - submenuPartitionHorizontal: submenuPartitionStyle.horizontal, - biSize: this.getStyle('common.bisize'), - subMenuRangeTitle: this.getStyle('range.title'), - submenuRangePointer: this.getStyle('range.pointer'), - submenuRangeBar: this.getStyle('range.bar'), - submenuRangeSubbar: this.getStyle('range.subbar'), - - submenuDisabledRangePointer: this.getStyle('range.disabledPointer'), - submenuDisabledRangeBar: this.getStyle('range.disabledBar'), - submenuDisabledRangeSubbar: this.getStyle('range.disabledSubbar'), - - submenuRangeValue: this.getStyle('range.value'), - submenuColorpickerTitle: this.getStyle('colorpicker.title'), - submenuColorpickerButton: this.getStyle('colorpicker.button'), - submenuCheckbox: this.getStyle('checkbox'), - menuIconSize: this.getStyle('menu.iconSize'), - submenuIconSize: this.getStyle('submenu.iconSize'), - menuIconStyle: this.getStyle('menu.icon'), - submenuIconStyle: this.getStyle('submenu.icon') - }); - } - - /** - * Change to low dimensional object. - * @param {object} styleOptions - style object of user interface - * @returns {object} low level object for style apply - * @private - */ - _changeToObject(styleOptions) { - const styleObject = {}; - forEach(styleOptions, (value, key) => { - const keyExplode = key.match(/^(.+)\.([a-z]+)$/i); - const [, property, subProperty] = keyExplode; - - if (!styleObject[property]) { - styleObject[property] = {}; - } - styleObject[property][subProperty] = value; - }); - - return styleObject; - } - - /** - * Style object to Csstext serialize - * @param {object} styleObject - style object - * @returns {string} - css text string - * @private - */ - _makeCssText(styleObject) { - const converterStack = []; - - forEach(styleObject, (value, key) => { - if (['backgroundImage'].indexOf(key) > -1 && value !== 'none') { - value = `url(${value})`; - } - - converterStack.push(`${this._toUnderScore(key)}: ${value}`); - }); - - return converterStack.join(';'); - } - - /** - * Camel key string to Underscore string - * @param {string} targetString - change target - * @returns {string} - * @private - */ - _toUnderScore(targetString) { - return targetString.replace(/([A-Z])/g, ($0, $1) => `-${$1.toLowerCase()}`); - } - - /** - * Load defulat svg icon - * @private - */ - _loadDefaultSvgIcon() { - if (!document.getElementById('tui-image-editor-svg-default-icons')) { - const parser = new DOMParser(); - const dom = parser.parseFromString(icon, 'text/xml'); - - document.body.appendChild(dom.documentElement); - } - } - - /** - * Make className for svg icon - * @param {string} iconType - normal' or 'active' or 'hover' or 'disabled - * @param {boolean} isSubmenu - submenu icon or not. - * @returns {string} - * @private - */ - _makeIconClassName(iconType, isSubmenu) { - const iconStyleInfo = isSubmenu ? this.getStyle('submenu.icon') : this.getStyle('menu.icon'); - const {path, name} = iconStyleInfo[iconType]; - - return path && name ? iconType : `${iconType} use-default`; - } - - /** - * Make svg use link path name - * @param {string} iconType - normal' or 'active' or 'hover' or 'disabled - * @param {boolean} isSubmenu - submenu icon or not. - * @returns {string} - * @private - */ - _makeSvgIconPrefix(iconType, isSubmenu) { - const iconStyleInfo = isSubmenu ? this.getStyle('submenu.icon') : this.getStyle('menu.icon'); - const {path, name} = iconStyleInfo[iconType]; - - return path && name ? `${path}#${name}-` : '#'; - } - - /** - * Make svg use link path name - * @param {Array.} useIconTypes - normal' or 'active' or 'hover' or 'disabled - * @param {string} menuName - menu name - * @param {boolean} isSubmenu - submenu icon or not. - * @returns {string} - * @private - */ - _makeSvgItem(useIconTypes, menuName, isSubmenu) { - return map(useIconTypes, iconType => { - const svgIconPrefix = this._makeSvgIconPrefix(iconType, isSubmenu); - const iconName = this._toUnderScore(menuName); - const svgIconClassName = this._makeIconClassName(iconType, isSubmenu); - - return ``; - }).join(''); + constructor(customTheme) { + this.styles = this._changeToObject(extend({}, standardTheme, customTheme)); + styleLoad(this._styleMaker()); + + this._loadDefaultSvgIcon(); + } + + /** + * Get a Style cssText or StyleObject + * @param {string} type - style type + * @returns {string|object} - cssText or StyleObject + */ + getStyle(type) { + // eslint-disable-line + let result = null; + const firstProperty = type.replace(/\..+$/, ''); + const option = this.styles[type]; + switch (type) { + case 'common.bi': + result = this.styles[type].image; + break; + case 'menu.icon': + result = { + active: this.styles[`${firstProperty}.activeIcon`], + normal: this.styles[`${firstProperty}.normalIcon`], + hover: this.styles[`${firstProperty}.hoverIcon`], + disabled: this.styles[`${firstProperty}.disabledIcon`], + }; + break; + case 'submenu.icon': + result = { + active: this.styles[`${firstProperty}.activeIcon`], + normal: this.styles[`${firstProperty}.normalIcon`], + }; + break; + case 'submenu.label': + result = { + active: this._makeCssText(this.styles[`${firstProperty}.activeLabel`]), + normal: this._makeCssText(this.styles[`${firstProperty}.normalLabel`]), + }; + break; + case 'submenu.partition': + result = { + vertical: this._makeCssText( + extend({}, option, { borderLeft: `1px solid ${option.color}` }) + ), + horizontal: this._makeCssText( + extend({}, option, { borderBottom: `1px solid ${option.color}` }) + ), + }; + break; + + case 'range.disabledPointer': + case 'range.disabledBar': + case 'range.disabledSubbar': + case 'range.pointer': + case 'range.bar': + case 'range.subbar': + option.backgroundColor = option.color; + result = this._makeCssText(option); + break; + default: + result = this._makeCssText(option); + break; } - /** - * Make svg icon set - * @param {Array.} useIconTypes - normal' or 'active' or 'hover' or 'disabled - * @param {string} menuName - menu name - * @param {boolean} isSubmenu - submenu icon or not. - * @returns {string} - */ - makeMenSvgIconSet(useIconTypes, menuName, isSubmenu = false) { - return `${this._makeSvgItem(useIconTypes, menuName, isSubmenu)}`; + return result; + } + + /** + * Make css resource + * @returns {string} - serialized css text + * @private + */ + _styleMaker() { + const submenuLabelStyle = this.getStyle('submenu.label'); + const submenuPartitionStyle = this.getStyle('submenu.partition'); + + return style({ + subMenuLabelActive: submenuLabelStyle.active, + subMenuLabelNormal: submenuLabelStyle.normal, + submenuPartitionVertical: submenuPartitionStyle.vertical, + submenuPartitionHorizontal: submenuPartitionStyle.horizontal, + biSize: this.getStyle('common.bisize'), + subMenuRangeTitle: this.getStyle('range.title'), + submenuRangePointer: this.getStyle('range.pointer'), + submenuRangeBar: this.getStyle('range.bar'), + submenuRangeSubbar: this.getStyle('range.subbar'), + + submenuDisabledRangePointer: this.getStyle('range.disabledPointer'), + submenuDisabledRangeBar: this.getStyle('range.disabledBar'), + submenuDisabledRangeSubbar: this.getStyle('range.disabledSubbar'), + + submenuRangeValue: this.getStyle('range.value'), + submenuColorpickerTitle: this.getStyle('colorpicker.title'), + submenuColorpickerButton: this.getStyle('colorpicker.button'), + submenuCheckbox: this.getStyle('checkbox'), + menuIconSize: this.getStyle('menu.iconSize'), + submenuIconSize: this.getStyle('submenu.iconSize'), + menuIconStyle: this.getStyle('menu.icon'), + submenuIconStyle: this.getStyle('submenu.icon'), + }); + } + + /** + * Change to low dimensional object. + * @param {object} styleOptions - style object of user interface + * @returns {object} low level object for style apply + * @private + */ + _changeToObject(styleOptions) { + const styleObject = {}; + forEach(styleOptions, (value, key) => { + const keyExplode = key.match(/^(.+)\.([a-z]+)$/i); + const [, property, subProperty] = keyExplode; + + if (!styleObject[property]) { + styleObject[property] = {}; + } + styleObject[property][subProperty] = value; + }); + + return styleObject; + } + + /** + * Style object to Csstext serialize + * @param {object} styleObject - style object + * @returns {string} - css text string + * @private + */ + _makeCssText(styleObject) { + const converterStack = []; + + forEach(styleObject, (value, key) => { + if (['backgroundImage'].indexOf(key) > -1 && value !== 'none') { + value = `url(${value})`; + } + + converterStack.push(`${this._toUnderScore(key)}: ${value}`); + }); + + return converterStack.join(';'); + } + + /** + * Camel key string to Underscore string + * @param {string} targetString - change target + * @returns {string} + * @private + */ + _toUnderScore(targetString) { + return targetString.replace(/([A-Z])/g, ($0, $1) => `-${$1.toLowerCase()}`); + } + + /** + * Load defulat svg icon + * @private + */ + _loadDefaultSvgIcon() { + if (!document.getElementById('tui-image-editor-svg-default-icons')) { + const parser = new DOMParser(); + const dom = parser.parseFromString(icon, 'text/xml'); + + document.body.appendChild(dom.documentElement); } + } + + /** + * Make className for svg icon + * @param {string} iconType - normal' or 'active' or 'hover' or 'disabled + * @param {boolean} isSubmenu - submenu icon or not. + * @returns {string} + * @private + */ + _makeIconClassName(iconType, isSubmenu) { + const iconStyleInfo = isSubmenu ? this.getStyle('submenu.icon') : this.getStyle('menu.icon'); + const { path, name } = iconStyleInfo[iconType]; + + return path && name ? iconType : `${iconType} use-default`; + } + + /** + * Make svg use link path name + * @param {string} iconType - normal' or 'active' or 'hover' or 'disabled + * @param {boolean} isSubmenu - submenu icon or not. + * @returns {string} + * @private + */ + _makeSvgIconPrefix(iconType, isSubmenu) { + const iconStyleInfo = isSubmenu ? this.getStyle('submenu.icon') : this.getStyle('menu.icon'); + const { path, name } = iconStyleInfo[iconType]; + + return path && name ? `${path}#${name}-` : '#'; + } + + /** + * Make svg use link path name + * @param {Array.} useIconTypes - normal' or 'active' or 'hover' or 'disabled + * @param {string} menuName - menu name + * @param {boolean} isSubmenu - submenu icon or not. + * @returns {string} + * @private + */ + _makeSvgItem(useIconTypes, menuName, isSubmenu) { + return map(useIconTypes, (iconType) => { + const svgIconPrefix = this._makeSvgIconPrefix(iconType, isSubmenu); + const iconName = this._toUnderScore(menuName); + const svgIconClassName = this._makeIconClassName(iconType, isSubmenu); + + return ``; + }).join(''); + } + + /** + * Make svg icon set + * @param {Array.} useIconTypes - normal' or 'active' or 'hover' or 'disabled + * @param {string} menuName - menu name + * @param {boolean} isSubmenu - submenu icon or not. + * @returns {string} + */ + makeMenSvgIconSet(useIconTypes, menuName, isSubmenu = false) { + return `${this._makeSvgItem( + useIconTypes, + menuName, + isSubmenu + )}`; + } } export default Theme; diff --git a/src/js/ui/tools/colorpicker.js b/src/js/ui/tools/colorpicker.js index e5899fa41..9abc2d20a 100644 --- a/src/js/ui/tools/colorpicker.js +++ b/src/js/ui/tools/colorpicker.js @@ -1,22 +1,22 @@ import snippet from 'tui-code-snippet'; import tuiColorPicker from 'tui-color-picker'; const PICKER_COLOR = [ - '#000000', - '#2a2a2a', - '#545454', - '#7e7e7e', - '#a8a8a8', - '#d2d2d2', - '#ffffff', - '', - '#ff4040', - '#ff6518', - '#ffbb3b', - '#03bd9e', - '#00a9ff', - '#515ce6', - '#9e5fff', - '#ff5583' + '#000000', + '#2a2a2a', + '#545454', + '#7e7e7e', + '#a8a8a8', + '#d2d2d2', + '#ffffff', + '', + '#ff4040', + '#ff6518', + '#ffbb3b', + '#03bd9e', + '#00a9ff', + '#515ce6', + '#9e5fff', + '#ff5583', ]; /** @@ -25,219 +25,224 @@ const PICKER_COLOR = [ * @ignore */ class Colorpicker { - constructor(colorpickerElement, defaultColor = '#7e7e7e', toggleDirection = 'up', usageStatistics) { - this.colorpickerElement = colorpickerElement; - this.usageStatistics = usageStatistics; - - this._show = false; - - this._colorpickerElement = colorpickerElement; - this._toggleDirection = toggleDirection; - this._makePickerButtonElement(defaultColor); - this._makePickerLayerElement(colorpickerElement, colorpickerElement.getAttribute('title')); - this._color = defaultColor; - this.picker = tuiColorPicker.create({ - container: this.pickerElement, - preset: PICKER_COLOR, - color: defaultColor, - usageStatistics: this.usageStatistics - }); - - this._addEvent(); + constructor( + colorpickerElement, + defaultColor = '#7e7e7e', + toggleDirection = 'up', + usageStatistics + ) { + this.colorpickerElement = colorpickerElement; + this.usageStatistics = usageStatistics; + + this._show = false; + + this._colorpickerElement = colorpickerElement; + this._toggleDirection = toggleDirection; + this._makePickerButtonElement(defaultColor); + this._makePickerLayerElement(colorpickerElement, colorpickerElement.getAttribute('title')); + this._color = defaultColor; + this.picker = tuiColorPicker.create({ + container: this.pickerElement, + preset: PICKER_COLOR, + color: defaultColor, + usageStatistics: this.usageStatistics, + }); + + this._addEvent(); + } + + /** + * Destroys the instance. + */ + destroy() { + this._removeEvent(); + this.picker.destroy(); + this.colorpickerElement.innerHTML = ''; + snippet.forEach(this, (value, key) => { + this[key] = null; + }); + } + + /** + * Get color + * @returns {Number} color value + */ + get color() { + return this._color; + } + + /** + * Set color + * @param {string} color color value + */ + set color(color) { + this._color = color; + this._changeColorElement(color); + } + + /** + * Change color element + * @param {string} color color value + * #private + */ + _changeColorElement(color) { + if (color) { + this.colorElement.classList.remove('transparent'); + this.colorElement.style.backgroundColor = color; + } else { + this.colorElement.style.backgroundColor = '#fff'; + this.colorElement.classList.add('transparent'); } - - /** - * Destroys the instance. - */ - destroy() { - this._removeEvent(); - this.picker.destroy(); - this.colorpickerElement.innerHTML = ''; - snippet.forEach(this, (value, key) => { - this[key] = null; - }); - } - - /** - * Get color - * @returns {Number} color value - */ - get color() { - return this._color; + } + + /** + * Make picker button element + * @param {string} defaultColor color value + * @private + */ + _makePickerButtonElement(defaultColor) { + this.colorpickerElement.classList.add('tui-image-editor-button'); + + this.colorElement = document.createElement('div'); + this.colorElement.className = 'color-picker-value'; + if (defaultColor) { + this.colorElement.style.backgroundColor = defaultColor; + } else { + this.colorElement.classList.add('transparent'); } - - /** - * Set color - * @param {string} color color value - */ - set color(color) { - this._color = color; - this._changeColorElement(color); + } + + /** + * Make picker layer element + * @param {HTMLElement} colorpickerElement color picker element + * @param {string} title picker title + * @private + */ + _makePickerLayerElement(colorpickerElement, title) { + const label = document.createElement('label'); + const triangle = document.createElement('div'); + + this.pickerControl = document.createElement('div'); + this.pickerControl.className = 'color-picker-control'; + + this.pickerElement = document.createElement('div'); + this.pickerElement.className = 'color-picker'; + + label.innerHTML = title; + triangle.className = 'triangle'; + + this.pickerControl.appendChild(this.pickerElement); + this.pickerControl.appendChild(triangle); + + colorpickerElement.appendChild(this.pickerControl); + colorpickerElement.appendChild(this.colorElement); + colorpickerElement.appendChild(label); + } + + /** + * Add event + * @private + */ + _addEvent() { + this.picker.on('selectColor', (value) => { + this._changeColorElement(value.color); + this._color = value.color; + this.fire('change', value.color); + }); + + this.eventHandler = { + pickerToggle: this._pickerToggleEventHandler.bind(this), + pickerHide: () => this.hide(), + }; + + this.colorpickerElement.addEventListener('click', this.eventHandler.pickerToggle); + document.body.addEventListener('click', this.eventHandler.pickerHide); + } + + /** + * Remove event + * @private + */ + _removeEvent() { + this.colorpickerElement.removeEventListener('click', this.eventHandler.pickerToggle); + document.body.removeEventListener('click', this.eventHandler.pickerHide); + this.picker.off(); + } + + /** + * Picker toggle event handler + * @param {object} event - change event + * @private + */ + _pickerToggleEventHandler(event) { + const { target } = event; + const isInPickerControl = target && this._isElementInColorPickerControl(target); + + if (!isInPickerControl || (isInPickerControl && this._isPaletteButton(target))) { + this._show = !this._show; + this.pickerControl.style.display = this._show ? 'block' : 'none'; + this._setPickerControlPosition(); + this.fire('changeShow', this); } - - /** - * Change color element - * @param {string} color color value - * #private - */ - _changeColorElement(color) { - if (color) { - this.colorElement.classList.remove('transparent'); - this.colorElement.style.backgroundColor = color; - } else { - this.colorElement.style.backgroundColor = '#fff'; - this.colorElement.classList.add('transparent'); - } + event.stopPropagation(); + } + + /** + * Check hex input or not + * @param {Element} target - Event target element + * @returns {boolean} + * @private + */ + _isPaletteButton(target) { + return target.className === 'tui-colorpicker-palette-button'; + } + + /** + * Check given element is in pickerControl element + * @param {Element} element - element to check + * @returns {boolean} + * @private + */ + _isElementInColorPickerControl(element) { + let parentNode = element; + + while (parentNode !== document.body) { + if (!parentNode) { + break; + } + + if (parentNode === this.pickerControl) { + return true; + } + + parentNode = parentNode.parentNode; } - /** - * Make picker button element - * @param {string} defaultColor color value - * @private - */ - _makePickerButtonElement(defaultColor) { - this.colorpickerElement.classList.add('tui-image-editor-button'); - - this.colorElement = document.createElement('div'); - this.colorElement.className = 'color-picker-value'; - if (defaultColor) { - this.colorElement.style.backgroundColor = defaultColor; - } else { - this.colorElement.classList.add('transparent'); - } + return false; + } + + hide() { + this._show = false; + this.pickerControl.style.display = 'none'; + } + + /** + * Set picker control position + * @private + */ + _setPickerControlPosition() { + const controlStyle = this.pickerControl.style; + const halfPickerWidth = this._colorpickerElement.clientWidth / 2 + 2; + const left = this.pickerControl.offsetWidth / 2 - halfPickerWidth; + let top = (this.pickerControl.offsetHeight + 10) * -1; + + if (this._toggleDirection === 'down') { + top = 30; } - /** - * Make picker layer element - * @param {HTMLElement} colorpickerElement color picker element - * @param {string} title picker title - * @private - */ - _makePickerLayerElement(colorpickerElement, title) { - const label = document.createElement('label'); - const triangle = document.createElement('div'); - - this.pickerControl = document.createElement('div'); - this.pickerControl.className = 'color-picker-control'; - - this.pickerElement = document.createElement('div'); - this.pickerElement.className = 'color-picker'; - - label.innerHTML = title; - triangle.className = 'triangle'; - - this.pickerControl.appendChild(this.pickerElement); - this.pickerControl.appendChild(triangle); - - colorpickerElement.appendChild(this.pickerControl); - colorpickerElement.appendChild(this.colorElement); - colorpickerElement.appendChild(label); - } - - /** - * Add event - * @private - */ - _addEvent() { - this.picker.on('selectColor', value => { - this._changeColorElement(value.color); - this._color = value.color; - this.fire('change', value.color); - }); - - this.eventHandler = { - pickerToggle: this._pickerToggleEventHandler.bind(this), - pickerHide: () => this.hide() - }; - - this.colorpickerElement.addEventListener('click', this.eventHandler.pickerToggle); - document.body.addEventListener('click', this.eventHandler.pickerHide); - } - - /** - * Remove event - * @private - */ - _removeEvent() { - this.colorpickerElement.removeEventListener('click', this.eventHandler.pickerToggle); - document.body.removeEventListener('click', this.eventHandler.pickerHide); - this.picker.off(); - } - - /** - * Picker toggle event handler - * @param {object} event - change event - * @private - */ - _pickerToggleEventHandler(event) { - const {target} = event; - const isInPickerControl = target && this._isElementInColorPickerControl(target); - - if (!isInPickerControl || (isInPickerControl && this._isPaletteButton(target))) { - this._show = !this._show; - this.pickerControl.style.display = this._show ? 'block' : 'none'; - this._setPickerControlPosition(); - this.fire('changeShow', this); - } - event.stopPropagation(); - } - - /** - * Check hex input or not - * @param {Element} target - Event target element - * @returns {boolean} - * @private - */ - _isPaletteButton(target) { - return target.className === 'tui-colorpicker-palette-button'; - } - - /** - * Check given element is in pickerControl element - * @param {Element} element - element to check - * @returns {boolean} - * @private - */ - _isElementInColorPickerControl(element) { - let parentNode = element; - - while (parentNode !== document.body) { - if (!parentNode) { - break; - } - - if (parentNode === this.pickerControl) { - return true; - } - - parentNode = parentNode.parentNode; - } - - return false; - } - - hide() { - this._show = false; - this.pickerControl.style.display = 'none'; - } - - /** - * Set picker control position - * @private - */ - _setPickerControlPosition() { - const controlStyle = this.pickerControl.style; - const halfPickerWidth = (this._colorpickerElement.clientWidth / 2) + 2; - const left = (this.pickerControl.offsetWidth / 2) - halfPickerWidth; - let top = (this.pickerControl.offsetHeight + 10) * -1; - - if (this._toggleDirection === 'down') { - top = 30; - } - - controlStyle.top = `${top}px`; - controlStyle.left = `-${left}px`; - } + controlStyle.top = `${top}px`; + controlStyle.left = `-${left}px`; + } } snippet.CustomEvents.mixin(Colorpicker); diff --git a/src/js/ui/tools/range.js b/src/js/ui/tools/range.js index 11e026c38..20fcf33eb 100644 --- a/src/js/ui/tools/range.js +++ b/src/js/ui/tools/range.js @@ -1,6 +1,6 @@ import snippet from 'tui-code-snippet'; -import {toInteger, clamp} from '../../util'; -import {keyCodes} from '../../consts'; +import { toInteger, clamp } from '../../util'; +import { keyCodes } from '../../consts'; const INPUT_FILTER_REGEXP = /(-?)([0-9]*)[^0-9]*([0-9]*)/g; @@ -10,346 +10,346 @@ const INPUT_FILTER_REGEXP = /(-?)([0-9]*)[^0-9]*([0-9]*)/g; * @ignore */ class Range { - /** - * @constructor - * @extends {View} - * @param {Object} rangeElements - Html resources for creating sliders - * @param {HTMLElement} rangeElements.slider - b - * @param {HTMLElement} [rangeElements.input] - c - * @param {Object} options - Slider make options - * @param {number} options.min - min value - * @param {number} options.max - max value - * @param {number} options.value - default value - * @param {number} [options.useDecimal] - Decimal point processing. - * @param {number} [options.realTimeEvent] - Reflect live events. - */ - constructor(rangeElements, options = {}) { - this._value = options.value || 0; - - this.rangeElement = rangeElements.slider; - this.rangeInputElement = rangeElements.input; - - this._drawRangeElement(); - - this.rangeWidth = this._getRangeWidth(); - this._min = options.min || 0; - this._max = options.max || 100; - this._useDecimal = options.useDecimal; - this._absMax = (this._min * -1) + this._max; - this.realTimeEvent = options.realTimeEvent || false; - - this.eventHandler = { - startChangingSlide: this._startChangingSlide.bind(this), - stopChangingSlide: this._stopChangingSlide.bind(this), - changeSlide: this._changeSlide.bind(this), - changeSlideFinally: this._changeSlideFinally.bind(this), - changeInput: this._changeValueWithInput.bind(this, false), - changeInputFinally: this._changeValueWithInput.bind(this, true), - changeInputWithArrow: this._changeValueWithInputKeyEvent.bind(this) - }; - - this._addClickEvent(); - this._addDragEvent(); - this._addInputEvent(); - this.value = options.value; - this.trigger('change'); + /** + * @constructor + * @extends {View} + * @param {Object} rangeElements - Html resources for creating sliders + * @param {HTMLElement} rangeElements.slider - b + * @param {HTMLElement} [rangeElements.input] - c + * @param {Object} options - Slider make options + * @param {number} options.min - min value + * @param {number} options.max - max value + * @param {number} options.value - default value + * @param {number} [options.useDecimal] - Decimal point processing. + * @param {number} [options.realTimeEvent] - Reflect live events. + */ + constructor(rangeElements, options = {}) { + this._value = options.value || 0; + + this.rangeElement = rangeElements.slider; + this.rangeInputElement = rangeElements.input; + + this._drawRangeElement(); + + this.rangeWidth = this._getRangeWidth(); + this._min = options.min || 0; + this._max = options.max || 100; + this._useDecimal = options.useDecimal; + this._absMax = this._min * -1 + this._max; + this.realTimeEvent = options.realTimeEvent || false; + + this.eventHandler = { + startChangingSlide: this._startChangingSlide.bind(this), + stopChangingSlide: this._stopChangingSlide.bind(this), + changeSlide: this._changeSlide.bind(this), + changeSlideFinally: this._changeSlideFinally.bind(this), + changeInput: this._changeValueWithInput.bind(this, false), + changeInputFinally: this._changeValueWithInput.bind(this, true), + changeInputWithArrow: this._changeValueWithInputKeyEvent.bind(this), + }; + + this._addClickEvent(); + this._addDragEvent(); + this._addInputEvent(); + this.value = options.value; + this.trigger('change'); + } + + /** + * Destroys the instance. + */ + destroy() { + this._removeClickEvent(); + this._removeDragEvent(); + this._removeInputEvent(); + this.rangeElement.innerHTML = ''; + snippet.forEach(this, (value, key) => { + this[key] = null; + }); + } + + /** + * Set range max value and re position cursor + * @param {number} maxValue - max value + */ + set max(maxValue) { + this._max = maxValue; + this._absMax = this._min * -1 + this._max; + this.value = this._value; + } + + get max() { + return this._max; + } + + /** + * Get range value + * @returns {Number} range value + */ + get value() { + return this._value; + } + + /** + * Set range value + * @param {Number} value range value + * @param {Boolean} fire whether fire custom event or not + */ + set value(value) { + value = this._useDecimal ? value : toInteger(value); + + const absValue = value - this._min; + let leftPosition = (absValue * this.rangeWidth) / this._absMax; + + if (this.rangeWidth < leftPosition) { + leftPosition = this.rangeWidth; } - /** - * Destroys the instance. - */ - destroy() { - this._removeClickEvent(); - this._removeDragEvent(); - this._removeInputEvent(); - this.rangeElement.innerHTML = ''; - snippet.forEach(this, (value, key) => { - this[key] = null; - }); - } + this.pointer.style.left = `${leftPosition}px`; + this.subbar.style.right = `${this.rangeWidth - leftPosition}px`; - /** - * Set range max value and re position cursor - * @param {number} maxValue - max value - */ - set max(maxValue) { - this._max = maxValue; - this._absMax = (this._min * -1) + this._max; - this.value = this._value; + this._value = value; + if (this.rangeInputElement) { + this.rangeInputElement.value = value; } - - get max() { - return this._max; + } + + /** + * event tirigger + * @param {string} type - type + */ + trigger(type) { + this.fire(type, this._value); + } + + /** + * Calculate slider width + * @returns {number} - slider width + */ + _getRangeWidth() { + const getElementWidth = (element) => toInteger(window.getComputedStyle(element, null).width); + + return getElementWidth(this.rangeElement) - getElementWidth(this.pointer); + } + + /** + * Make range element + * @private + */ + _drawRangeElement() { + this.rangeElement.classList.add('tui-image-editor-range'); + + this.bar = document.createElement('div'); + this.bar.className = 'tui-image-editor-virtual-range-bar'; + + this.subbar = document.createElement('div'); + this.subbar.className = 'tui-image-editor-virtual-range-subbar'; + + this.pointer = document.createElement('div'); + this.pointer.className = 'tui-image-editor-virtual-range-pointer'; + + this.bar.appendChild(this.subbar); + this.bar.appendChild(this.pointer); + this.rangeElement.appendChild(this.bar); + } + + /** + * Add range input editing event + * @private + */ + _addInputEvent() { + if (this.rangeInputElement) { + this.rangeInputElement.addEventListener('keydown', this.eventHandler.changeInputWithArrow); + this.rangeInputElement.addEventListener('keyup', this.eventHandler.changeInput); + this.rangeInputElement.addEventListener('blur', this.eventHandler.changeInputFinally); } - - /** - * Get range value - * @returns {Number} range value - */ - get value() { - return this._value; + } + + /** + * Remove range input editing event + * @private + */ + _removeInputEvent() { + if (this.rangeInputElement) { + this.rangeInputElement.removeEventListener('keydown', this.eventHandler.changeInputWithArrow); + this.rangeInputElement.removeEventListener('keyup', this.eventHandler.changeInput); + this.rangeInputElement.removeEventListener('blur', this.eventHandler.changeInputFinally); } - - /** - * Set range value - * @param {Number} value range value - * @param {Boolean} fire whether fire custom event or not - */ - set value(value) { - value = this._useDecimal ? value : toInteger(value); - - const absValue = value - this._min; - let leftPosition = (absValue * this.rangeWidth) / this._absMax; - - if (this.rangeWidth < leftPosition) { - leftPosition = this.rangeWidth; - } - - this.pointer.style.left = `${leftPosition}px`; - this.subbar.style.right = `${this.rangeWidth - leftPosition}px`; - - this._value = value; - if (this.rangeInputElement) { - this.rangeInputElement.value = value; - } + } + + /** + * change angle event + * @param {object} event - key event + * @private + */ + _changeValueWithInputKeyEvent(event) { + const { keyCode, target } = event; + + if ([keyCodes.ARROW_UP, keyCodes.ARROW_DOWN].indexOf(keyCode) < 0) { + return; } - /** - * event tirigger - * @param {string} type - type - */ - trigger(type) { - this.fire(type, this._value); - } + let value = Number(target.value); - /** - * Calculate slider width - * @returns {number} - slider width - */ - _getRangeWidth() { - const getElementWidth = element => toInteger(window.getComputedStyle(element, null).width); + value = this._valueUpDownForKeyEvent(value, keyCode); - return getElementWidth(this.rangeElement) - getElementWidth(this.pointer); - } + const unChanged = value < this._min || value > this._max; - /** - * Make range element - * @private - */ - _drawRangeElement() { - this.rangeElement.classList.add('tui-image-editor-range'); - - this.bar = document.createElement('div'); - this.bar.className = 'tui-image-editor-virtual-range-bar'; - - this.subbar = document.createElement('div'); - this.subbar.className = 'tui-image-editor-virtual-range-subbar'; - - this.pointer = document.createElement('div'); - this.pointer.className = 'tui-image-editor-virtual-range-pointer'; - - this.bar.appendChild(this.subbar); - this.bar.appendChild(this.pointer); - this.rangeElement.appendChild(this.bar); + if (!unChanged) { + const clampValue = clamp(value, this._min, this.max); + this.value = clampValue; + this.fire('change', clampValue, false); } - - /** - * Add range input editing event - * @private - */ - _addInputEvent() { - if (this.rangeInputElement) { - this.rangeInputElement.addEventListener('keydown', this.eventHandler.changeInputWithArrow); - this.rangeInputElement.addEventListener('keyup', this.eventHandler.changeInput); - this.rangeInputElement.addEventListener('blur', this.eventHandler.changeInputFinally); - } + } + + /** + * value up down for input + * @param {number} value - original value number + * @param {number} keyCode - input event key code + * @returns {number} value - changed value + * @private + */ + _valueUpDownForKeyEvent(value, keyCode) { + const step = this._useDecimal ? 0.1 : 1; + + if (keyCode === keyCodes.ARROW_UP) { + value += step; + } else if (keyCode === keyCodes.ARROW_DOWN) { + value -= step; } - /** - * Remove range input editing event - * @private - */ - _removeInputEvent() { - if (this.rangeInputElement) { - this.rangeInputElement.removeEventListener('keydown', this.eventHandler.changeInputWithArrow); - this.rangeInputElement.removeEventListener('keyup', this.eventHandler.changeInput); - this.rangeInputElement.removeEventListener('blur', this.eventHandler.changeInputFinally); - } - } - - /** - * change angle event - * @param {object} event - key event - * @private - */ - _changeValueWithInputKeyEvent(event) { - const {keyCode, target} = event; - - if ([keyCodes.ARROW_UP, keyCodes.ARROW_DOWN].indexOf(keyCode) < 0) { - return; - } - - let value = Number(target.value); + return value; + } - value = this._valueUpDownForKeyEvent(value, keyCode); + /** + * change angle event + * @param {boolean} isLast - Is last change + * @param {object} event - key event + * @private + */ + _changeValueWithInput(isLast, event) { + const { keyCode, target } = event; - const unChanged = value < this._min || value > this._max; - - if (!unChanged) { - const clampValue = clamp(value, this._min, this.max); - this.value = clampValue; - this.fire('change', clampValue, false); - } - } - - /** - * value up down for input - * @param {number} value - original value number - * @param {number} keyCode - input event key code - * @returns {number} value - changed value - * @private - */ - _valueUpDownForKeyEvent(value, keyCode) { - const step = this._useDecimal ? 0.1 : 1; - - if (keyCode === keyCodes.ARROW_UP) { - value += step; - } else if (keyCode === keyCodes.ARROW_DOWN) { - value -= step; - } - - return value; - } - - /** - * change angle event - * @param {boolean} isLast - Is last change - * @param {object} event - key event - * @private - */ - _changeValueWithInput(isLast, event) { - const {keyCode, target} = event; - - if ([keyCodes.ARROW_UP, keyCodes.ARROW_DOWN].indexOf(keyCode) >= 0) { - return; - } - - const stringValue = this._filterForInputText(target.value); - const waitForChange = !stringValue || isNaN(stringValue); - target.value = stringValue; - - if (!waitForChange) { - let value = this._useDecimal ? Number(stringValue) : toInteger(stringValue); - value = clamp(value, this._min, this.max); - - this.value = value; - this.fire('change', value, isLast); - } - } - - /** - * Add Range click event - * @private - */ - _addClickEvent() { - this.rangeElement.addEventListener('click', this.eventHandler.changeSlideFinally); - } - - /** - * Remove Range click event - * @private - */ - _removeClickEvent() { - this.rangeElement.removeEventListener('click', this.eventHandler.changeSlideFinally); + if ([keyCodes.ARROW_UP, keyCodes.ARROW_DOWN].indexOf(keyCode) >= 0) { + return; } - /** - * Add Range drag event - * @private - */ - _addDragEvent() { - this.pointer.addEventListener('mousedown', this.eventHandler.startChangingSlide); - } + const stringValue = this._filterForInputText(target.value); + const waitForChange = !stringValue || isNaN(stringValue); + target.value = stringValue; - /** - * Remove Range drag event - * @private - */ - _removeDragEvent() { - this.pointer.removeEventListener('mousedown', this.eventHandler.startChangingSlide); - } + if (!waitForChange) { + let value = this._useDecimal ? Number(stringValue) : toInteger(stringValue); + value = clamp(value, this._min, this.max); - /** - * change angle event - * @param {object} event - change event - * @private - */ - _changeSlide(event) { - const changePosition = event.screenX; - const diffPosition = changePosition - this.firstPosition; - let touchPx = this.firstLeft + diffPosition; - touchPx = touchPx > this.rangeWidth ? this.rangeWidth : touchPx; - touchPx = touchPx < 0 ? 0 : touchPx; - - this.pointer.style.left = `${touchPx}px`; - this.subbar.style.right = `${this.rangeWidth - touchPx}px`; - - const ratio = touchPx / this.rangeWidth; - const resultValue = (this._absMax * ratio) + this._min; - const value = this._useDecimal ? resultValue : toInteger(resultValue); - const isValueChanged = this.value !== value; - - if (isValueChanged) { - this.value = value; - if (this.realTimeEvent) { - this.fire('change', this._value, false); - } - } + this.value = value; + this.fire('change', value, isLast); } - - _changeSlideFinally(event) { - event.stopPropagation(); - if (event.target.className !== 'tui-image-editor-range') { - return; - } - const touchPx = event.offsetX; - const ratio = touchPx / this.rangeWidth; - const value = (this._absMax * ratio) + this._min; - this.pointer.style.left = `${ratio * this.rangeWidth}px`; - this.subbar.style.right = `${(1 - ratio) * this.rangeWidth}px`; - this.value = value; - - this.fire('change', value, true); - } - - _startChangingSlide(event) { - this.firstPosition = event.screenX; - this.firstLeft = toInteger(this.pointer.style.left) || 0; - - document.addEventListener('mousemove', this.eventHandler.changeSlide); - document.addEventListener('mouseup', this.eventHandler.stopChangingSlide); - } - - /** - * stop change angle event - * @private - */ - _stopChangingSlide() { - this.fire('change', this._value, true); - - document.removeEventListener('mousemove', this.eventHandler.changeSlide); - document.removeEventListener('mouseup', this.eventHandler.stopChangingSlide); + } + + /** + * Add Range click event + * @private + */ + _addClickEvent() { + this.rangeElement.addEventListener('click', this.eventHandler.changeSlideFinally); + } + + /** + * Remove Range click event + * @private + */ + _removeClickEvent() { + this.rangeElement.removeEventListener('click', this.eventHandler.changeSlideFinally); + } + + /** + * Add Range drag event + * @private + */ + _addDragEvent() { + this.pointer.addEventListener('mousedown', this.eventHandler.startChangingSlide); + } + + /** + * Remove Range drag event + * @private + */ + _removeDragEvent() { + this.pointer.removeEventListener('mousedown', this.eventHandler.startChangingSlide); + } + + /** + * change angle event + * @param {object} event - change event + * @private + */ + _changeSlide(event) { + const changePosition = event.screenX; + const diffPosition = changePosition - this.firstPosition; + let touchPx = this.firstLeft + diffPosition; + touchPx = touchPx > this.rangeWidth ? this.rangeWidth : touchPx; + touchPx = touchPx < 0 ? 0 : touchPx; + + this.pointer.style.left = `${touchPx}px`; + this.subbar.style.right = `${this.rangeWidth - touchPx}px`; + + const ratio = touchPx / this.rangeWidth; + const resultValue = this._absMax * ratio + this._min; + const value = this._useDecimal ? resultValue : toInteger(resultValue); + const isValueChanged = this.value !== value; + + if (isValueChanged) { + this.value = value; + if (this.realTimeEvent) { + this.fire('change', this._value, false); + } } + } - /** - * Unnecessary string filtering. - * @param {string} inputValue - origin string of input - * @returns {string} filtered string - * @private - */ - _filterForInputText(inputValue) { - return inputValue.replace(INPUT_FILTER_REGEXP, '$1$2$3'); + _changeSlideFinally(event) { + event.stopPropagation(); + if (event.target.className !== 'tui-image-editor-range') { + return; } + const touchPx = event.offsetX; + const ratio = touchPx / this.rangeWidth; + const value = this._absMax * ratio + this._min; + this.pointer.style.left = `${ratio * this.rangeWidth}px`; + this.subbar.style.right = `${(1 - ratio) * this.rangeWidth}px`; + this.value = value; + + this.fire('change', value, true); + } + + _startChangingSlide(event) { + this.firstPosition = event.screenX; + this.firstLeft = toInteger(this.pointer.style.left) || 0; + + document.addEventListener('mousemove', this.eventHandler.changeSlide); + document.addEventListener('mouseup', this.eventHandler.stopChangingSlide); + } + + /** + * stop change angle event + * @private + */ + _stopChangingSlide() { + this.fire('change', this._value, true); + + document.removeEventListener('mousemove', this.eventHandler.changeSlide); + document.removeEventListener('mouseup', this.eventHandler.stopChangingSlide); + } + + /** + * Unnecessary string filtering. + * @param {string} inputValue - origin string of input + * @returns {string} filtered string + * @private + */ + _filterForInputText(inputValue) { + return inputValue.replace(INPUT_FILTER_REGEXP, '$1$2$3'); + } } snippet.CustomEvents.mixin(Range); diff --git a/src/js/util.js b/src/js/util.js index afd12a2d2..4400bc6bf 100644 --- a/src/js/util.js +++ b/src/js/util.js @@ -2,19 +2,19 @@ * @author NHN Ent. FE Development Team * @fileoverview Util */ -import {forEach, sendHostname, extend, isString, pick, inArray} from 'tui-code-snippet'; +import { forEach, sendHostname, extend, isString, pick, inArray } from 'tui-code-snippet'; import Promise from 'core-js-pure/features/promise'; -import {SHAPE_FILL_TYPE, SHAPE_TYPE} from './consts'; +import { SHAPE_FILL_TYPE, SHAPE_TYPE } from './consts'; const FLOATING_POINT_DIGIT = 2; const CSS_PREFIX = 'tui-image-editor-'; -const {min, max} = Math; +const { min, max } = Math; let hostnameSent = false; /** * Export Promise Class (for simplified module path) * @returns {Promise} promise class */ -export {Promise}; +export { Promise }; /** * Clamp value @@ -24,14 +24,14 @@ export {Promise}; * @returns {number} clamped value */ export function clamp(value, minValue, maxValue) { - let temp; - if (minValue > maxValue) { - temp = minValue; - minValue = maxValue; - maxValue = temp; - } - - return max(minValue, min(value, maxValue)); + let temp; + if (minValue > maxValue) { + temp = minValue; + minValue = maxValue; + maxValue = temp; + } + + return max(minValue, min(value, maxValue)); } /** @@ -39,13 +39,13 @@ export function clamp(value, minValue, maxValue) { * @returns {object.} */ export function keyMirror(...args) { - const obj = {}; + const obj = {}; - forEach(args, key => { - obj[key] = key; - }); + forEach(args, (key) => { + obj[key] = key; + }); - return obj; + return obj; } /** @@ -54,13 +54,13 @@ export function keyMirror(...args) { * @returns {string} Connected string of style */ export function makeStyleText(styleObj) { - let styleStr = ''; + let styleStr = ''; - forEach(styleObj, (value, prop) => { - styleStr += `${prop}: ${value};`; - }); + forEach(styleObj, (value, prop) => { + styleStr += `${prop}: ${value};`; + }); - return styleStr; + return styleStr; } /** @@ -70,17 +70,17 @@ export function makeStyleText(styleObj) { * @returns {Object} properties object */ export function getProperties(obj, keys) { - const props = {}; - const {length} = keys; - let i = 0; - let key; + const props = {}; + const { length } = keys; + let i = 0; + let key; - for (i = 0; i < length; i += 1) { - key = keys[i]; - props[key] = obj[key]; - } + for (i = 0; i < length; i += 1) { + key = keys[i]; + props[key] = obj[key]; + } - return props; + return props; } /** @@ -89,7 +89,7 @@ export function getProperties(obj, keys) { * @returns {number} */ export function toInteger(value) { - return parseInt(value, 10); + return parseInt(value, 10); } /** @@ -99,7 +99,7 @@ export function toInteger(value) { * @private */ export function toCamelCase(targetString) { - return targetString.replace(/-([a-z])/g, ($0, $1) => $1.toUpperCase()); + return targetString.replace(/-([a-z])/g, ($0, $1) => $1.toUpperCase()); } /** @@ -108,7 +108,7 @@ export function toCamelCase(targetString) { * @private */ export function isSupportFileApi() { - return !!(window.File && window.FileList && window.FileReader); + return !!(window.File && window.FileList && window.FileReader); } /** @@ -118,27 +118,27 @@ export function isSupportFileApi() { * @returns {string} rgb expression */ export function getRgb(color, alpha) { - if (color.length === 4) { - color = `${color}${color.slice(1, 4)}`; - } - const r = parseInt(color.slice(1, 3), 16); - const g = parseInt(color.slice(3, 5), 16); - const b = parseInt(color.slice(5, 7), 16); - const a = alpha || 1; - - return `rgba(${r}, ${g}, ${b}, ${a})`; + if (color.length === 4) { + color = `${color}${color.slice(1, 4)}`; + } + const r = parseInt(color.slice(1, 3), 16); + const g = parseInt(color.slice(3, 5), 16); + const b = parseInt(color.slice(5, 7), 16); + const a = alpha || 1; + + return `rgba(${r}, ${g}, ${b}, ${a})`; } /** * send hostname */ export function sendHostName() { - if (hostnameSent) { - return; - } - hostnameSent = true; + if (hostnameSent) { + return; + } + hostnameSent = true; - sendHostname('image-editor', 'UA-129999381-1'); + sendHostname('image-editor', 'UA-129999381-1'); } /** @@ -147,17 +147,17 @@ export function sendHostName() { * @param {string} tagId - style tag id */ export function styleLoad(styleBuffer, tagId) { - const [head] = document.getElementsByTagName('head'); - const linkElement = document.createElement('link'); - const styleData = encodeURIComponent(styleBuffer); - if (tagId) { - linkElement.id = tagId; - // linkElement.id = 'tui-image-editor-theme-style'; - } - linkElement.setAttribute('rel', 'stylesheet'); - linkElement.setAttribute('type', 'text/css'); - linkElement.setAttribute('href', `data:text/css;charset=UTF-8,${styleData}`); - head.appendChild(linkElement); + const [head] = document.getElementsByTagName('head'); + const linkElement = document.createElement('link'); + const styleData = encodeURIComponent(styleBuffer); + if (tagId) { + linkElement.id = tagId; + // linkElement.id = 'tui-image-editor-theme-style'; + } + linkElement.setAttribute('rel', 'stylesheet'); + linkElement.setAttribute('type', 'text/css'); + linkElement.setAttribute('href', `data:text/css;charset=UTF-8,${styleData}`); + head.appendChild(linkElement); } /** @@ -166,7 +166,7 @@ export function styleLoad(styleBuffer, tagId) { * @returns {Function} selector */ export function getSelector(targetElement) { - return str => targetElement.querySelector(str); + return (str) => targetElement.querySelector(str); } /** @@ -175,25 +175,25 @@ export function getSelector(targetElement) { * @returns {Blob} Blob Data */ export function base64ToBlob(data) { - const rImageType = /data:(image\/.+);base64,/; - let mimeString = ''; - let raw, uInt8Array, i; + const rImageType = /data:(image\/.+);base64,/; + let mimeString = ''; + let raw, uInt8Array, i; - raw = data.replace(rImageType, (header, imageType) => { - mimeString = imageType; + raw = data.replace(rImageType, (header, imageType) => { + mimeString = imageType; - return ''; - }); + return ''; + }); - raw = atob(raw); - const rawLength = raw.length; - uInt8Array = new Uint8Array(rawLength); // eslint-disable-line + raw = atob(raw); + const rawLength = raw.length; + uInt8Array = new Uint8Array(rawLength); // eslint-disable-line - for (i = 0; i < rawLength; i += 1) { - uInt8Array[i] = raw.charCodeAt(i); - } + for (i = 0; i < rawLength; i += 1) { + uInt8Array[i] = raw.charCodeAt(i); + } - return new Blob([uInt8Array], {type: mimeString}); + return new Blob([uInt8Array], { type: mimeString }); } /** @@ -202,7 +202,7 @@ export function base64ToBlob(data) { * @returns {number} fixed value */ export function fixFloatingPoint(value) { - return Number(value.toFixed(FLOATING_POINT_DIGIT)); + return Number(value.toFixed(FLOATING_POINT_DIGIT)); } /** @@ -210,9 +210,9 @@ export function fixFloatingPoint(value) { * @param {Object} targetObject - object to be removed. */ export function assignmentForDestroy(targetObject) { - forEach(targetObject, (value, key) => { - targetObject[key] = null; - }); + forEach(targetObject, (value, key) => { + targetObject[key] = null; + }); } /** @@ -222,11 +222,11 @@ export function assignmentForDestroy(targetObject) { * @returns {String} class name */ export function cls(str = '', prefix = '') { - if (str.charAt(0) === '.') { - return `.${CSS_PREFIX}${prefix}${str.slice(1)}`; - } + if (str.charAt(0) === '.') { + return `.${CSS_PREFIX}${prefix}${str.slice(1)}`; + } - return `${CSS_PREFIX}${prefix}${str}`; + return `${CSS_PREFIX}${prefix}${str}`; } /** @@ -237,32 +237,32 @@ export function cls(str = '', prefix = '') { * @param {string} originY - vertical basis. */ export function changeOrigin(fObject, origin) { - const {originX, originY} = origin; - const {x: left, y: top} = fObject.getPointByOrigin(originX, originY); + const { originX, originY } = origin; + const { x: left, y: top } = fObject.getPointByOrigin(originX, originY); - fObject.set({ - left, - top, - originX, - originY - }); + fObject.set({ + left, + top, + originX, + originY, + }); - fObject.setCoords(); + fObject.setCoords(); } /** * Object key value flip - * @param {Object} targetObject - The data object of the key value. + * @param {Object} targetObject - The data object of the key value. * @returns {Object} */ export function flipObject(targetObject) { - const result = {}; + const result = {}; - Object.keys(targetObject).forEach(key => { - result[targetObject[key]] = key; - }); + Object.keys(targetObject).forEach((key) => { + result[targetObject[key]] = key; + }); - return result; + return result; } /** @@ -271,8 +271,8 @@ export function flipObject(targetObject) { * @param {Object} props - custom props object */ export function setCustomProperty(targetObject, props) { - targetObject.customProps = targetObject.customProps || {}; - extend(targetObject.customProps, props); + targetObject.customProps = targetObject.customProps || {}; + extend(targetObject.customProps, props); } /** @@ -282,15 +282,15 @@ export function setCustomProperty(targetObject, props) { * @returns {object | number | string} */ export function getCustomProperty(fObject, propNames) { - const resultObject = {}; - if (isString(propNames)) { - propNames = [propNames]; - } - forEach(propNames, propName => { - resultObject[propName] = fObject.customProps[propName]; - }); - - return resultObject; + const resultObject = {}; + if (isString(propNames)) { + propNames = [propNames]; + } + forEach(propNames, (propName) => { + resultObject[propName] = fObject.customProps[propName]; + }); + + return resultObject; } /** @@ -299,7 +299,7 @@ export function getCustomProperty(fObject, propNames) { * @returns {string} */ export function capitalizeString(targetString) { - return targetString.charAt(0).toUpperCase() + targetString.slice(1); + return targetString.charAt(0).toUpperCase() + targetString.slice(1); } /** @@ -309,7 +309,7 @@ export function capitalizeString(targetString) { * @returns {boolean} */ export function includes(targetArray, compareValue) { - return targetArray.indexOf(compareValue) >= 0; + return targetArray.indexOf(compareValue) >= 0; } /** @@ -318,7 +318,7 @@ export function includes(targetArray, compareValue) { * @returns {string} 'color' or 'filter' */ export function getFillTypeFromOption(fillOption = {}) { - return pick(fillOption, 'type') || SHAPE_FILL_TYPE.COLOR; + return pick(fillOption, 'type') || SHAPE_FILL_TYPE.COLOR; } /** @@ -327,12 +327,12 @@ export function getFillTypeFromOption(fillOption = {}) { * @returns {string} 'transparent' or 'color' or 'filter' */ export function getFillTypeFromObject(shapeObj) { - const {fill = {}} = shapeObj; - if (fill.source) { - return SHAPE_FILL_TYPE.FILTER; - } + const { fill = {} } = shapeObj; + if (fill.source) { + return SHAPE_FILL_TYPE.FILTER; + } - return SHAPE_FILL_TYPE.COLOR; + return SHAPE_FILL_TYPE.COLOR; } /** @@ -341,5 +341,5 @@ export function getFillTypeFromObject(shapeObj) { * @returns {boolean} */ export function isShape(obj) { - return inArray(obj.get('type'), SHAPE_TYPE) >= 0; + return inArray(obj.get('type'), SHAPE_TYPE) >= 0; } diff --git a/test/.eslintrc.js b/test/.eslintrc.js index 3f6c165ce..0cf0bbd9f 100644 --- a/test/.eslintrc.js +++ b/test/.eslintrc.js @@ -1,5 +1,5 @@ module.exports = { - "rules": { - "max-nested-callbacks": 0 - } + rules: { + 'max-nested-callbacks': 0, + }, }; diff --git a/test/action.spec.js b/test/action.spec.js index d4fc65085..a05aca0d9 100644 --- a/test/action.spec.js +++ b/test/action.spec.js @@ -3,497 +3,527 @@ * @fileoverview Test cases of "src/js/action.js" */ import snippet from 'tui-code-snippet'; -import {Promise} from '../src/js/util'; +import { Promise } from '../src/js/util'; import ImageEditor from '../src/js/imageEditor'; import action from '../src/js/action'; -import {eventNames} from '../src/js/consts'; +import { eventNames } from '../src/js/consts'; describe('Ui', () => { - let actions; - let imageEditorMock; + let actions; + let imageEditorMock; + + beforeEach(() => { + action.mixin(ImageEditor); + imageEditorMock = new ImageEditor(document.createElement('div'), { + includeUI: { + loadImage: false, + initMenu: 'flip', + menuBarPosition: 'bottom', + applyCropSelectionStyle: true, + }, + }); + actions = imageEditorMock.getActions(); - beforeEach(() => { - action.mixin(ImageEditor); - imageEditorMock = new ImageEditor(document.createElement('div'), { - includeUI: { - loadImage: false, - initMenu: 'flip', - menuBarPosition: 'bottom', - applyCropSelectionStyle: true - } - }); - actions = imageEditorMock.getActions(); + spyOn(snippet, 'imagePing'); + }); - spyOn(snippet, 'imagePing'); - }); + afterEach(() => { + imageEditorMock.destroy(); + }); - afterEach(() => { - imageEditorMock.destroy(); + describe('mainAction', () => { + let mainAction; + beforeEach(() => { + mainAction = actions.main; }); - describe('mainAction', () => { - let mainAction; - beforeEach(() => { - mainAction = actions.main; - }); - - it('LoadImageFromURL() API should be executed When the initLoadImage action occurs', done => { - const promise = new Promise(resolve => { - resolve(300); - }); - spyOn(imageEditorMock, 'loadImageFromURL').and.returnValue(promise); - spyOn(imageEditorMock, 'clearUndoStack'); - spyOn(imageEditorMock.ui, 'resizeEditor'); - - mainAction.initLoadImage('path', 'imageName').then(() => { - expect(imageEditorMock.clearUndoStack).toHaveBeenCalled(); - expect(imageEditorMock.ui.resizeEditor).toHaveBeenCalled(); - expect(imageEditorMock.loadImageFromURL).toHaveBeenCalled(); - done(); - }); - }); + it('LoadImageFromURL() API should be executed When the initLoadImage action occurs', (done) => { + const promise = new Promise((resolve) => { + resolve(300); + }); + spyOn(imageEditorMock, 'loadImageFromURL').and.returnValue(promise); + spyOn(imageEditorMock, 'clearUndoStack'); + spyOn(imageEditorMock.ui, 'resizeEditor'); + + mainAction.initLoadImage('path', 'imageName').then(() => { + expect(imageEditorMock.clearUndoStack).toHaveBeenCalled(); + expect(imageEditorMock.ui.resizeEditor).toHaveBeenCalled(); + expect(imageEditorMock.loadImageFromURL).toHaveBeenCalled(); + done(); + }); + }); - it('Undo() API should be executed When the undo action occurs', () => { - spyOn(imageEditorMock, 'isEmptyUndoStack').and.returnValue(false); - spyOn(imageEditorMock, 'undo').and.returnValue({then: () => {}}); + it('Undo() API should be executed When the undo action occurs', () => { + spyOn(imageEditorMock, 'isEmptyUndoStack').and.returnValue(false); + spyOn(imageEditorMock, 'undo').and.returnValue({ then: () => {} }); - mainAction.undo(); + mainAction.undo(); - expect(imageEditorMock.undo).toHaveBeenCalled(); - }); + expect(imageEditorMock.undo).toHaveBeenCalled(); + }); - it('Redo() API should be executed When the redo action occurs', () => { - spyOn(imageEditorMock, 'isEmptyRedoStack').and.returnValue(false); - spyOn(imageEditorMock, 'redo').and.returnValue({then: () => {}}); + it('Redo() API should be executed When the redo action occurs', () => { + spyOn(imageEditorMock, 'isEmptyRedoStack').and.returnValue(false); + spyOn(imageEditorMock, 'redo').and.returnValue({ then: () => {} }); - mainAction.redo(); + mainAction.redo(); - expect(imageEditorMock.redo).toHaveBeenCalled(); - }); + expect(imageEditorMock.redo).toHaveBeenCalled(); + }); - it('removeObject() API should be executed When the delete action occurs', () => { - imageEditorMock.activeObjectId = 10; - spyOn(imageEditorMock, 'removeActiveObject'); + it('removeObject() API should be executed When the delete action occurs', () => { + imageEditorMock.activeObjectId = 10; + spyOn(imageEditorMock, 'removeActiveObject'); - mainAction['delete'](); + mainAction['delete'](); - expect(imageEditorMock.removeActiveObject).toHaveBeenCalled(); - expect(imageEditorMock.activeObjectId).toBe(null); - }); + expect(imageEditorMock.removeActiveObject).toHaveBeenCalled(); + expect(imageEditorMock.activeObjectId).toBe(null); + }); - it('clearObjects() API should be run and the enabled state should be changed When the deleteAll action occurs', () => { - spyOn(imageEditorMock, 'clearObjects'); - spyOn(imageEditorMock.ui, 'changeHelpButtonEnabled'); + it('clearObjects() API should be run and the enabled state should be changed When the deleteAll action occurs', () => { + spyOn(imageEditorMock, 'clearObjects'); + spyOn(imageEditorMock.ui, 'changeHelpButtonEnabled'); - mainAction.deleteAll(); + mainAction.deleteAll(); - const changeHelpButtonCalls = imageEditorMock.ui.changeHelpButtonEnabled.calls; + const changeHelpButtonCalls = imageEditorMock.ui.changeHelpButtonEnabled.calls; - expect(imageEditorMock.clearObjects).toHaveBeenCalled(); - expect(changeHelpButtonCalls.argsFor(0)[0]).toBe('delete'); - expect(changeHelpButtonCalls.argsFor(1)[0]).toBe('deleteAll'); - }); + expect(imageEditorMock.clearObjects).toHaveBeenCalled(); + expect(changeHelpButtonCalls.argsFor(0)[0]).toBe('delete'); + expect(changeHelpButtonCalls.argsFor(1)[0]).toBe('deleteAll'); + }); - it('loadImageFromFile() API should be executed When the load action occurs', done => { - const promise = new Promise(resolve => { - resolve(); - }); + it('loadImageFromFile() API should be executed When the load action occurs', (done) => { + const promise = new Promise((resolve) => { + resolve(); + }); - spyOn(imageEditorMock, 'loadImageFromFile').and.returnValue(promise); - spyOn(imageEditorMock, 'clearUndoStack'); - spyOn(imageEditorMock.ui, 'resizeEditor'); + spyOn(imageEditorMock, 'loadImageFromFile').and.returnValue(promise); + spyOn(imageEditorMock, 'clearUndoStack'); + spyOn(imageEditorMock.ui, 'resizeEditor'); - window.URL = { - createObjectURL: jasmine.createSpy('URL') - }; + window.URL = { + createObjectURL: jasmine.createSpy('URL'), + }; - mainAction.load(); + mainAction.load(); - promise.then(() => { - expect(imageEditorMock.loadImageFromFile).toHaveBeenCalled(); - expect(imageEditorMock.clearUndoStack).toHaveBeenCalled(); - expect(imageEditorMock.ui.resizeEditor).toHaveBeenCalled(); - done(); - }); - }); + promise.then(() => { + expect(imageEditorMock.loadImageFromFile).toHaveBeenCalled(); + expect(imageEditorMock.clearUndoStack).toHaveBeenCalled(); + expect(imageEditorMock.ui.resizeEditor).toHaveBeenCalled(); + done(); + }); }); + }); - describe('shapeAction', () => { - let shapeAction; + describe('shapeAction', () => { + let shapeAction; - beforeEach(() => { - shapeAction = actions.shape; - }); + beforeEach(() => { + shapeAction = actions.shape; + }); - it('changeShape() API should be executed When the changeShape action occurs', () => { - imageEditorMock.activeObjectId = 10; - spyOn(imageEditorMock, 'changeShape'); + it('changeShape() API should be executed When the changeShape action occurs', () => { + imageEditorMock.activeObjectId = 10; + spyOn(imageEditorMock, 'changeShape'); - shapeAction.changeShape({ - strokeWidth: '#000000' - }); - expect(imageEditorMock.changeShape).toHaveBeenCalled(); - }); + shapeAction.changeShape({ + strokeWidth: '#000000', + }); + expect(imageEditorMock.changeShape).toHaveBeenCalled(); + }); - it('setDrawingShape() API should be executed When the setDrawingShape action occurs', () => { - spyOn(imageEditorMock, 'setDrawingShape'); + it('setDrawingShape() API should be executed When the setDrawingShape action occurs', () => { + spyOn(imageEditorMock, 'setDrawingShape'); - shapeAction.setDrawingShape(); - expect(imageEditorMock.setDrawingShape).toHaveBeenCalled(); - }); + shapeAction.setDrawingShape(); + expect(imageEditorMock.setDrawingShape).toHaveBeenCalled(); }); + }); - describe('cropAction', () => { - let cropAction; - beforeEach(() => { - cropAction = actions.crop; - }); - it('getCropzoneRect(), stopDrawingMode(), ui.resizeEditor(), ui.changeMenu() API should be executed When the crop action occurs', done => { - const promise = new Promise(resolve => { - resolve(); - }); - spyOn(imageEditorMock, 'crop').and.returnValue(promise); - spyOn(imageEditorMock, 'getCropzoneRect').and.returnValue(true); - spyOn(imageEditorMock, 'stopDrawingMode'); - spyOn(imageEditorMock.ui, 'resizeEditor'); - spyOn(imageEditorMock.ui, 'changeMenu'); - - cropAction.crop(); - - expect(imageEditorMock.getCropzoneRect).toHaveBeenCalled(); - expect(imageEditorMock.crop).toHaveBeenCalled(); - promise.then(() => { - expect(imageEditorMock.stopDrawingMode).toHaveBeenCalled(); - expect(imageEditorMock.ui.resizeEditor).toHaveBeenCalled(); - expect(imageEditorMock.ui.changeMenu).toHaveBeenCalled(); - done(); - }); - }); + describe('cropAction', () => { + let cropAction; + beforeEach(() => { + cropAction = actions.crop; + }); + it('getCropzoneRect(), stopDrawingMode(), ui.resizeEditor(), ui.changeMenu() API should be executed When the crop action occurs', (done) => { + const promise = new Promise((resolve) => { + resolve(); + }); + spyOn(imageEditorMock, 'crop').and.returnValue(promise); + spyOn(imageEditorMock, 'getCropzoneRect').and.returnValue(true); + spyOn(imageEditorMock, 'stopDrawingMode'); + spyOn(imageEditorMock.ui, 'resizeEditor'); + spyOn(imageEditorMock.ui, 'changeMenu'); + + cropAction.crop(); + + expect(imageEditorMock.getCropzoneRect).toHaveBeenCalled(); + expect(imageEditorMock.crop).toHaveBeenCalled(); + promise.then(() => { + expect(imageEditorMock.stopDrawingMode).toHaveBeenCalled(); + expect(imageEditorMock.ui.resizeEditor).toHaveBeenCalled(); + expect(imageEditorMock.ui.changeMenu).toHaveBeenCalled(); + done(); + }); + }); - it('stopDrawingMode() API should be executed When the cancel action occurs', () => { - spyOn(imageEditorMock, 'stopDrawingMode'); - spyOn(imageEditorMock.ui, 'changeMenu'); + it('stopDrawingMode() API should be executed When the cancel action occurs', () => { + spyOn(imageEditorMock, 'stopDrawingMode'); + spyOn(imageEditorMock.ui, 'changeMenu'); - cropAction.cancel(); - expect(imageEditorMock.stopDrawingMode).toHaveBeenCalled(); - expect(imageEditorMock.ui.changeMenu).toHaveBeenCalled(); - }); + cropAction.cancel(); + expect(imageEditorMock.stopDrawingMode).toHaveBeenCalled(); + expect(imageEditorMock.ui.changeMenu).toHaveBeenCalled(); }); + }); - describe('flipAction', () => { - let flipAction; - beforeEach(() => { - flipAction = actions.flip; - }); - it('{flipType}() API should be executed When the flip(fliptype) action occurs', () => { - spyOn(imageEditorMock, 'flipX'); - spyOn(imageEditorMock, 'flipY'); + describe('flipAction', () => { + let flipAction; + beforeEach(() => { + flipAction = actions.flip; + }); + it('{flipType}() API should be executed When the flip(fliptype) action occurs', () => { + spyOn(imageEditorMock, 'flipX'); + spyOn(imageEditorMock, 'flipY'); - flipAction.flip('flipX'); - expect(imageEditorMock.flipX).toHaveBeenCalled(); + flipAction.flip('flipX'); + expect(imageEditorMock.flipX).toHaveBeenCalled(); - flipAction.flip('flipY'); - expect(imageEditorMock.flipY).toHaveBeenCalled(); - }); + flipAction.flip('flipY'); + expect(imageEditorMock.flipY).toHaveBeenCalled(); }); + }); - describe('rotateAction', () => { - let rotateAction; - beforeEach(() => { - rotateAction = actions.rotate; - }); + describe('rotateAction', () => { + let rotateAction; + beforeEach(() => { + rotateAction = actions.rotate; + }); - it('rotate() API should be executed When the rotate action occurs', () => { - spyOn(imageEditorMock, 'rotate'); - spyOn(imageEditorMock.ui, 'resizeEditor'); + it('rotate() API should be executed When the rotate action occurs', () => { + spyOn(imageEditorMock, 'rotate'); + spyOn(imageEditorMock.ui, 'resizeEditor'); - rotateAction.rotate(30); - expect(imageEditorMock.rotate).toHaveBeenCalled(); - expect(imageEditorMock.ui.resizeEditor).toHaveBeenCalled(); - }); + rotateAction.rotate(30); + expect(imageEditorMock.rotate).toHaveBeenCalled(); + expect(imageEditorMock.ui.resizeEditor).toHaveBeenCalled(); + }); - it('setAngle() API should be executed When the setAngle action occurs', () => { - spyOn(imageEditorMock, 'setAngle'); - spyOn(imageEditorMock.ui, 'resizeEditor'); + it('setAngle() API should be executed When the setAngle action occurs', () => { + spyOn(imageEditorMock, 'setAngle'); + spyOn(imageEditorMock.ui, 'resizeEditor'); - rotateAction.setAngle(30); - expect(imageEditorMock.setAngle).toHaveBeenCalled(); - expect(imageEditorMock.ui.resizeEditor).toHaveBeenCalled(); - }); + rotateAction.setAngle(30); + expect(imageEditorMock.setAngle).toHaveBeenCalled(); + expect(imageEditorMock.ui.resizeEditor).toHaveBeenCalled(); }); + }); - describe('textAction', () => { - let textAction; - beforeEach(() => { - textAction = actions.text; - }); + describe('textAction', () => { + let textAction; + beforeEach(() => { + textAction = actions.text; + }); - it('changeTextStyle() API should be executed When the changeTextStyle action occurs', () => { - imageEditorMock.activeObjectId = 10; - spyOn(imageEditorMock, 'changeTextStyle'); + it('changeTextStyle() API should be executed When the changeTextStyle action occurs', () => { + imageEditorMock.activeObjectId = 10; + spyOn(imageEditorMock, 'changeTextStyle'); - textAction.changeTextStyle({fontSize: 10}); - expect(imageEditorMock.changeTextStyle.calls.mostRecent().args[0]).toBe(10); - expect(imageEditorMock.changeTextStyle.calls.mostRecent().args[1]).toEqual({fontSize: 10}); - }); + textAction.changeTextStyle({ fontSize: 10 }); + expect(imageEditorMock.changeTextStyle.calls.mostRecent().args[0]).toBe(10); + expect(imageEditorMock.changeTextStyle.calls.mostRecent().args[1]).toEqual({ fontSize: 10 }); }); + }); - describe('maskAction', () => { - let maskAction; - beforeEach(() => { - maskAction = actions.mask; - }); + describe('maskAction', () => { + let maskAction; + beforeEach(() => { + maskAction = actions.mask; + }); - it('applyFilter() API should be executed When the applyFilter action occurs', () => { - imageEditorMock.activeObjectId = 10; - spyOn(imageEditorMock, 'applyFilter'); + it('applyFilter() API should be executed When the applyFilter action occurs', () => { + imageEditorMock.activeObjectId = 10; + spyOn(imageEditorMock, 'applyFilter'); - maskAction.applyFilter(); - expect(imageEditorMock.applyFilter.calls.mostRecent().args[1]).toEqual({maskObjId: 10}); - }); + maskAction.applyFilter(); + expect(imageEditorMock.applyFilter.calls.mostRecent().args[1]).toEqual({ maskObjId: 10 }); }); + }); - describe('drawAction', () => { - let drawAction, expected; - beforeEach(() => { - drawAction = actions.draw; - }); + describe('drawAction', () => { + let drawAction, expected; + beforeEach(() => { + drawAction = actions.draw; + }); - it('startDrawingMode("FREE_DRAWING") API should be executed When the setDrawMode("free") action occurs', () => { - spyOn(imageEditorMock, 'startDrawingMode'); - drawAction.setDrawMode('free'); + it('startDrawingMode("FREE_DRAWING") API should be executed When the setDrawMode("free") action occurs', () => { + spyOn(imageEditorMock, 'startDrawingMode'); + drawAction.setDrawMode('free'); - expected = imageEditorMock.startDrawingMode.calls.mostRecent().args[0]; - expect(expected).toBe('FREE_DRAWING'); - }); + expected = imageEditorMock.startDrawingMode.calls.mostRecent().args[0]; + expect(expected).toBe('FREE_DRAWING'); + }); - it('setBrush() API should be executed When the setColor() action occurs', () => { - spyOn(imageEditorMock, 'setBrush'); - drawAction.setColor('#000000'); + it('setBrush() API should be executed When the setColor() action occurs', () => { + spyOn(imageEditorMock, 'setBrush'); + drawAction.setColor('#000000'); - expected = imageEditorMock.setBrush.calls.mostRecent().args[0].color; - expect(expected).toBe('#000000'); - }); + expected = imageEditorMock.setBrush.calls.mostRecent().args[0].color; + expect(expected).toBe('#000000'); }); + }); - describe('iconAction', () => { - let iconAction; + describe('iconAction', () => { + let iconAction; - beforeEach(() => { - iconAction = actions.icon; - }); + beforeEach(() => { + iconAction = actions.icon; + }); - it('when the add icon occurs, the drawing mode should be run.', () => { - spyOn(imageEditorMock, 'startDrawingMode'); - spyOn(imageEditorMock, 'setDrawingIcon'); + it('when the add icon occurs, the drawing mode should be run.', () => { + spyOn(imageEditorMock, 'startDrawingMode'); + spyOn(imageEditorMock, 'setDrawingIcon'); - iconAction.addIcon('iconTypeA', '#fff'); + iconAction.addIcon('iconTypeA', '#fff'); - expect(imageEditorMock.startDrawingMode).toHaveBeenCalled(); - expect(imageEditorMock.setDrawingIcon).toHaveBeenCalled(); - }); + expect(imageEditorMock.startDrawingMode).toHaveBeenCalled(); + expect(imageEditorMock.setDrawingIcon).toHaveBeenCalled(); }); + }); - describe('filterAction', () => { - let filterAction; - beforeEach(() => { - filterAction = actions.filter; - }); + describe('filterAction', () => { + let filterAction; + beforeEach(() => { + filterAction = actions.filter; + }); - it('removeFilter() API should be executed When the type of applyFilter is false', () => { - spyOn(imageEditorMock, 'removeFilter'); - spyOn(imageEditorMock, 'hasFilter').and.returnValue(true); - filterAction.applyFilter(false, {}); + it('removeFilter() API should be executed When the type of applyFilter is false', () => { + spyOn(imageEditorMock, 'removeFilter'); + spyOn(imageEditorMock, 'hasFilter').and.returnValue(true); + filterAction.applyFilter(false, {}); - expect(imageEditorMock.removeFilter).toHaveBeenCalled(); - }); + expect(imageEditorMock.removeFilter).toHaveBeenCalled(); + }); - it('applyFilter() API should be executed When the type of applyFilter is true', () => { - spyOn(imageEditorMock, 'applyFilter'); - filterAction.applyFilter(true, {}); + it('applyFilter() API should be executed When the type of applyFilter is true', () => { + spyOn(imageEditorMock, 'applyFilter'); + filterAction.applyFilter(true, {}); - expect(imageEditorMock.applyFilter).toHaveBeenCalled(); - }); + expect(imageEditorMock.applyFilter).toHaveBeenCalled(); + }); + }); + + describe('commonAction', () => { + it('Each action returned to the getActions method must contain commonAction.', () => { + const submenus = [ + 'shape', + 'crop', + 'flip', + 'rotate', + 'text', + 'mask', + 'draw', + 'icon', + 'filter', + ]; + snippet.forEach(submenus, (submenu) => { + expect(actions[submenu].modeChange).toBeDefined(); + expect(actions[submenu].deactivateAll).toBeDefined(); + expect(actions[submenu].changeSelectableAll).toBeDefined(); + expect(actions[submenu].discardSelection).toBeDefined(); + expect(actions[submenu].stopDrawingMode).toBeDefined(); + }); }); - describe('commonAction', () => { - it('Each action returned to the getActions method must contain commonAction.', () => { - const submenus = ['shape', 'crop', 'flip', 'rotate', 'text', 'mask', 'draw', 'icon', 'filter']; - snippet.forEach(submenus, submenu => { - expect(actions[submenu].modeChange).toBeDefined(); - expect(actions[submenu].deactivateAll).toBeDefined(); - expect(actions[submenu].changeSelectableAll).toBeDefined(); - expect(actions[submenu].discardSelection).toBeDefined(); - expect(actions[submenu].stopDrawingMode).toBeDefined(); - }); - }); - - describe('modeChange()', () => { - let commonAction; - beforeEach(() => { - commonAction = actions.main; - }); + describe('modeChange()', () => { + let commonAction; + beforeEach(() => { + commonAction = actions.main; + }); - it('_changeActivateMode("TEXT") API should be executed When the modeChange("text") action occurs', () => { - spyOn(imageEditorMock, '_changeActivateMode'); + it('_changeActivateMode("TEXT") API should be executed When the modeChange("text") action occurs', () => { + spyOn(imageEditorMock, '_changeActivateMode'); - commonAction.modeChange('text'); - expect(imageEditorMock._changeActivateMode).toHaveBeenCalled(); - }); + commonAction.modeChange('text'); + expect(imageEditorMock._changeActivateMode).toHaveBeenCalled(); + }); - it('startDrawingMode() API should be executed When the modeChange("crop") action occurs', () => { - spyOn(imageEditorMock, 'startDrawingMode'); + it('startDrawingMode() API should be executed When the modeChange("crop") action occurs', () => { + spyOn(imageEditorMock, 'startDrawingMode'); - commonAction.modeChange('crop'); - expect(imageEditorMock.startDrawingMode).toHaveBeenCalled(); - }); + commonAction.modeChange('crop'); + expect(imageEditorMock.startDrawingMode).toHaveBeenCalled(); + }); - it('stopDrawingMode(), setDrawingShape(), _changeActivateMode() API should be executed When the modeChange("shape") action occurs', () => { - spyOn(imageEditorMock, 'setDrawingShape'); - spyOn(imageEditorMock, '_changeActivateMode'); + it('stopDrawingMode(), setDrawingShape(), _changeActivateMode() API should be executed When the modeChange("shape") action occurs', () => { + spyOn(imageEditorMock, 'setDrawingShape'); + spyOn(imageEditorMock, '_changeActivateMode'); - commonAction.modeChange('shape'); - expect(imageEditorMock.setDrawingShape).toHaveBeenCalled(); - expect(imageEditorMock._changeActivateMode).toHaveBeenCalled(); - }); - }); + commonAction.modeChange('shape'); + expect(imageEditorMock.setDrawingShape).toHaveBeenCalled(); + expect(imageEditorMock._changeActivateMode).toHaveBeenCalled(); + }); }); + }); - describe('reAction', () => { - beforeEach(() => { - imageEditorMock.setReAction(); - spyOn(imageEditorMock.ui, 'changeHelpButtonEnabled'); - }); + describe('reAction', () => { + beforeEach(() => { + imageEditorMock.setReAction(); + spyOn(imageEditorMock.ui, 'changeHelpButtonEnabled'); + }); - describe('undoStackChanged', () => { - it('If the undo stack has a length greater than zero, the state of changeUndoButtonStatus, changeResetButtonStatus should be true.', () => { - imageEditorMock.fire('undoStackChanged', 1); + describe('undoStackChanged', () => { + it('If the undo stack has a length greater than zero, the state of changeUndoButtonStatus, changeResetButtonStatus should be true.', () => { + imageEditorMock.fire('undoStackChanged', 1); + + expect(imageEditorMock.ui.changeHelpButtonEnabled.calls.argsFor(0)).toEqual(['undo', true]); + expect(imageEditorMock.ui.changeHelpButtonEnabled.calls.argsFor(1)).toEqual([ + 'reset', + true, + ]); + }); + + it('If the undo stack has a length of 0, the state of changeUndoButtonStatus, changeResetButtonStatus should be false.', () => { + imageEditorMock.fire('undoStackChanged', 0); + + expect(imageEditorMock.ui.changeHelpButtonEnabled.calls.argsFor(0)).toEqual([ + 'undo', + false, + ]); + expect(imageEditorMock.ui.changeHelpButtonEnabled.calls.argsFor(1)).toEqual([ + 'reset', + false, + ]); + }); + }); - expect(imageEditorMock.ui.changeHelpButtonEnabled.calls.argsFor(0)).toEqual(['undo', true]); - expect(imageEditorMock.ui.changeHelpButtonEnabled.calls.argsFor(1)).toEqual(['reset', true]); - }); + describe('redoStackChanged', () => { + it('If the redo stack is greater than zero length, the state of changeRedoButtonStatus should be true.', () => { + imageEditorMock.fire('redoStackChanged', 1); + expect(imageEditorMock.ui.changeHelpButtonEnabled.calls.argsFor(0)).toEqual(['redo', true]); + }); + + it('If the redo stack has a length of zero, the state of changeRedoButtonStatus should be false.', () => { + imageEditorMock.fire('redoStackChanged', 0); + expect(imageEditorMock.ui.changeHelpButtonEnabled.calls.argsFor(0)).toEqual([ + 'redo', + false, + ]); + }); + }); - it('If the undo stack has a length of 0, the state of changeUndoButtonStatus, changeResetButtonStatus should be false.', () => { - imageEditorMock.fire('undoStackChanged', 0); + describe('objectActivated', () => { + it('When objectActivated occurs, the state of the delete button should be enabled.', () => { + imageEditorMock.fire('objectActivated', { id: 1 }); + expect(imageEditorMock.ui.changeHelpButtonEnabled.calls.argsFor(0)).toEqual([ + 'delete', + true, + ]); + expect(imageEditorMock.ui.changeHelpButtonEnabled.calls.argsFor(1)).toEqual([ + 'deleteAll', + true, + ]); + }); + + it("When objectActivated's target is cropzone, changeApplyButtonStatus should be enabled.", () => { + spyOn(imageEditorMock.ui.crop, 'changeApplyButtonStatus'); + imageEditorMock.fire('objectActivated', { + id: 1, + type: 'cropzone', + }); + expect(imageEditorMock.ui.crop.changeApplyButtonStatus.calls.mostRecent().args[0]).toBe( + true + ); + }); + + it('If the target of objectActivated is shape and the existing menu is not shpe, the menu should be changed to shape.', () => { + imageEditorMock.ui.submenu = 'crop'; + spyOn(imageEditorMock.ui, 'changeMenu'); + spyOn(imageEditorMock.ui.shape, 'setShapeStatus'); + spyOn(imageEditorMock.ui.shape, 'setMaxStrokeValue'); + imageEditorMock.fire('objectActivated', { + id: 1, + type: 'circle', + }); + + expect(imageEditorMock.ui.changeMenu.calls.mostRecent().args[0]).toBe('shape'); + expect(imageEditorMock.ui.shape.setMaxStrokeValue).toHaveBeenCalled(); + }); + + it('If the target of objectActivated is text and the existing menu is not text, the menu should be changed to text.', () => { + imageEditorMock.ui.submenu = 'crop'; + spyOn(imageEditorMock.ui, 'changeMenu'); + imageEditorMock.fire('objectActivated', { + id: 1, + type: 'i-text', + }); + + expect(imageEditorMock.ui.changeMenu.calls.mostRecent().args[0]).toBe('text'); + }); + + it('If the target of objectActivated is icon and the existing menu is not icon, the menu should be changed to icon.', () => { + imageEditorMock.ui.submenu = 'crop'; + spyOn(imageEditorMock.ui, 'changeMenu'); + spyOn(imageEditorMock.ui.icon, 'setIconPickerColor'); + imageEditorMock.fire('objectActivated', { + id: 1, + type: 'icon', + }); + + expect(imageEditorMock.ui.changeMenu.calls.mostRecent().args[0]).toBe('icon'); + expect(imageEditorMock.ui.icon.setIconPickerColor).toHaveBeenCalled(); + }); + }); - expect(imageEditorMock.ui.changeHelpButtonEnabled.calls.argsFor(0)).toEqual(['undo', false]); - expect(imageEditorMock.ui.changeHelpButtonEnabled.calls.argsFor(1)).toEqual(['reset', false]); - }); + describe('addObjectAfter', () => { + it("When addObjectAfter occurs, the shape's maxStrokeValue should be changed to match the size of the added object.", () => { + spyOn(imageEditorMock.ui.shape, 'setMaxStrokeValue'); + spyOn(imageEditorMock.ui.shape, 'changeStandbyMode'); + imageEditorMock.fire('addObjectAfter', { + type: 'circle', + width: 100, + height: 200, }); - describe('redoStackChanged', () => { - it('If the redo stack is greater than zero length, the state of changeRedoButtonStatus should be true.', () => { - imageEditorMock.fire('redoStackChanged', 1); - expect(imageEditorMock.ui.changeHelpButtonEnabled.calls.argsFor(0)).toEqual(['redo', true]); - }); - - it('If the redo stack has a length of zero, the state of changeRedoButtonStatus should be false.', () => { - imageEditorMock.fire('redoStackChanged', 0); - expect(imageEditorMock.ui.changeHelpButtonEnabled.calls.argsFor(0)).toEqual(['redo', false]); - }); - }); + expect(imageEditorMock.ui.shape.setMaxStrokeValue.calls.mostRecent().args[0]).toBe(100); + expect(imageEditorMock.ui.shape.changeStandbyMode).toHaveBeenCalled(); + }); + }); - describe('objectActivated', () => { - it('When objectActivated occurs, the state of the delete button should be enabled.', () => { - imageEditorMock.fire('objectActivated', {id: 1}); - expect(imageEditorMock.ui.changeHelpButtonEnabled.calls.argsFor(0)).toEqual(['delete', true]); - expect(imageEditorMock.ui.changeHelpButtonEnabled.calls.argsFor(1)).toEqual(['deleteAll', true]); - }); - - it('When objectActivated\'s target is cropzone, changeApplyButtonStatus should be enabled.', () => { - spyOn(imageEditorMock.ui.crop, 'changeApplyButtonStatus'); - imageEditorMock.fire('objectActivated', { - id: 1, - type: 'cropzone' - }); - expect(imageEditorMock.ui.crop.changeApplyButtonStatus.calls.mostRecent().args[0]).toBe(true); - }); - - it('If the target of objectActivated is shape and the existing menu is not shpe, the menu should be changed to shape.', () => { - imageEditorMock.ui.submenu = 'crop'; - spyOn(imageEditorMock.ui, 'changeMenu'); - spyOn(imageEditorMock.ui.shape, 'setShapeStatus'); - spyOn(imageEditorMock.ui.shape, 'setMaxStrokeValue'); - imageEditorMock.fire('objectActivated', { - id: 1, - type: 'circle' - }); - - expect(imageEditorMock.ui.changeMenu.calls.mostRecent().args[0]).toBe('shape'); - expect(imageEditorMock.ui.shape.setMaxStrokeValue).toHaveBeenCalled(); - }); - - it('If the target of objectActivated is text and the existing menu is not text, the menu should be changed to text.', () => { - imageEditorMock.ui.submenu = 'crop'; - spyOn(imageEditorMock.ui, 'changeMenu'); - imageEditorMock.fire('objectActivated', { - id: 1, - type: 'i-text' - }); - - expect(imageEditorMock.ui.changeMenu.calls.mostRecent().args[0]).toBe('text'); - }); - - it('If the target of objectActivated is icon and the existing menu is not icon, the menu should be changed to icon.', () => { - imageEditorMock.ui.submenu = 'crop'; - spyOn(imageEditorMock.ui, 'changeMenu'); - spyOn(imageEditorMock.ui.icon, 'setIconPickerColor'); - imageEditorMock.fire('objectActivated', { - id: 1, - type: 'icon' - }); - - expect(imageEditorMock.ui.changeMenu.calls.mostRecent().args[0]).toBe('icon'); - expect(imageEditorMock.ui.icon.setIconPickerColor).toHaveBeenCalled(); - }); + describe('objectScaled', () => { + it('If objectScaled occurs on an object of type text, fontSize must be changed.', () => { + imageEditorMock.ui.text.fontSize = 0; + imageEditorMock.fire('objectScaled', { + type: 'i-text', + fontSize: 20, }); - describe('addObjectAfter', () => { - it('When addObjectAfter occurs, the shape\'s maxStrokeValue should be changed to match the size of the added object.', () => { - spyOn(imageEditorMock.ui.shape, 'setMaxStrokeValue'); - spyOn(imageEditorMock.ui.shape, 'changeStandbyMode'); - imageEditorMock.fire('addObjectAfter', { - type: 'circle', - width: 100, - height: 200 - }); - - expect(imageEditorMock.ui.shape.setMaxStrokeValue.calls.mostRecent().args[0]).toBe(100); - expect(imageEditorMock.ui.shape.changeStandbyMode).toHaveBeenCalled(); - }); - }); + expect(imageEditorMock.ui.text.fontSize).toBe(20); + }); - describe('objectScaled', () => { - it('If objectScaled occurs on an object of type text, fontSize must be changed.', () => { - imageEditorMock.ui.text.fontSize = 0; - imageEditorMock.fire('objectScaled', { - type: 'i-text', - fontSize: 20 - }); - - expect(imageEditorMock.ui.text.fontSize).toBe(20); - }); - - it('If objectScaled is for a shape type object and strokeValue is greater than the size of the object, the value should change.', () => { - spyOn(imageEditorMock.ui.shape, 'getStrokeValue').and.returnValue(20); - spyOn(imageEditorMock.ui.shape, 'setStrokeValue'); - imageEditorMock.fire('objectScaled', { - type: 'rect', - width: 10, - height: 10 - }); - expect(imageEditorMock.ui.shape.setStrokeValue.calls.mostRecent().args[0]).toBe(10); - }); + it('If objectScaled is for a shape type object and strokeValue is greater than the size of the object, the value should change.', () => { + spyOn(imageEditorMock.ui.shape, 'getStrokeValue').and.returnValue(20); + spyOn(imageEditorMock.ui.shape, 'setStrokeValue'); + imageEditorMock.fire('objectScaled', { + type: 'rect', + width: 10, + height: 10, }); + expect(imageEditorMock.ui.shape.setStrokeValue.calls.mostRecent().args[0]).toBe(10); + }); + }); - describe('selectionCleared', () => { - it('If selectionCleared occurs in the text menu state, the menu should be closed.', () => { - imageEditorMock.ui.submenu = 'text'; - spyOn(imageEditorMock, 'changeCursor'); + describe('selectionCleared', () => { + it('If selectionCleared occurs in the text menu state, the menu should be closed.', () => { + imageEditorMock.ui.submenu = 'text'; + spyOn(imageEditorMock, 'changeCursor'); - imageEditorMock.fire('selectionCleared'); - expect(imageEditorMock.changeCursor.calls.mostRecent().args[0]).toBe('text'); - }); - }); + imageEditorMock.fire('selectionCleared'); + expect(imageEditorMock.changeCursor.calls.mostRecent().args[0]).toBe('text'); + }); }); + }); }); diff --git a/test/arrowLine.spec.js b/test/arrowLine.spec.js index 7de15b23b..2477977e2 100644 --- a/test/arrowLine.spec.js +++ b/test/arrowLine.spec.js @@ -5,69 +5,69 @@ import ArrowLine from '../src/js/extension/arrowLine'; describe('AllowLine', () => { - let ctx, arrowLine, linePath; + let ctx, arrowLine, linePath; - beforeEach(() => { - ctx = { - lineWidth: 1, - beginPath: jasmine.createSpy('beginPath'), - moveTo: jasmine.createSpy('moveTo'), - lineTo: jasmine.createSpy('lineTo'), - closePath: jasmine.createSpy('closePath') - }; - arrowLine = new ArrowLine(); - arrowLine.ctx = ctx; - linePath = { - fromX: 1, - fromY: 1, - toX: 10, - toY: 10 - }; - }); + beforeEach(() => { + ctx = { + lineWidth: 1, + beginPath: jasmine.createSpy('beginPath'), + moveTo: jasmine.createSpy('moveTo'), + lineTo: jasmine.createSpy('lineTo'), + closePath: jasmine.createSpy('closePath'), + }; + arrowLine = new ArrowLine(); + arrowLine.ctx = ctx; + linePath = { + fromX: 1, + fromY: 1, + toX: 10, + toY: 10, + }; + }); - it('When attaching the "chevron" type to the endpoint, you need to draw the "v" calculated according to the angle around the "tail" of the line.', () => { - arrowLine.arrowType = { - tail: 'chevron' - }; - arrowLine._drawDecoratorPath(linePath); + it('When attaching the "chevron" type to the endpoint, you need to draw the "v" calculated according to the angle around the "tail" of the line.', () => { + arrowLine.arrowType = { + tail: 'chevron', + }; + arrowLine._drawDecoratorPath(linePath); - const firstPoint = ctx.moveTo.calls.argsFor(0).map(value => Math.round(value)); - const secondPoint = ctx.lineTo.calls.argsFor(0).map(value => Math.round(value)); - const lastPoint = ctx.lineTo.calls.argsFor(1).map(value => Math.round(value)); + const firstPoint = ctx.moveTo.calls.argsFor(0).map((value) => Math.round(value)); + const secondPoint = ctx.lineTo.calls.argsFor(0).map((value) => Math.round(value)); + const lastPoint = ctx.lineTo.calls.argsFor(1).map((value) => Math.round(value)); - expect(firstPoint).toEqual([9, 7]); - expect(secondPoint).toEqual([10, 10]); - expect(lastPoint).toEqual([7, 9]); - }); + expect(firstPoint).toEqual([9, 7]); + expect(secondPoint).toEqual([10, 10]); + expect(lastPoint).toEqual([7, 9]); + }); - it('When attaching the "chevron" type to the startpoint, you need to draw the "v" calculated according to the angle around the "head" of the line.', () => { - arrowLine.arrowType = { - head: 'chevron' - }; - arrowLine._drawDecoratorPath(linePath); + it('When attaching the "chevron" type to the startpoint, you need to draw the "v" calculated according to the angle around the "head" of the line.', () => { + arrowLine.arrowType = { + head: 'chevron', + }; + arrowLine._drawDecoratorPath(linePath); - const firstPoint = ctx.moveTo.calls.argsFor(0).map(value => Math.round(value)); - const secondPoint = ctx.lineTo.calls.argsFor(0).map(value => Math.round(value)); - const lastPoint = ctx.lineTo.calls.argsFor(1).map(value => Math.round(value)); + const firstPoint = ctx.moveTo.calls.argsFor(0).map((value) => Math.round(value)); + const secondPoint = ctx.lineTo.calls.argsFor(0).map((value) => Math.round(value)); + const lastPoint = ctx.lineTo.calls.argsFor(1).map((value) => Math.round(value)); - expect(firstPoint).toEqual([2, 4]); - expect(secondPoint).toEqual([1, 1]); - expect(lastPoint).toEqual([4, 2]); - }); + expect(firstPoint).toEqual([2, 4]); + expect(secondPoint).toEqual([1, 1]); + expect(lastPoint).toEqual([4, 2]); + }); - it('"triangle" should be a triangular shape that closes the path with closePath after drawing.', () => { - arrowLine.arrowType = { - head: 'triangle' - }; - arrowLine._drawDecoratorPath(linePath); + it('"triangle" should be a triangular shape that closes the path with closePath after drawing.', () => { + arrowLine.arrowType = { + head: 'triangle', + }; + arrowLine._drawDecoratorPath(linePath); - const firstPoint = ctx.moveTo.calls.argsFor(0).map(value => Math.round(value)); - const secondPoint = ctx.lineTo.calls.argsFor(0).map(value => Math.round(value)); - const thirdPoint = ctx.lineTo.calls.argsFor(1).map(value => Math.round(value)); + const firstPoint = ctx.moveTo.calls.argsFor(0).map((value) => Math.round(value)); + const secondPoint = ctx.lineTo.calls.argsFor(0).map((value) => Math.round(value)); + const thirdPoint = ctx.lineTo.calls.argsFor(1).map((value) => Math.round(value)); - expect(firstPoint).toEqual([1, 3]); - expect(secondPoint).toEqual([1, 1]); - expect(thirdPoint).toEqual([3, 1]); - expect(ctx.closePath.calls.count()).toBe(1); - }); + expect(firstPoint).toEqual([1, 3]); + expect(secondPoint).toEqual([1, 1]); + expect(thirdPoint).toEqual([3, 1]); + expect(ctx.closePath.calls.count()).toBe(1); + }); }); diff --git a/test/command.spec.js b/test/command.spec.js index ec6656c6a..705b0951d 100644 --- a/test/command.spec.js +++ b/test/command.spec.js @@ -3,534 +3,560 @@ * @fileoverview Tests command with command-factory */ import snippet from 'tui-code-snippet'; -import {Promise} from '../src/js/util'; +import { Promise } from '../src/js/util'; import fabric from 'fabric'; import Invoker from '../src/js/invoker'; import commandFactory from '../src/js/factory/command'; import Graphics from '../src/js/graphics'; -import {commandNames as commands} from '../src/js/consts'; -import {getCachedUndoDataForDimension} from '../src/js/helper/selectionModifyHelper'; +import { commandNames as commands } from '../src/js/consts'; +import { getCachedUndoDataForDimension } from '../src/js/helper/selectionModifyHelper'; describe('commandFactory', () => { - let invoker, mockImage, canvas, graphics; - - beforeEach(() => { - graphics = new Graphics(document.createElement('canvas')); - invoker = new Invoker(); - mockImage = new fabric.Image(); - - graphics.setCanvasImage('', mockImage); - canvas = graphics.getCanvas(); - }); - - describe('functions', () => { - it('can register custom command', done => { - const testCommand = { - name: 'testCommand', - execute() { - }, - undo() { - } - }; - - spyOn(testCommand, 'execute').and.returnValue(Promise.resolve('testCommand')); - spyOn(testCommand, 'undo').and.returnValue(Promise.resolve()); - - commandFactory.register(testCommand); - - const command = commandFactory.create('testCommand'); - expect(command).not.toBe(null); - - invoker.execute('testCommand', graphics).then(commandName => { - expect(commandName).toBe('testCommand'); - expect(testCommand.execute).toHaveBeenCalledWith(graphics); - done(); - })['catch'](message => { - fail(message); - done(); - }); + let invoker, mockImage, canvas, graphics; + + beforeEach(() => { + graphics = new Graphics(document.createElement('canvas')); + invoker = new Invoker(); + mockImage = new fabric.Image(); + + graphics.setCanvasImage('', mockImage); + canvas = graphics.getCanvas(); + }); + + describe('functions', () => { + it('can register custom command', (done) => { + const testCommand = { + name: 'testCommand', + execute() {}, + undo() {}, + }; + + spyOn(testCommand, 'execute').and.returnValue(Promise.resolve('testCommand')); + spyOn(testCommand, 'undo').and.returnValue(Promise.resolve()); + + commandFactory.register(testCommand); + + const command = commandFactory.create('testCommand'); + expect(command).not.toBe(null); + + invoker + .execute('testCommand', graphics) + .then((commandName) => { + expect(commandName).toBe('testCommand'); + expect(testCommand.execute).toHaveBeenCalledWith(graphics); + done(); + }) + ['catch']((message) => { + fail(message); + done(); }); + }); - it('can pass parameters on execute', done => { - commandFactory.register({ - name: 'testCommand', - execute(compMap, obj1, obj2, obj3) { - expect(obj1).toBe(1); - expect(obj2).toBe(2); - expect(obj3).toBe(3); - - return Promise.resolve(); - } - }); - - invoker.execute('testCommand', graphics, 1, 2, 3).then(() => { - done(); - })['catch'](message => { - fail(message); - done(); - }); + it('can pass parameters on execute', (done) => { + commandFactory.register({ + name: 'testCommand', + execute(compMap, obj1, obj2, obj3) { + expect(obj1).toBe(1); + expect(obj2).toBe(2); + expect(obj3).toBe(3); + + return Promise.resolve(); + }, + }); + + invoker + .execute('testCommand', graphics, 1, 2, 3) + .then(() => { + done(); + }) + ['catch']((message) => { + fail(message); + done(); }); + }); - it('can pass parameters on undo', done => { - commandFactory.register({ - name: 'testCommand', - execute() { - return Promise.resolve(); - }, - undo(compMap, obj1, obj2, obj3) { - expect(obj1).toBe(1); - expect(obj2).toBe(2); - expect(obj3).toBe(3); - - return Promise.resolve(); - } - }); - - invoker.execute('testCommand', graphics, 1, 2, 3).then(() => ( - invoker.undo() - )).then(() => done() - )['catch'](message => { - fail(message); - done(); - }); + it('can pass parameters on undo', (done) => { + commandFactory.register({ + name: 'testCommand', + execute() { + return Promise.resolve(); + }, + undo(compMap, obj1, obj2, obj3) { + expect(obj1).toBe(1); + expect(obj2).toBe(2); + expect(obj3).toBe(3); + + return Promise.resolve(); + }, + }); + + invoker + .execute('testCommand', graphics, 1, 2, 3) + .then(() => invoker.undo()) + .then(() => done()) + ['catch']((message) => { + fail(message); + done(); }); }); + }); - describe('addObjectCommand', () => { - let obj; + describe('addObjectCommand', () => { + let obj; - it('should stamp object', done => { - obj = new fabric.Rect(); - invoker.execute(commands.ADD_OBJECT, graphics, obj).then(() => { - expect(snippet.hasStamp(obj)).toBe(true); - done(); - }); - }); + it('should stamp object', (done) => { + obj = new fabric.Rect(); + invoker.execute(commands.ADD_OBJECT, graphics, obj).then(() => { + expect(snippet.hasStamp(obj)).toBe(true); + done(); + }); + }); - it('should add object to canvas', done => { - obj = new fabric.Rect(); - invoker.execute(commands.ADD_OBJECT, graphics, obj).then(() => { - expect(canvas.contains(obj)).toBe(true); - done(); - }); - }); + it('should add object to canvas', (done) => { + obj = new fabric.Rect(); + invoker.execute(commands.ADD_OBJECT, graphics, obj).then(() => { + expect(canvas.contains(obj)).toBe(true); + done(); + }); + }); - it('"undo()" should remove object from canvas', done => { - obj = new fabric.Rect(); - invoker.execute(commands.ADD_OBJECT, graphics, obj).then(() => invoker.undo()).then(() => { - expect(canvas.contains(obj)).toBe(false); - done(); - }); + it('"undo()" should remove object from canvas', (done) => { + obj = new fabric.Rect(); + invoker + .execute(commands.ADD_OBJECT, graphics, obj) + .then(() => invoker.undo()) + .then(() => { + expect(canvas.contains(obj)).toBe(false); + done(); }); }); - describe('changeSelectionCommand', () => { - let obj; - - beforeEach(() => { - spyOn(canvas, 'getPointer'); - obj = new fabric.Rect({ - width: 10, - height: 10, - top: 10, - left: 10, - scaleX: 1, - scaleY: 1, - angle: 0 - }); - graphics._addFabricObject(obj); - graphics._onMouseDown({target: obj}); - - const props = [{ - id: graphics.getObjectId(obj), - width: 30, - height: 30, - top: 30, - left: 30, - scaleX: 0.5, - scaleY: 0.5, - angle: 10 - }]; - const makeCommand = commandFactory.create(commands.CHANGE_SELECTION, graphics, props); - makeCommand.execute(graphics, props); - invoker.pushUndoStack(makeCommand); - }); + }); + describe('changeSelectionCommand', () => { + let obj; - it('should work undo command correctly', done => { - invoker.undo().then(() => { - expect(obj.width).toBe(10); - expect(obj.height).toBe(10); - expect(obj.left).toBe(10); - expect(obj.top).toBe(10); - expect(obj.scaleX).toBe(1); - expect(obj.scaleY).toBe(1); - expect(obj.angle).toBe(0); - done(); - }); - }); + beforeEach(() => { + spyOn(canvas, 'getPointer'); + obj = new fabric.Rect({ + width: 10, + height: 10, + top: 10, + left: 10, + scaleX: 1, + scaleY: 1, + angle: 0, + }); + graphics._addFabricObject(obj); + graphics._onMouseDown({ target: obj }); + + const props = [ + { + id: graphics.getObjectId(obj), + width: 30, + height: 30, + top: 30, + left: 30, + scaleX: 0.5, + scaleY: 0.5, + angle: 10, + }, + ]; + const makeCommand = commandFactory.create(commands.CHANGE_SELECTION, graphics, props); + makeCommand.execute(graphics, props); + invoker.pushUndoStack(makeCommand); + }); - it('should work redo command correctly', done => { - invoker.undo().then(() => { - invoker.redo().then(() => { - expect(obj.width).toBe(30); - expect(obj.height).toBe(30); - expect(obj.left).toBe(30); - expect(obj.top).toBe(30); - expect(obj.scaleX).toBe(0.5); - expect(obj.scaleY).toBe(0.5); - expect(obj.angle).toBe(10); - done(); - }); - }); - }); + it('should work undo command correctly', (done) => { + invoker.undo().then(() => { + expect(obj.width).toBe(10); + expect(obj.height).toBe(10); + expect(obj.left).toBe(10); + expect(obj.top).toBe(10); + expect(obj.scaleX).toBe(1); + expect(obj.scaleY).toBe(1); + expect(obj.angle).toBe(0); + done(); + }); }); - describe('loadImageCommand', () => { - const imageURL = 'base/test/fixtures/sampleImage.jpg'; + it('should work redo command correctly', (done) => { + invoker.undo().then(() => { + invoker.redo().then(() => { + expect(obj.width).toBe(30); + expect(obj.height).toBe(30); + expect(obj.left).toBe(30); + expect(obj.top).toBe(30); + expect(obj.scaleX).toBe(0.5); + expect(obj.scaleY).toBe(0.5); + expect(obj.angle).toBe(10); + done(); + }); + }); + }); + }); - beforeEach(() => { - graphics.setCanvasImage('', null); - }); + describe('loadImageCommand', () => { + const imageURL = 'base/test/fixtures/sampleImage.jpg'; - it('should clear canvas', () => { - spyOn(canvas, 'clear'); - invoker.execute(commands.LOAD_IMAGE, graphics, 'image', imageURL); + beforeEach(() => { + graphics.setCanvasImage('', null); + }); - expect(canvas.clear).toHaveBeenCalled(); - }); + it('should clear canvas', () => { + spyOn(canvas, 'clear'); + invoker.execute(commands.LOAD_IMAGE, graphics, 'image', imageURL); - it('should load new image', done => { - invoker.execute(commands.LOAD_IMAGE, graphics, 'image', imageURL).then(sizeChange => { - expect(graphics.getImageName()).toEqual('image'); - expect(graphics.getCanvasImage().getSrc()).toContain(imageURL); - expect(sizeChange.oldWidth).toEqual(jasmine.any(Number)); - expect(sizeChange.oldHeight).toEqual(jasmine.any(Number)); - expect(sizeChange.newWidth).toEqual(jasmine.any(Number)); - expect(sizeChange.newHeight).toEqual(jasmine.any(Number)); - done(); - }); - }); + expect(canvas.clear).toHaveBeenCalled(); + }); - it('After running the LOAD_IMAGE command, existing objects should not include cropzone.', done => { - const objCropzone = new fabric.Object({type: 'cropzone'}); + it('should load new image', (done) => { + invoker.execute(commands.LOAD_IMAGE, graphics, 'image', imageURL).then((sizeChange) => { + expect(graphics.getImageName()).toEqual('image'); + expect(graphics.getCanvasImage().getSrc()).toContain(imageURL); + expect(sizeChange.oldWidth).toEqual(jasmine.any(Number)); + expect(sizeChange.oldHeight).toEqual(jasmine.any(Number)); + expect(sizeChange.newWidth).toEqual(jasmine.any(Number)); + expect(sizeChange.newHeight).toEqual(jasmine.any(Number)); + done(); + }); + }); - invoker.execute(commands.ADD_OBJECT, graphics, objCropzone).then(() => { - invoker.execute(commands.LOAD_IMAGE, graphics, 'image', imageURL).then(() => { - const lastUndoIndex = invoker._undoStack.length - 1; - const savedObjects = invoker._undoStack[lastUndoIndex].undoData.objects; + it('After running the LOAD_IMAGE command, existing objects should not include cropzone.', (done) => { + const objCropzone = new fabric.Object({ type: 'cropzone' }); - expect(savedObjects.length).toBe(0); - done(); - }); - }); - }); + invoker.execute(commands.ADD_OBJECT, graphics, objCropzone).then(() => { + invoker.execute(commands.LOAD_IMAGE, graphics, 'image', imageURL).then(() => { + const lastUndoIndex = invoker._undoStack.length - 1; + const savedObjects = invoker._undoStack[lastUndoIndex].undoData.objects; - it('`evented` attribute of the saved object must be true after LOAD_IMAGE.', done => { - const objCircle = new fabric.Object({ - type: 'circle', - evented: false - }); - - invoker.execute(commands.ADD_OBJECT, graphics, objCircle).then(() => { - invoker.execute(commands.LOAD_IMAGE, graphics, 'image', imageURL).then(() => { - const lastUndoIndex = invoker._undoStack.length - 1; - const [savedObject] = invoker._undoStack[lastUndoIndex].undoData.objects; - - expect(savedObject.evented).toBe(true); - done(); - }); - }); + expect(savedObjects.length).toBe(0); + done(); }); + }); + }); + + it('`evented` attribute of the saved object must be true after LOAD_IMAGE.', (done) => { + const objCircle = new fabric.Object({ + type: 'circle', + evented: false, + }); - it('"undo()" should clear image if not exists prev image', done => { - invoker.execute(commands.LOAD_IMAGE, graphics, 'image', imageURL).then(() => ( - invoker.undo() - )).then(() => { - expect(graphics.getCanvasImage()).toBe(null); - expect(graphics.getImageName()).toBe(''); - done(); - }); + invoker.execute(commands.ADD_OBJECT, graphics, objCircle).then(() => { + invoker.execute(commands.LOAD_IMAGE, graphics, 'image', imageURL).then(() => { + const lastUndoIndex = invoker._undoStack.length - 1; + const [savedObject] = invoker._undoStack[lastUndoIndex].undoData.objects; + + expect(savedObject.evented).toBe(true); + done(); }); + }); + }); - it('"undo()" should restore to prev image', done => { - const newImageURL = 'base/test/fixtures/TOAST%20UI%20Component.png'; - - invoker.execute(commands.LOAD_IMAGE, graphics, 'image', imageURL).then(() => ( - invoker.execute(commands.LOAD_IMAGE, graphics, 'newImage', newImageURL) - )).then(() => { - expect(graphics.getImageName()).toBe('newImage'); - expect(graphics.getCanvasImage().getSrc()).toContain(newImageURL); - - return invoker.undo(); - }).then(() => { - expect(graphics.getImageName()).toEqual('image'); - expect(graphics.getCanvasImage().getSrc()).toContain(imageURL); - done(); - }); + it('"undo()" should clear image if not exists prev image', (done) => { + invoker + .execute(commands.LOAD_IMAGE, graphics, 'image', imageURL) + .then(() => invoker.undo()) + .then(() => { + expect(graphics.getCanvasImage()).toBe(null); + expect(graphics.getImageName()).toBe(''); + done(); }); }); - describe('flipImageCommand', () => { - it('flipX', () => { - const originFlipX = mockImage.flipX; + it('"undo()" should restore to prev image', (done) => { + const newImageURL = 'base/test/fixtures/TOAST%20UI%20Component.png'; - invoker.execute(commands.FLIP_IMAGE, graphics, 'flipX'); + invoker + .execute(commands.LOAD_IMAGE, graphics, 'image', imageURL) + .then(() => invoker.execute(commands.LOAD_IMAGE, graphics, 'newImage', newImageURL)) + .then(() => { + expect(graphics.getImageName()).toBe('newImage'); + expect(graphics.getCanvasImage().getSrc()).toContain(newImageURL); - expect(mockImage.flipX).toBe(!originFlipX); + return invoker.undo(); + }) + .then(() => { + expect(graphics.getImageName()).toEqual('image'); + expect(graphics.getCanvasImage().getSrc()).toContain(imageURL); + done(); }); + }); + }); - it('flipY', () => { - const originFlipY = mockImage.flipY; + describe('flipImageCommand', () => { + it('flipX', () => { + const originFlipX = mockImage.flipX; - invoker.execute(commands.FLIP_IMAGE, graphics, 'flipY'); + invoker.execute(commands.FLIP_IMAGE, graphics, 'flipX'); - expect(mockImage.flipY).toBe(!originFlipY); - }); + expect(mockImage.flipX).toBe(!originFlipX); + }); - it('resetFlip', () => { - mockImage.flipX = true; - mockImage.flipY = true; + it('flipY', () => { + const originFlipY = mockImage.flipY; - invoker.execute(commands.FLIP_IMAGE, graphics, 'reset'); + invoker.execute(commands.FLIP_IMAGE, graphics, 'flipY'); - expect(mockImage.flipX).toBe(false); - expect(mockImage.flipY).toBe(false); - }); + expect(mockImage.flipY).toBe(!originFlipY); + }); - it('"undo()" should restore flipX', done => { - const originFlipX = mockImage.flipX; + it('resetFlip', () => { + mockImage.flipX = true; + mockImage.flipY = true; - invoker.execute(commands.FLIP_IMAGE, graphics, 'flipX').then(() => ( - invoker.undo() - )).then(() => { - expect(mockImage.flipX).toBe(originFlipX); - done(); - }); - }); + invoker.execute(commands.FLIP_IMAGE, graphics, 'reset'); + + expect(mockImage.flipX).toBe(false); + expect(mockImage.flipY).toBe(false); + }); - it('"undo()" should restore filpY', done => { - const originFlipY = mockImage.flipY; + it('"undo()" should restore flipX', (done) => { + const originFlipX = mockImage.flipX; - invoker.execute(commands.FLIP_IMAGE, graphics, 'flipY').then(() => ( - invoker.undo() - )).then(() => { - expect(mockImage.flipY).toBe(originFlipY); - done(); - }); + invoker + .execute(commands.FLIP_IMAGE, graphics, 'flipX') + .then(() => invoker.undo()) + .then(() => { + expect(mockImage.flipX).toBe(originFlipX); + done(); }); }); - describe('textCommand', () => { - let textObjectId; - const defaultFontSize = 50; - const defaultUnderline = false; - beforeEach(done => { - invoker.execute(commands.ADD_TEXT, graphics, 'text', { - styles: { - fontSize: defaultFontSize, - underline: false - } - }).then(textObject => { - textObjectId = textObject.id; - done(); - }); + it('"undo()" should restore filpY', (done) => { + const originFlipY = mockImage.flipY; + + invoker + .execute(commands.FLIP_IMAGE, graphics, 'flipY') + .then(() => invoker.undo()) + .then(() => { + expect(mockImage.flipY).toBe(originFlipY); + done(); }); - it('"changeTextStyle" should set text style', done => { - invoker.execute(commands.CHANGE_TEXT_STYLE, graphics, textObjectId, { - fontSize: 30, - underline: true - }).then(() => { - const textObject = graphics.getObject(textObjectId); - expect(textObject.fontSize).toBe(30); - expect(textObject.underline).toBe(true); - done(); - }); + }); + }); + + describe('textCommand', () => { + let textObjectId; + const defaultFontSize = 50; + const defaultUnderline = false; + beforeEach((done) => { + invoker + .execute(commands.ADD_TEXT, graphics, 'text', { + styles: { + fontSize: defaultFontSize, + underline: false, + }, + }) + .then((textObject) => { + textObjectId = textObject.id; + done(); + }); + }); + it('"changeTextStyle" should set text style', (done) => { + invoker + .execute(commands.CHANGE_TEXT_STYLE, graphics, textObjectId, { + fontSize: 30, + underline: true, + }) + .then(() => { + const textObject = graphics.getObject(textObjectId); + expect(textObject.fontSize).toBe(30); + expect(textObject.underline).toBe(true); + done(); }); - it('"undo()" should restore fontSize', done => { - invoker.execute(commands.CHANGE_TEXT_STYLE, graphics, textObjectId, { - fontSize: 30, - underline: true - }).then(() => invoker.undo()).then(() => { - const textObject = graphics.getObject(textObjectId); - expect(textObject.fontSize).toBe(defaultFontSize); - expect(textObject.underline).toBe(defaultUnderline); - done(); - }); + }); + it('"undo()" should restore fontSize', (done) => { + invoker + .execute(commands.CHANGE_TEXT_STYLE, graphics, textObjectId, { + fontSize: 30, + underline: true, + }) + .then(() => invoker.undo()) + .then(() => { + const textObject = graphics.getObject(textObjectId); + expect(textObject.fontSize).toBe(defaultFontSize); + expect(textObject.underline).toBe(defaultUnderline); + done(); }); }); + }); - describe('rotationImageCommand', () => { - it('"rotate()" should add angle', () => { - const originAngle = mockImage.angle; + describe('rotationImageCommand', () => { + it('"rotate()" should add angle', () => { + const originAngle = mockImage.angle; - invoker.execute(commands.ROTATE_IMAGE, graphics, 'rotate', 10); + invoker.execute(commands.ROTATE_IMAGE, graphics, 'rotate', 10); - expect(mockImage.angle).toBe(originAngle + 10); - }); + expect(mockImage.angle).toBe(originAngle + 10); + }); - it('"setAngle()" should set angle', () => { - mockImage.angle = 100; - invoker.execute(commands.ROTATE_IMAGE, graphics, 'setAngle', 30); + it('"setAngle()" should set angle', () => { + mockImage.angle = 100; + invoker.execute(commands.ROTATE_IMAGE, graphics, 'setAngle', 30); - expect(mockImage.angle).toBe(30); - }); + expect(mockImage.angle).toBe(30); + }); - it('"undo()" should restore angle', done => { - const originalAngle = mockImage.angle; + it('"undo()" should restore angle', (done) => { + const originalAngle = mockImage.angle; - invoker.execute(commands.ROTATE_IMAGE, graphics, 'setAngle', 100).then(() => ( - invoker.undo() - )).then(() => { - expect(mockImage.angle).toBe(originalAngle); - done(); - }); + invoker + .execute(commands.ROTATE_IMAGE, graphics, 'setAngle', 100) + .then(() => invoker.undo()) + .then(() => { + expect(mockImage.angle).toBe(originalAngle); + done(); }); }); - - describe('shapeCommand', () => { - let shapeObjectId; - const defaultStrokeWidth = 12; - beforeEach(done => { - invoker.execute(commands.ADD_SHAPE, graphics, 'rect', { - strokeWidth: defaultStrokeWidth - }).then(shapeObject => { - shapeObjectId = shapeObject.id; - done(); - }); + }); + + describe('shapeCommand', () => { + let shapeObjectId; + const defaultStrokeWidth = 12; + beforeEach((done) => { + invoker + .execute(commands.ADD_SHAPE, graphics, 'rect', { + strokeWidth: defaultStrokeWidth, + }) + .then((shapeObject) => { + shapeObjectId = shapeObject.id; + done(); }); - it('"changeShape" should set strokeWidth', done => { - invoker.execute(commands.CHANGE_SHAPE, graphics, shapeObjectId, { - strokeWidth: 50 - }).then(() => { - const shapeObject = graphics.getObject(shapeObjectId); - expect(shapeObject.strokeWidth).toBe(50); - done(); - }); + }); + it('"changeShape" should set strokeWidth', (done) => { + invoker + .execute(commands.CHANGE_SHAPE, graphics, shapeObjectId, { + strokeWidth: 50, + }) + .then(() => { + const shapeObject = graphics.getObject(shapeObjectId); + expect(shapeObject.strokeWidth).toBe(50); + done(); }); - it('"redo()" should restore strokeWidth', done => { - invoker.execute(commands.CHANGE_SHAPE, graphics, shapeObjectId, { - strokeWidth: 50 - }).then(() => invoker.undo()).then(() => { - const shapeObject = graphics.getObject(shapeObjectId); - expect(shapeObject.strokeWidth).toBe(defaultStrokeWidth); - done(); - }); + }); + it('"redo()" should restore strokeWidth', (done) => { + invoker + .execute(commands.CHANGE_SHAPE, graphics, shapeObjectId, { + strokeWidth: 50, + }) + .then(() => invoker.undo()) + .then(() => { + const shapeObject = graphics.getObject(shapeObjectId); + expect(shapeObject.strokeWidth).toBe(defaultStrokeWidth); + done(); }); }); + }); - describe('clearCommand', () => { - let canvasContext; + describe('clearCommand', () => { + let canvasContext; - beforeEach(() => { - canvasContext = canvas; - }); + beforeEach(() => { + canvasContext = canvas; + }); - it('should clear all objects', () => { - const objects = [ - new fabric.Rect(), - new fabric.Rect(), - new fabric.Rect() - ]; - canvas.add.apply(canvasContext, objects); + it('should clear all objects', () => { + const objects = [new fabric.Rect(), new fabric.Rect(), new fabric.Rect()]; + canvas.add.apply(canvasContext, objects); - expect(canvas.contains(objects[0])).toBe(true); - expect(canvas.contains(objects[1])).toBe(true); - expect(canvas.contains(objects[2])).toBe(true); + expect(canvas.contains(objects[0])).toBe(true); + expect(canvas.contains(objects[1])).toBe(true); + expect(canvas.contains(objects[2])).toBe(true); - invoker.execute(commands.CLEAR_OBJECTS, graphics); + invoker.execute(commands.CLEAR_OBJECTS, graphics); - expect(canvas.contains(objects[0])).toBe(false); - expect(canvas.contains(objects[1])).toBe(false); - expect(canvas.contains(objects[2])).toBe(false); - }); + expect(canvas.contains(objects[0])).toBe(false); + expect(canvas.contains(objects[1])).toBe(false); + expect(canvas.contains(objects[2])).toBe(false); + }); - it('"undo()" restore all objects', done => { - const objects = [ - new fabric.Rect(), - new fabric.Rect(), - new fabric.Rect() - ]; - canvas.add.apply(canvasContext, objects); - invoker.execute(commands.CLEAR_OBJECTS, graphics).then(() => ( - invoker.undo() - )).then(() => { - expect(canvas.contains(objects[0])).toBe(true); - expect(canvas.contains(objects[1])).toBe(true); - expect(canvas.contains(objects[2])).toBe(true); - done(); - }); + it('"undo()" restore all objects', (done) => { + const objects = [new fabric.Rect(), new fabric.Rect(), new fabric.Rect()]; + canvas.add.apply(canvasContext, objects); + invoker + .execute(commands.CLEAR_OBJECTS, graphics) + .then(() => invoker.undo()) + .then(() => { + expect(canvas.contains(objects[0])).toBe(true); + expect(canvas.contains(objects[1])).toBe(true); + expect(canvas.contains(objects[2])).toBe(true); + done(); }); }); + }); - describe('removeCommand', () => { - let object, object2, group; - - beforeEach(() => { - object = new fabric.Rect({ - left: 10, - top: 10 - }); - object2 = new fabric.Rect({ - left: 5, - top: 20 - }); - group = new fabric.Group(); - - graphics.add(object); - graphics.add(object2); - graphics.add(group); - group.add(object, object2); - }); + describe('removeCommand', () => { + let object, object2, group; - it('should remove an object', () => { - graphics.setActiveObject(object); - invoker.execute(commands.REMOVE_OBJECT, graphics, snippet.stamp(object)); + beforeEach(() => { + object = new fabric.Rect({ + left: 10, + top: 10, + }); + object2 = new fabric.Rect({ + left: 5, + top: 20, + }); + group = new fabric.Group(); + + graphics.add(object); + graphics.add(object2); + graphics.add(group); + group.add(object, object2); + }); - expect(canvas.contains(object)).toBe(false); - }); + it('should remove an object', () => { + graphics.setActiveObject(object); + invoker.execute(commands.REMOVE_OBJECT, graphics, snippet.stamp(object)); - it('should remove objects group', () => { - canvas.setActiveObject(group); - invoker.execute(commands.REMOVE_OBJECT, graphics, snippet.stamp(group)); + expect(canvas.contains(object)).toBe(false); + }); - expect(canvas.contains(object)).toBe(false); - expect(canvas.contains(object2)).toBe(false); - }); + it('should remove objects group', () => { + canvas.setActiveObject(group); + invoker.execute(commands.REMOVE_OBJECT, graphics, snippet.stamp(group)); + + expect(canvas.contains(object)).toBe(false); + expect(canvas.contains(object2)).toBe(false); + }); - it('"undo()" should restore the removed object', done => { - canvas.setActiveObject(object); + it('"undo()" should restore the removed object', (done) => { + canvas.setActiveObject(object); - invoker.execute(commands.REMOVE_OBJECT, graphics, snippet.stamp(object)).then(() => ( - invoker.undo() - )).then(() => { - expect(canvas.contains(object)).toBe(true); - done(); - }); + invoker + .execute(commands.REMOVE_OBJECT, graphics, snippet.stamp(object)) + .then(() => invoker.undo()) + .then(() => { + expect(canvas.contains(object)).toBe(true); + done(); }); + }); - it('"undo()" should restore the removed objects (group)', done => { - canvas.setActiveObject(group); - invoker.execute(commands.REMOVE_OBJECT, graphics, snippet.stamp(group)).then(() => ( - invoker.undo() - )).then(() => { - expect(canvas.contains(object)).toBe(true); - expect(canvas.contains(object2)).toBe(true); - done(); - }); + it('"undo()" should restore the removed objects (group)', (done) => { + canvas.setActiveObject(group); + invoker + .execute(commands.REMOVE_OBJECT, graphics, snippet.stamp(group)) + .then(() => invoker.undo()) + .then(() => { + expect(canvas.contains(object)).toBe(true); + expect(canvas.contains(object2)).toBe(true); + done(); }); + }); + + it('"undo ()" should restore the position of the removed object (group). ', (done) => { + const activeSelection = graphics.getActiveSelectionFromObjects(canvas.getObjects()); + graphics.setActiveObject(activeSelection); - it('"undo ()" should restore the position of the removed object (group). ', done => { - const activeSelection = graphics.getActiveSelectionFromObjects(canvas.getObjects()); - graphics.setActiveObject(activeSelection); - - invoker.execute(commands.REMOVE_OBJECT, graphics, graphics.getActiveObjectIdForRemove()).then(() => ( - invoker.undo() - )).then(() => { - expect(object.left).toBe(10); - expect(object.top).toBe(10); - expect(object2.left).toBe(5); - expect(object2.top).toBe(20); - done(); - }); + invoker + .execute(commands.REMOVE_OBJECT, graphics, graphics.getActiveObjectIdForRemove()) + .then(() => invoker.undo()) + .then(() => { + expect(object.left).toBe(10); + expect(object.top).toBe(10); + expect(object2.left).toBe(5); + expect(object2.top).toBe(20); + done(); }); }); + }); }); diff --git a/test/cropper.spec.js b/test/cropper.spec.js index f9b17f389..189167328 100644 --- a/test/cropper.spec.js +++ b/test/cropper.spec.js @@ -7,366 +7,373 @@ import fabric from 'fabric'; import $ from 'jquery'; import Cropper from '../src/js/component/cropper'; import Graphics from '../src/js/graphics'; -import {eventNames, CROPZONE_DEFAULT_OPTIONS} from '../src/js/consts'; +import { eventNames, CROPZONE_DEFAULT_OPTIONS } from '../src/js/consts'; describe('Cropper', () => { - let cropper, graphics, canvas; + let cropper, graphics, canvas; - beforeEach(() => { - graphics = new Graphics($('')[0]); - canvas = graphics.getCanvas(); - cropper = new Cropper(graphics); + beforeEach(() => { + graphics = new Graphics($('')[0]); + canvas = graphics.getCanvas(); + cropper = new Cropper(graphics); + }); + + describe('start()', () => { + it('should create a cropzone', () => { + cropper.start(); + + expect(cropper._cropzone).toBeDefined(); + }); + + it('should be applied predefined default options When creating a cropzone', () => { + cropper.start(); + const cropzone = cropper._cropzone; + + snippet.forEach(CROPZONE_DEFAULT_OPTIONS, (optionValue, optionName) => { + expect(cropzone[optionName]).toBe(optionValue); + }); + }); + + it('should add a cropzone to canvas', () => { + spyOn(canvas, 'add'); + cropper.start(); + + expect(canvas.add).toHaveBeenCalledWith(cropper._cropzone); + }); + + it('should no action if a croppzone has been defined', () => { + cropper._cropzone = {}; + spyOn(canvas, 'add'); + cropper.start(); + + expect(canvas.add).not.toHaveBeenCalled(); + }); + + it('should set "evented" of all objects to false', () => { + const objects = [ + new fabric.Rect({ evented: true }), + new fabric.Rect({ evented: true }), + new fabric.Rect({ evented: true }), + ]; + canvas.add(objects[0], objects[1], objects[2]); + + cropper.start(); + expect(objects[0].evented).toBe(false); + expect(objects[1].evented).toBe(false); + expect(objects[2].evented).toBe(false); }); + }); - describe('start()', () => { - it('should create a cropzone', () => { - cropper.start(); - - expect(cropper._cropzone).toBeDefined(); - }); - - it('should be applied predefined default options When creating a cropzone', () => { - cropper.start(); - const cropzone = cropper._cropzone; - - snippet.forEach(CROPZONE_DEFAULT_OPTIONS, (optionValue, optionName) => { - expect(cropzone[optionName]).toBe(optionValue); - }); - }); - - it('should add a cropzone to canvas', () => { - spyOn(canvas, 'add'); - cropper.start(); - - expect(canvas.add).toHaveBeenCalledWith(cropper._cropzone); - }); - - it('should no action if a croppzone has been defined', () => { - cropper._cropzone = {}; - spyOn(canvas, 'add'); - cropper.start(); - - expect(canvas.add).not.toHaveBeenCalled(); - }); - - it('should set "evented" of all objects to false', () => { - const objects = [ - new fabric.Rect({evented: true}), - new fabric.Rect({evented: true}), - new fabric.Rect({evented: true}) - ]; - canvas.add(objects[0], objects[1], objects[2]); - - cropper.start(); - expect(objects[0].evented).toBe(false); - expect(objects[1].evented).toBe(false); - expect(objects[2].evented).toBe(false); - }); + describe('"onFabricMouseDown()"', () => { + let fEvent; + beforeEach(() => { + fEvent = { + e: {}, + }; + spyOn(canvas, 'getPointer').and.returnValue({ + x: 10, + y: 20, + }); }); - describe('"onFabricMouseDown()"', () => { - let fEvent; - beforeEach(() => { - fEvent = { - e: {} - }; - spyOn(canvas, 'getPointer').and.returnValue({ - x: 10, - y: 20 - }); - }); - - it('should set "selection" to false', () => { - cropper._onFabricMouseDown(fEvent); - expect(canvas.selection).toBe(false); - }); - - it('should set "startX, startY"', () => { - // canvas.getPointer will return object{x: 10, y: 20} - cropper._onFabricMouseDown(fEvent); - expect(cropper._startX).toEqual(10); - expect(cropper._startY).toEqual(20); - }); + it('should set "selection" to false', () => { + cropper._onFabricMouseDown(fEvent); + expect(canvas.selection).toBe(false); }); - describe('"onFabricMouseMove()', () => { - beforeEach(() => { - spyOn(canvas, 'getPointer').and.returnValue({ - x: 10, - y: 20 - }); - spyOn(canvas, 'getWidth').and.returnValue(100); - spyOn(canvas, 'getHeight').and.returnValue(200); - }); - - it('should re-render(remove->set->add) cropzone ' + - 'if the mouse moving is over the threshold(=10)', () => { - cropper._startX = 0; - cropper._startY = 0; - - cropper.start(); - spyOn(canvas, 'remove'); - spyOn(cropper._cropzone, 'set'); - spyOn(canvas, 'add'); - cropper._onFabricMouseMove({e: {}}); - - expect(canvas.remove).toHaveBeenCalled(); - expect(cropper._cropzone.set).toHaveBeenCalled(); - expect(canvas.add).toHaveBeenCalled(); - }); - - it('should not re-render cropzone ' + - 'if the mouse moving is under the threshold', () => { - cropper._startX = 14; - cropper._startY = 18; - - cropper.start(); - spyOn(canvas, 'remove'); - spyOn(cropper._cropzone, 'set'); - spyOn(canvas, 'add'); - cropper._onFabricMouseMove({e: {}}); - - expect(canvas.remove).not.toHaveBeenCalled(); - expect(cropper._cropzone.set).not.toHaveBeenCalled(); - expect(canvas.add).not.toHaveBeenCalled(); - }); + it('should set "startX, startY"', () => { + // canvas.getPointer will return object{x: 10, y: 20} + cropper._onFabricMouseDown(fEvent); + expect(cropper._startX).toEqual(10); + expect(cropper._startY).toEqual(20); }); + }); - describe('_calcRectDimensionFromPoint()', () => { - beforeEach(() => { - cropper._startX = 10; - cropper._startY = 20; - snippet.extend(canvas, { - getWidth() { - return 100; - }, - getHeight() { - return 200; - } - }); - }); - - it('should return cropzone-left&top (min: 0, max: startX,Y)', () => { - const x = 20, - y = -1, - expected = { - left: 10, - top: 0, - width: jasmine.any(Number), - height: jasmine.any(Number) - }, - actual = cropper._calcRectDimensionFromPoint(x, y); - - expect(actual).toEqual(expected); - }); - - it('should calculate and return cropzone-width&height', () => { - let x, y, expected, actual; - - x = 30; - y = 40; - expected = { - left: 10, - top: 20, - width: 20, - height: 20 - }; - actual = cropper._calcRectDimensionFromPoint(x, y); - expect(actual).toEqual(expected); - - x = 300; - y = 400; - expected = { - left: 10, - top: 20, - width: 90, - height: 180 - }; - actual = cropper._calcRectDimensionFromPoint(x, y); - expect(actual).toEqual(expected); - }); - - it('should create cropzone that has fixed ratio during shift key is pressed.', () => { - const x = 100; - const y = 200; - const expected = { - left: 10, - top: 20, - width: 180, - height: 180 - }; - - cropper._withShiftKey = true; - - const actual = cropper._calcRectDimensionFromPoint(x, y); - - expect(actual).toEqual(expected); - }); - - it('should create cropzone that inverted current mouse position during shift key is pressed.', () => { - const x = -10; - const y = -20; - const expected = { - left: -10, - top: 0, - width: 20, - height: 20 - }; - - cropper._withShiftKey = true; - - const actual = cropper._calcRectDimensionFromPoint(x, y); - - expect(actual).toEqual(expected); - }); + describe('"onFabricMouseMove()', () => { + beforeEach(() => { + spyOn(canvas, 'getPointer').and.returnValue({ + x: 10, + y: 20, + }); + spyOn(canvas, 'getWidth').and.returnValue(100); + spyOn(canvas, 'getHeight').and.returnValue(200); }); - it('"onFabricMouseUp()" should activate cropzone', () => { - canvas.setActiveObject = jasmine.createSpy(); + it( + 'should re-render(remove->set->add) cropzone ' + + 'if the mouse moving is over the threshold(=10)', + () => { + cropper._startX = 0; + cropper._startY = 0; + cropper.start(); - cropper._onFabricMouseUp(); + spyOn(canvas, 'remove'); + spyOn(cropper._cropzone, 'set'); + spyOn(canvas, 'add'); + cropper._onFabricMouseMove({ e: {} }); + + expect(canvas.remove).toHaveBeenCalled(); + expect(cropper._cropzone.set).toHaveBeenCalled(); + expect(canvas.add).toHaveBeenCalled(); + } + ); + + it('should not re-render cropzone ' + 'if the mouse moving is under the threshold', () => { + cropper._startX = 14; + cropper._startY = 18; + + cropper.start(); + spyOn(canvas, 'remove'); + spyOn(cropper._cropzone, 'set'); + spyOn(canvas, 'add'); + cropper._onFabricMouseMove({ e: {} }); + + expect(canvas.remove).not.toHaveBeenCalled(); + expect(cropper._cropzone.set).not.toHaveBeenCalled(); + expect(canvas.add).not.toHaveBeenCalled(); + }); + }); + + describe('_calcRectDimensionFromPoint()', () => { + beforeEach(() => { + cropper._startX = 10; + cropper._startY = 20; + snippet.extend(canvas, { + getWidth() { + return 100; + }, + getHeight() { + return 200; + }, + }); + }); + + it('should return cropzone-left&top (min: 0, max: startX,Y)', () => { + const x = 20, + y = -1, + expected = { + left: 10, + top: 0, + width: jasmine.any(Number), + height: jasmine.any(Number), + }, + actual = cropper._calcRectDimensionFromPoint(x, y); + + expect(actual).toEqual(expected); + }); + + it('should calculate and return cropzone-width&height', () => { + let x, y, expected, actual; + + x = 30; + y = 40; + expected = { + left: 10, + top: 20, + width: 20, + height: 20, + }; + actual = cropper._calcRectDimensionFromPoint(x, y); + expect(actual).toEqual(expected); + + x = 300; + y = 400; + expected = { + left: 10, + top: 20, + width: 90, + height: 180, + }; + actual = cropper._calcRectDimensionFromPoint(x, y); + expect(actual).toEqual(expected); + }); + + it('should create cropzone that has fixed ratio during shift key is pressed.', () => { + const x = 100; + const y = 200; + const expected = { + left: 10, + top: 20, + width: 180, + height: 180, + }; - expect(canvas.setActiveObject).toHaveBeenCalledWith(cropper._cropzone); + cropper._withShiftKey = true; + + const actual = cropper._calcRectDimensionFromPoint(x, y); + + expect(actual).toEqual(expected); + }); + + it('should create cropzone that inverted current mouse position during shift key is pressed.', () => { + const x = -10; + const y = -20; + const expected = { + left: -10, + top: 0, + width: 20, + height: 20, + }; + + cropper._withShiftKey = true; + + const actual = cropper._calcRectDimensionFromPoint(x, y); + + expect(actual).toEqual(expected); + }); + }); + + it('"onFabricMouseUp()" should activate cropzone', () => { + canvas.setActiveObject = jasmine.createSpy(); + cropper.start(); + cropper._onFabricMouseUp(); + + expect(canvas.setActiveObject).toHaveBeenCalledWith(cropper._cropzone); + }); + + describe('"crop()"', () => { + it('should return cropzone rect', () => { + cropper.start(); + spyOn(cropper._cropzone, 'isValid').and.returnValue(true); + + expect(cropper.getCropzoneRect()).toBeTruthy(); + cropper.end(); + }); + + it('should return cropzone data if the cropzone is valid', () => { + cropper.start(); + spyOn(cropper._cropzone, 'isValid').and.returnValue(true); + + expect(cropper.getCroppedImageData(cropper.getCropzoneRect())).toEqual({ + imageName: jasmine.any(String), + url: jasmine.any(String), + }); + cropper.end(); }); + }); - describe('"crop()"', () => { - it('should return cropzone rect', () => { - cropper.start(); - spyOn(cropper._cropzone, 'isValid').and.returnValue(true); - - expect(cropper.getCropzoneRect()).toBeTruthy(); - cropper.end(); - }); - - it('should return cropzone data if the cropzone is valid', () => { - cropper.start(); - spyOn(cropper._cropzone, 'isValid').and.returnValue(true); - - expect(cropper.getCroppedImageData(cropper.getCropzoneRect())).toEqual({ - imageName: jasmine.any(String), - url: jasmine.any(String) - }); - cropper.end(); - }); + describe('"presets - setCropzoneRect()"', () => { + beforeEach(() => { + cropper.start(); }); - describe('"presets - setCropzoneRect()"', () => { - beforeEach(() => { - cropper.start(); - }); + afterEach(() => { + cropper.end(); + }); - afterEach(() => { - cropper.end(); - }); + it('should return cropzone rect as a square', () => { + spyOn(cropper._cropzone, 'isValid').and.returnValue(true); + cropper.setCropzoneRect(1 / 1); - it('should return cropzone rect as a square', () => { - spyOn(cropper._cropzone, 'isValid').and.returnValue(true); - cropper.setCropzoneRect(1 / 1); + expect(cropper.getCropzoneRect().width).toBe(cropper.getCropzoneRect().height); + }); - expect(cropper.getCropzoneRect().width).toBe(cropper.getCropzoneRect().height); - }); + it('should return cropzone rect as a 3:2 aspect box', () => { + spyOn(cropper._cropzone, 'isValid').and.returnValue(true); + cropper.setCropzoneRect(3 / 2); - it('should return cropzone rect as a 3:2 aspect box', () => { - spyOn(cropper._cropzone, 'isValid').and.returnValue(true); - cropper.setCropzoneRect(3 / 2); + expect((cropper.getCropzoneRect().width / cropper.getCropzoneRect().height).toFixed(1)).toBe( + (3 / 2).toFixed(1) + ); + }); - expect((cropper.getCropzoneRect().width / cropper.getCropzoneRect().height).toFixed(1)) - .toBe((3 / 2).toFixed(1)); - }); + it('should return cropzone rect as a 4:3 aspect box', () => { + spyOn(cropper._cropzone, 'isValid').and.returnValue(true); + cropper.setCropzoneRect(4 / 3); - it('should return cropzone rect as a 4:3 aspect box', () => { - spyOn(cropper._cropzone, 'isValid').and.returnValue(true); - cropper.setCropzoneRect(4 / 3); + expect((cropper.getCropzoneRect().width / cropper.getCropzoneRect().height).toFixed(1)).toBe( + (4 / 3).toFixed(1) + ); + }); - expect((cropper.getCropzoneRect().width / cropper.getCropzoneRect().height).toFixed(1)) - .toBe((4 / 3).toFixed(1)); - }); + it('should return cropzone rect as a 5:4 aspect box', () => { + spyOn(cropper._cropzone, 'isValid').and.returnValue(true); + cropper.setCropzoneRect(5 / 4); - it('should return cropzone rect as a 5:4 aspect box', () => { - spyOn(cropper._cropzone, 'isValid').and.returnValue(true); - cropper.setCropzoneRect(5 / 4); + expect((cropper.getCropzoneRect().width / cropper.getCropzoneRect().height).toFixed(1)).toBe( + (5 / 4).toFixed(1) + ); + }); + + it('should return cropzone rect as a 7:5 aspect box', () => { + spyOn(cropper._cropzone, 'isValid').and.returnValue(true); + cropper.setCropzoneRect(7 / 5); - expect((cropper.getCropzoneRect().width / cropper.getCropzoneRect().height).toFixed(1)) - .toBe((5 / 4).toFixed(1)); - }); + expect((cropper.getCropzoneRect().width / cropper.getCropzoneRect().height).toFixed(1)).toBe( + (7 / 5).toFixed(1) + ); + }); - it('should return cropzone rect as a 7:5 aspect box', () => { - spyOn(cropper._cropzone, 'isValid').and.returnValue(true); - cropper.setCropzoneRect(7 / 5); + it('should return cropzone rect as a 16:9 aspect box', () => { + spyOn(cropper._cropzone, 'isValid').and.returnValue(true); + cropper.setCropzoneRect(16 / 9); - expect((cropper.getCropzoneRect().width / cropper.getCropzoneRect().height).toFixed(1)) - .toBe((7 / 5).toFixed(1)); - }); + expect((cropper.getCropzoneRect().width / cropper.getCropzoneRect().height).toFixed(1)).toBe( + (16 / 9).toFixed(1) + ); + }); - it('should return cropzone rect as a 16:9 aspect box', () => { - spyOn(cropper._cropzone, 'isValid').and.returnValue(true); - cropper.setCropzoneRect(16 / 9); + it('Even in situations with floating point problems, should calculate the exact width you expect.', () => { + spyOn(canvas, 'getWidth').and.returnValue(408); + spyOn(canvas, 'getHeight').and.returnValue(312); + spyOn(cropper._cropzone, 'set').and.callThrough(); - expect((cropper.getCropzoneRect().width / cropper.getCropzoneRect().height).toFixed(1)) - .toBe((16 / 9).toFixed(1)); - }); + cropper.setCropzoneRect(16 / 9); - it('Even in situations with floating point problems, should calculate the exact width you expect.', () => { - spyOn(canvas, 'getWidth').and.returnValue(408); - spyOn(canvas, 'getHeight').and.returnValue(312); - spyOn(cropper._cropzone, 'set').and.callThrough(); + expect(cropper._cropzone.set.calls.first().args[0].width).toBe(408); + }); - cropper.setCropzoneRect(16 / 9); + it('should remove cropzone of cropper when falsy is passed', () => { + cropper.setCropzoneRect(); + expect(cropper.getCropzoneRect()).toBeFalsy(); - expect(cropper._cropzone.set.calls.first().args[0].width).toBe(408); - }); + cropper.setCropzoneRect(0); + expect(cropper.getCropzoneRect()).toBeFalsy(); - it('should remove cropzone of cropper when falsy is passed', () => { - cropper.setCropzoneRect(); - expect(cropper.getCropzoneRect()).toBeFalsy(); + cropper.setCropzoneRect(null); + expect(cropper.getCropzoneRect()).toBeFalsy(); + }); + }); - cropper.setCropzoneRect(0); - expect(cropper.getCropzoneRect()).toBeFalsy(); + describe('"end()"', () => { + it('should set cropzone of cropper to null', () => { + cropper.start(); + cropper.end(); - cropper.setCropzoneRect(null); - expect(cropper.getCropzoneRect()).toBeFalsy(); - }); + expect(cropper._cropzone).toBe(null); }); - describe('"end()"', () => { - it('should set cropzone of cropper to null', () => { - cropper.start(); - cropper.end(); - - expect(cropper._cropzone).toBe(null); - }); - - it('should set "evented" of all obejcts to true', () => { - const objects = [ - new fabric.Rect({evented: false}), - new fabric.Rect({evented: false}), - new fabric.Rect({evented: false}) - ]; - canvas.add(objects[0], objects[1], objects[2]); - - cropper.start(); - cropper.end(); - expect(objects[0].evented).toBe(true); - expect(objects[1].evented).toBe(true); - expect(objects[2].evented).toBe(true); - }); + it('should set "evented" of all obejcts to true', () => { + const objects = [ + new fabric.Rect({ evented: false }), + new fabric.Rect({ evented: false }), + new fabric.Rect({ evented: false }), + ]; + canvas.add(objects[0], objects[1], objects[2]); + + cropper.start(); + cropper.end(); + expect(objects[0].evented).toBe(true); + expect(objects[1].evented).toBe(true); + expect(objects[2].evented).toBe(true); }); - describe('canvas event delegator', () => { - it('The event of an object with an eventDelegator must fire the graphics.fire registered with the trigger.', () => { - cropper.start(); - spyOn(graphics, 'fire'); - const events = eventNames; - const fEvent = { - target: cropper._cropzone - }; - - const cropzone = cropper._cropzone; - - canvas.fire('object:scaling', fEvent); - - expect(graphics.fire.calls.count()).toBe(0); - cropzone.canvasEventTrigger[events.OBJECT_SCALED](cropzone); - expect(graphics.fire.calls.count()).toBe(1); - }); + }); + describe('canvas event delegator', () => { + it('The event of an object with an eventDelegator must fire the graphics.fire registered with the trigger.', () => { + cropper.start(); + spyOn(graphics, 'fire'); + const events = eventNames; + const fEvent = { + target: cropper._cropzone, + }; + + const cropzone = cropper._cropzone; + + canvas.fire('object:scaling', fEvent); + + expect(graphics.fire.calls.count()).toBe(0); + cropzone.canvasEventTrigger[events.OBJECT_SCALED](cropzone); + expect(graphics.fire.calls.count()).toBe(1); }); + }); }); diff --git a/test/cropzone.spec.js b/test/cropzone.spec.js index 5a33e8600..bece4be1c 100644 --- a/test/cropzone.spec.js +++ b/test/cropzone.spec.js @@ -7,147 +7,174 @@ import fabric from 'fabric'; import Cropzone from '../src/js/extension/cropzone'; describe('Cropzone', () => { - const options = { - left: 10, - top: 10, - width: 100, - height: 100, - cornerSize: 10, - strokeWidth: 0, - cornerColor: 'black', - fill: 'transparent', - hasRotatingPoint: false, - hasBorders: false, - lockScalingFlip: true, - lockRotation: true - }; - const canvas = new fabric.Canvas(); - canvas.height = 400; - canvas.width = 300; - - it('"_getCoordinates()" should return outer&inner rect coordinates(array)', () => { - const cropzone = new Cropzone(canvas, options, {}); - const coords = cropzone._getCoordinates(); - - expect(coords).toEqual({ - x: [-60, -50, 50, 240], - y: [-60, -50, 50, 340] - }); - }); - - it('"_onMoving()" should set left and top between 0 and canvas size', () => { - const cropzone = new Cropzone(canvas, options, {}); - const mockFabricCanvas = { - getWidth() { - return 300; - }, - getHeight() { - return 400; - } - }; - - cropzone.canvas = mockFabricCanvas; - cropzone.left = -1; - cropzone.top = -1; - cropzone._onMoving(); - - expect(cropzone.top).toEqual(0); - expect(cropzone.left).toEqual(0); - - cropzone.left = 1000; - cropzone.top = 1000; - cropzone._onMoving(); - - expect(cropzone.left).toEqual(200); - expect(cropzone.top).toEqual(300); - }); - - it('"isValid()" should return whether the cropzone has real area or not', () => { - const cropzone = new Cropzone(canvas, options, {}); - cropzone.left = -1; - expect(cropzone.isValid()).toBe(false); - - cropzone.left = 1; - expect(cropzone.isValid()).toBe(true); - - cropzone.height = -1; - expect(cropzone.isValid()).toBe(false); - - cropzone.height = 1; - expect(cropzone.isValid()).toBe(true); + const options = { + left: 10, + top: 10, + width: 100, + height: 100, + cornerSize: 10, + strokeWidth: 0, + cornerColor: 'black', + fill: 'transparent', + hasRotatingPoint: false, + hasBorders: false, + lockScalingFlip: true, + lockRotation: true, + }; + const canvas = new fabric.Canvas(); + canvas.height = 400; + canvas.width = 300; + + it('"_getCoordinates()" should return outer&inner rect coordinates(array)', () => { + const cropzone = new Cropzone(canvas, options, {}); + const coords = cropzone._getCoordinates(); + + expect(coords).toEqual({ + x: [-60, -50, 50, 240], + y: [-60, -50, 50, 340], }); + }); + + it('"_onMoving()" should set left and top between 0 and canvas size', () => { + const cropzone = new Cropzone(canvas, options, {}); + const mockFabricCanvas = { + getWidth() { + return 300; + }, + getHeight() { + return 400; + }, + }; - it('"_resizeTL" should give the expected value at run', () => { - const cropzone = new Cropzone(canvas, options, {}); - - expect(cropzone._resizeCropZone({ - x: 30, - y: 40 - }, 'tl')).toEqual({ - left: 30, - top: 40, - width: 80, - height: 70 - }); + cropzone.canvas = mockFabricCanvas; + cropzone.left = -1; + cropzone.top = -1; + cropzone._onMoving(); + + expect(cropzone.top).toEqual(0); + expect(cropzone.left).toEqual(0); + + cropzone.left = 1000; + cropzone.top = 1000; + cropzone._onMoving(); + + expect(cropzone.left).toEqual(200); + expect(cropzone.top).toEqual(300); + }); + + it('"isValid()" should return whether the cropzone has real area or not', () => { + const cropzone = new Cropzone(canvas, options, {}); + cropzone.left = -1; + expect(cropzone.isValid()).toBe(false); + + cropzone.left = 1; + expect(cropzone.isValid()).toBe(true); + + cropzone.height = -1; + expect(cropzone.isValid()).toBe(false); + + cropzone.height = 1; + expect(cropzone.isValid()).toBe(true); + }); + + it('"_resizeTL" should give the expected value at run', () => { + const cropzone = new Cropzone(canvas, options, {}); + + expect( + cropzone._resizeCropZone( + { + x: 30, + y: 40, + }, + 'tl' + ) + ).toEqual({ + left: 30, + top: 40, + width: 80, + height: 70, }); - - it('"_resizeTR" should give the expected value at run', () => { - const cropzone = new Cropzone(canvas, options, {}); - - expect(cropzone._resizeCropZone({ - x: 80, - y: 50 - }, 'tr')).toEqual({ - left: 10, - top: 50, - width: 70, - height: 60 - }); + }); + + it('"_resizeTR" should give the expected value at run', () => { + const cropzone = new Cropzone(canvas, options, {}); + + expect( + cropzone._resizeCropZone( + { + x: 80, + y: 50, + }, + 'tr' + ) + ).toEqual({ + left: 10, + top: 50, + width: 70, + height: 60, }); - - it('"_resizeBL" should give the expected value at run', () => { - const cropzone = new Cropzone(canvas, options, {}); - - expect(cropzone._resizeCropZone({ - x: 30, - y: 40 - }, 'bl')).toEqual({ - left: 30, - top: 10, - width: 80, - height: 30 - }); + }); + + it('"_resizeBL" should give the expected value at run', () => { + const cropzone = new Cropzone(canvas, options, {}); + + expect( + cropzone._resizeCropZone( + { + x: 30, + y: 40, + }, + 'bl' + ) + ).toEqual({ + left: 30, + top: 10, + width: 80, + height: 30, }); - - it('"_resizeBR" should give the expected value at run', () => { - const cropzone = new Cropzone(canvas, options, {}); - - expect(cropzone._resizeCropZone({ - x: 30, - y: 40 - }, 'br')).toEqual({ - left: 10, - top: 10, - width: 20, - height: 30 - }); + }); + + it('"_resizeBR" should give the expected value at run', () => { + const cropzone = new Cropzone(canvas, options, {}); + + expect( + cropzone._resizeCropZone( + { + x: 30, + y: 40, + }, + 'br' + ) + ).toEqual({ + left: 10, + top: 10, + width: 20, + height: 30, }); - - it('should yield the result of maintaining the ratio at running the resize function at a fixed rate', () => { - const presetRatio = 5 / 4; - const cropzone = new Cropzone(canvas, snippet.extend({}, options, { - width: 50, - height: 40, - presetRatio - }), {}); - - snippet.forEach(['tl', 'tr', 'mt', 'ml', 'mr', 'mb', 'bl', 'br'], cornerType => { - const {width, height} = cropzone._resizeCropZone({ - x: 20, - y: 20 - }, cornerType); - - expect(width / height).toEqual(presetRatio); - }); + }); + + it('should yield the result of maintaining the ratio at running the resize function at a fixed rate', () => { + const presetRatio = 5 / 4; + const cropzone = new Cropzone( + canvas, + snippet.extend({}, options, { + width: 50, + height: 40, + presetRatio, + }), + {} + ); + + snippet.forEach(['tl', 'tr', 'mt', 'ml', 'mr', 'mb', 'bl', 'br'], (cornerType) => { + const { width, height } = cropzone._resizeCropZone( + { + x: 20, + y: 20, + }, + cornerType + ); + + expect(width / height).toEqual(presetRatio); }); + }); }); diff --git a/test/drawingMode.spec.js b/test/drawingMode.spec.js index 1ad8f9430..407edad6d 100644 --- a/test/drawingMode.spec.js +++ b/test/drawingMode.spec.js @@ -5,48 +5,48 @@ import ImageEditor from '../src/js/imageEditor'; describe('DrawingMode', () => { - let imageEditor; - const imageURL = 'base/test/fixtures/sampleImage.jpg'; - - beforeEach(done => { - imageEditor = new ImageEditor(document.createElement('div'), { - cssMaxWidth: 700, - cssMaxHeight: 500 - }); - imageEditor.loadImageFromURL(imageURL, 'sampleImage').then(() => { - done(); - }); - }); + let imageEditor; + const imageURL = 'base/test/fixtures/sampleImage.jpg'; - afterEach(() => { - imageEditor.destroy(); + beforeEach((done) => { + imageEditor = new ImageEditor(document.createElement('div'), { + cssMaxWidth: 700, + cssMaxHeight: 500, + }); + imageEditor.loadImageFromURL(imageURL, 'sampleImage').then(() => { + done(); }); + }); - it('enter a drawing mode with startDrawingMode, CROPPER', () => { - imageEditor.startDrawingMode('CROPPER'); + afterEach(() => { + imageEditor.destroy(); + }); - expect(imageEditor.getDrawingMode()).toBe('CROPPER'); - }); + it('enter a drawing mode with startDrawingMode, CROPPER', () => { + imageEditor.startDrawingMode('CROPPER'); - it('stop a drawing mode with stopDrawingMode, ie, to normal', () => { - imageEditor.stopDrawingMode(); + expect(imageEditor.getDrawingMode()).toBe('CROPPER'); + }); - expect(imageEditor.getDrawingMode()).toBe('NORMAL'); - }); + it('stop a drawing mode with stopDrawingMode, ie, to normal', () => { + imageEditor.stopDrawingMode(); - it('enter all drawing mode with startDrawingMode in consecutive order', () => { - const drawingModes = ['CROPPER', 'FREE_DRAWING', 'LINE_DRAWING', 'TEXT', 'SHAPE']; - const {length} = drawingModes; - let i; + expect(imageEditor.getDrawingMode()).toBe('NORMAL'); + }); - for (i = 0; i < length; i += 1) { - imageEditor.startDrawingMode(drawingModes[i]); + it('enter all drawing mode with startDrawingMode in consecutive order', () => { + const drawingModes = ['CROPPER', 'FREE_DRAWING', 'LINE_DRAWING', 'TEXT', 'SHAPE']; + const { length } = drawingModes; + let i; - expect(imageEditor.getDrawingMode()).toBe(drawingModes[i]); - } + for (i = 0; i < length; i += 1) { + imageEditor.startDrawingMode(drawingModes[i]); - expect(imageEditor.startDrawingMode('CROPPER')).toBe(true); - expect(imageEditor.startDrawingMode('CROPPER')).toBe(true); // call again, should return true - expect(imageEditor.startDrawingMode('NOT_A_DRAWING_MODE')).toBe(false); - }); + expect(imageEditor.getDrawingMode()).toBe(drawingModes[i]); + } + + expect(imageEditor.startDrawingMode('CROPPER')).toBe(true); + expect(imageEditor.startDrawingMode('CROPPER')).toBe(true); // call again, should return true + expect(imageEditor.startDrawingMode('NOT_A_DRAWING_MODE')).toBe(false); + }); }); diff --git a/test/filter.spec.js b/test/filter.spec.js index cf4b741e5..c69612e6e 100644 --- a/test/filter.spec.js +++ b/test/filter.spec.js @@ -5,62 +5,71 @@ import ImageEditor from '../src/js/imageEditor'; describe('Filter', () => { - let imageEditor; - const imageURL = 'base/test/fixtures/sampleImage.jpg'; + let imageEditor; + const imageURL = 'base/test/fixtures/sampleImage.jpg'; - beforeEach(done => { - imageEditor = new ImageEditor(document.createElement('div'), { - cssMaxWidth: 700, - cssMaxHeight: 500 - }); - imageEditor.loadImageFromURL(imageURL, 'sampleImage').then(() => { - imageEditor.clearUndoStack(); - done(); - }); + beforeEach((done) => { + imageEditor = new ImageEditor(document.createElement('div'), { + cssMaxWidth: 700, + cssMaxHeight: 500, }); - - afterEach(() => { - imageEditor.destroy(); + imageEditor.loadImageFromURL(imageURL, 'sampleImage').then(() => { + imageEditor.clearUndoStack(); + done(); }); + }); - it('applyFilter() can add undo stack', done => { - imageEditor.applyFilter('colorFilter').then(() => { - expect(imageEditor.isEmptyUndoStack()).toBe(false); - done(); - })['catch'](() => { - fail(); - done(); - }); - }); + afterEach(() => { + imageEditor.destroy(); + }); - it('applyFilter() can not add undo stack at isSilent', done => { - const isSilent = true; + it('applyFilter() can add undo stack', (done) => { + imageEditor + .applyFilter('colorFilter') + .then(() => { + expect(imageEditor.isEmptyUndoStack()).toBe(false); + done(); + }) + ['catch'](() => { + fail(); + done(); + }); + }); - imageEditor.applyFilter('colorFilter', {}, isSilent).then(() => { - expect(imageEditor.isEmptyUndoStack()).toBe(true); - done(); - })['catch'](() => { - fail(); - done(); - }); - }); + it('applyFilter() can not add undo stack at isSilent', (done) => { + const isSilent = true; - it('hasFilter', () => { - imageEditor.applyFilter('colorFilter'); + imageEditor + .applyFilter('colorFilter', {}, isSilent) + .then(() => { + expect(imageEditor.isEmptyUndoStack()).toBe(true); + done(); + }) + ['catch'](() => { + fail(); + done(); + }); + }); - expect(imageEditor.hasFilter('invert')).toBe(false); - expect(imageEditor.hasFilter('colorFilter')).toBe(true); - }); + it('hasFilter', () => { + imageEditor.applyFilter('colorFilter'); - it('removeFilter() can remove added filter', done => { - imageEditor.applyFilter('colorFilter').then(() => imageEditor.removeFilter('colorFilter') - ).then(() => { - expect(imageEditor.hasFilter('colorFilter')).toBe(false); - expect(imageEditor.isEmptyUndoStack()).toBe(false); - done(); - })['catch'](() => { - fail(); - done(); - }); - }); + expect(imageEditor.hasFilter('invert')).toBe(false); + expect(imageEditor.hasFilter('colorFilter')).toBe(true); + }); + + it('removeFilter() can remove added filter', (done) => { + imageEditor + .applyFilter('colorFilter') + .then(() => imageEditor.removeFilter('colorFilter')) + .then(() => { + expect(imageEditor.hasFilter('colorFilter')).toBe(false); + expect(imageEditor.isEmptyUndoStack()).toBe(false); + done(); + }) + ['catch'](() => { + fail(); + done(); + }); + }); }); diff --git a/test/flip.spec.js b/test/flip.spec.js index f7336ed2f..7482ca73d 100644 --- a/test/flip.spec.js +++ b/test/flip.spec.js @@ -8,141 +8,143 @@ import Graphics from '../src/js/graphics'; import Flip from '../src/js/component/flip'; describe('Flip', () => { - let graphics, flipModule, mockImage; + let graphics, flipModule, mockImage; - beforeAll(() => { - graphics = new Graphics($('')[0]); - flipModule = new Flip(graphics); - }); + beforeAll(() => { + graphics = new Graphics($('')[0]); + flipModule = new Flip(graphics); + }); - beforeEach(() => { - mockImage = new fabric.Image(); - graphics.setCanvasImage('mockImage', mockImage); - }); + beforeEach(() => { + mockImage = new fabric.Image(); + graphics.setCanvasImage('mockImage', mockImage); + }); - it('"getCurrentSetting()" should return current flip-setting', () => { - let setting = flipModule.getCurrentSetting(); + it('"getCurrentSetting()" should return current flip-setting', () => { + let setting = flipModule.getCurrentSetting(); - expect(setting).toEqual({ - flipX: false, - flipY: false - }); + expect(setting).toEqual({ + flipX: false, + flipY: false, + }); - mockImage.set({flipX: true}); - setting = flipModule.getCurrentSetting(); - expect(setting).toEqual({ - flipX: true, - flipY: false - }); + mockImage.set({ flipX: true }); + setting = flipModule.getCurrentSetting(); + expect(setting).toEqual({ + flipX: true, + flipY: false, }); + }); - it('"set()" should set flip-setting', () => { - flipModule.set({ - flipX: false, - flipY: true - }); + it('"set()" should set flip-setting', () => { + flipModule.set({ + flipX: false, + flipY: true, + }); - expect(flipModule.getCurrentSetting()).toEqual({ - flipX: false, - flipY: true - }); + expect(flipModule.getCurrentSetting()).toEqual({ + flipX: false, + flipY: true, }); + }); - it('"reset()" should reset flip-setting to false', () => { - mockImage.set({ - flipX: true, - flipY: true - }); - flipModule.reset(); + it('"reset()" should reset flip-setting to false', () => { + mockImage.set({ + flipX: true, + flipY: true, + }); + flipModule.reset(); - expect(flipModule.getCurrentSetting()).toEqual({ - flipX: false, - flipY: false - }); + expect(flipModule.getCurrentSetting()).toEqual({ + flipX: false, + flipY: false, }); + }); - it('"flipX()" should toggle flipX', () => { - flipModule.flipX(); + it('"flipX()" should toggle flipX', () => { + flipModule.flipX(); - expect(flipModule.getCurrentSetting()).toEqual({ - flipX: true, - flipY: false - }); + expect(flipModule.getCurrentSetting()).toEqual({ + flipX: true, + flipY: false, + }); - flipModule.flipX(); + flipModule.flipX(); - expect(flipModule.getCurrentSetting()).toEqual({ - flipX: false, - flipY: false - }); + expect(flipModule.getCurrentSetting()).toEqual({ + flipX: false, + flipY: false, }); + }); - it('"flipY()" should toggle flipY', () => { - flipModule.flipY(); + it('"flipY()" should toggle flipY', () => { + flipModule.flipY(); - expect(flipModule.getCurrentSetting()).toEqual({ - flipX: false, - flipY: true - }); + expect(flipModule.getCurrentSetting()).toEqual({ + flipX: false, + flipY: true, + }); - flipModule.flipY(); + flipModule.flipY(); - expect(flipModule.getCurrentSetting()).toEqual({ - flipX: false, - flipY: false - }); + expect(flipModule.getCurrentSetting()).toEqual({ + flipX: false, + flipY: false, }); + }); - describe('Promise is returned with settings and angle,', () => { - beforeEach(() => { - mockImage.angle = 10; - }); + describe('Promise is returned with settings and angle,', () => { + beforeEach(() => { + mockImage.angle = 10; + }); - it('flipX() is called.', done => { - flipModule.flipX().then(obj => { - expect(obj).toEqual({ - flipX: true, - flipY: false, - angle: -10 - }); - done(); - }); + it('flipX() is called.', (done) => { + flipModule.flipX().then((obj) => { + expect(obj).toEqual({ + flipX: true, + flipY: false, + angle: -10, }); + done(); + }); + }); - it('flipY() is called.', done => { - flipModule.flipY().then(obj => { - expect(obj).toEqual({ - flipX: false, - flipY: true, - angle: -10 - }); - done(); - }); + it('flipY() is called.', (done) => { + flipModule.flipY().then((obj) => { + expect(obj).toEqual({ + flipX: false, + flipY: true, + angle: -10, }); + done(); + }); + }); - it('flipY() is called.', done => { - flipModule.flipY().then(obj => { - expect(obj).toEqual({ - flipX: false, - flipY: true, - angle: -10 - }); - done(); - }); + it('flipY() is called.', (done) => { + flipModule.flipY().then((obj) => { + expect(obj).toEqual({ + flipX: false, + flipY: true, + angle: -10, }); + done(); + }); + }); - it('set() is called.', done => { - flipModule.set({ - flipX: true, - flipY: false - }).then(obj => { - expect(obj).toEqual({ - flipX: true, - flipY: false, - angle: -10 - }); - done(); - }); + it('set() is called.', (done) => { + flipModule + .set({ + flipX: true, + flipY: false, + }) + .then((obj) => { + expect(obj).toEqual({ + flipX: true, + flipY: false, + angle: -10, + }); + done(); }); }); + }); }); diff --git a/test/graphics.spec.js b/test/graphics.spec.js index a19050204..1e500b608 100644 --- a/test/graphics.spec.js +++ b/test/graphics.spec.js @@ -5,228 +5,230 @@ import snippet from 'tui-code-snippet'; import fabric from 'fabric'; import Graphics from '../src/js/graphics'; -import {drawingModes, componentNames as components} from '../src/js/consts'; +import { drawingModes, componentNames as components } from '../src/js/consts'; describe('Graphics', () => { - const cssMaxWidth = 900; - const cssMaxHeight = 700; - let graphics, canvas; - - beforeEach(() => { - graphics = new Graphics(document.createElement('canvas'), { - cssMaxWidth, - cssMaxHeight - }); - canvas = graphics.getCanvas(); + const cssMaxWidth = 900; + const cssMaxHeight = 700; + let graphics, canvas; + + beforeEach(() => { + graphics = new Graphics(document.createElement('canvas'), { + cssMaxWidth, + cssMaxHeight, }); - - it('has several properties', () => { - expect(canvas).not.toBe(null); - expect(canvas).toEqual(jasmine.any(fabric.Canvas)); - expect(graphics.cssMaxWidth).toBe(900); - expect(graphics.cssMaxHeight).toBe(700); - expect(graphics.canvasImage).toBe(null); - expect(graphics.imageName).toBe(''); - expect(graphics._drawingMode).toBe(drawingModes.NORMAL); - expect(graphics._componentMap).not.toBe(null); + canvas = graphics.getCanvas(); + }); + + it('has several properties', () => { + expect(canvas).not.toBe(null); + expect(canvas).toEqual(jasmine.any(fabric.Canvas)); + expect(graphics.cssMaxWidth).toBe(900); + expect(graphics.cssMaxHeight).toBe(700); + expect(graphics.canvasImage).toBe(null); + expect(graphics.imageName).toBe(''); + expect(graphics._drawingMode).toBe(drawingModes.NORMAL); + expect(graphics._componentMap).not.toBe(null); + }); + + it('After the path has been drawn, "origin" should change to "left top-> center center" and "position" should change to the center coordinates of path.', () => { + const pathObj = new fabric.Path('M 0 0 L 100 0 L 100 100 L 0 100 z'); + const expectPosition = pathObj.getCenterPoint(); + const expectX = expectPosition.x; + const expectY = expectPosition.y; + + graphics._onPathCreated({ path: pathObj }); + + expect(pathObj.originX).toBe('center'); + expect(pathObj.originY).toBe('center'); + expect(pathObj.left).toBe(expectX); + expect(pathObj.top).toBe(expectY); + }); + + it('can attach canvas events', () => { + const onMousedown = jasmine.createSpy('onMousedown'); + const onObjectAdded = jasmine.createSpy('onObjectAdded'); + const onObjectSelected = jasmine.createSpy('onObjectSelected'); + + graphics.on({ + mousedown: onMousedown, + 'object:added': onObjectAdded, }); - - it('After the path has been drawn, "origin" should change to "left top-> center center" and "position" should change to the center coordinates of path.', () => { - const pathObj = new fabric.Path('M 0 0 L 100 0 L 100 100 L 0 100 z'); - const expectPosition = pathObj.getCenterPoint(); - const expectX = expectPosition.x; - const expectY = expectPosition.y; - - graphics._onPathCreated({path: pathObj}); - - expect(pathObj.originX).toBe('center'); - expect(pathObj.originY).toBe('center'); - expect(pathObj.left).toBe(expectX); - expect(pathObj.top).toBe(expectY); + graphics.once('object:selected', onObjectSelected); + + graphics.fire('mousedown'); + graphics.fire('mousedown'); + graphics.fire('object:added'); + graphics.fire('object:added'); + graphics.fire('object:selected'); + graphics.fire('object:selected'); + + expect(onMousedown.calls.count()).toBe(2); + expect(onObjectAdded.calls.count()).toBe(2); + expect(onObjectSelected.calls.count()).toBe(1); + }); + + it('deactivates all objects', () => { + const triangle = new fabric.Triangle({ + width: 20, + height: 30, }); - it('can attach canvas events', () => { - const onMousedown = jasmine.createSpy('onMousedown'); - const onObjectAdded = jasmine.createSpy('onObjectAdded'); - const onObjectSelected = jasmine.createSpy('onObjectSelected'); - - graphics.on({ - 'mousedown': onMousedown, - 'object:added': onObjectAdded - }); - graphics.once('object:selected', onObjectSelected); - - graphics.fire('mousedown'); - graphics.fire('mousedown'); - graphics.fire('object:added'); - graphics.fire('object:added'); - graphics.fire('object:selected'); - graphics.fire('object:selected'); - - expect(onMousedown.calls.count()).toBe(2); - expect(onObjectAdded.calls.count()).toBe(2); - expect(onObjectSelected.calls.count()).toBe(1); + canvas.add(triangle).setActiveObject(triangle); + expect(canvas.getActiveObject()).not.toBe(null); + graphics.deactivateAll(); + expect(canvas.getActiveObject()).toBe(null); + }); + + it('renders objects', (done) => { + let beforeRender = false; + const triangle = new fabric.Triangle({ + width: 20, + height: 30, }); - it('deactivates all objects', () => { - const triangle = new fabric.Triangle({ - width: 20, - height: 30 - }); + canvas.add(triangle); + canvas.on('before:render', () => { + beforeRender = true; + }); + canvas.on('after:render', () => { + expect(beforeRender).toBe(true); + done(); + }); + graphics.renderAll(); + }); - canvas.add(triangle).setActiveObject(triangle); - expect(canvas.getActiveObject()).not.toBe(null); - graphics.deactivateAll(); - expect(canvas.getActiveObject()).toBe(null); + it('removes a object or group by id', () => { + const triangle = new fabric.Triangle({ + width: 20, + height: 30, }); - it('renders objects', done => { - let beforeRender = false; - const triangle = new fabric.Triangle({ - width: 20, - height: 30 - }); - - canvas.add(triangle); - canvas.on('before:render', () => { - beforeRender = true; - }); - canvas.on('after:render', () => { - expect(beforeRender).toBe(true); - done(); - }); - graphics.renderAll(); + graphics.add(triangle); + const objectId = snippet.stamp(triangle); + graphics.removeObjectById(objectId); + expect(graphics.getObjects().length).toBe(0); + }); + + it('switches drawing modes', () => { + let modeName; + for (modeName in drawingModes) { + if (drawingModes.hasOwnProperty(modeName)) { + graphics.startDrawingMode(modeName); + expect(graphics.getDrawingMode()).toBe(modeName); + graphics.stopDrawingMode(); + expect(graphics.getDrawingMode()).toBe(drawingModes.NORMAL); + } + } + }); + + it('can get the cropped image data', () => { + graphics.startDrawingMode(drawingModes.CROPPER); + spyOn(graphics.getComponent(components.CROPPER)._cropzone, 'isValid').and.returnValue(true); + + expect(graphics.getCropzoneRect()).toBeTruthy(); + expect(graphics.getCroppedImageData(graphics.getCropzoneRect())).toEqual({ + imageName: jasmine.any(String), + url: jasmine.any(String), }); - it('removes a object or group by id', () => { - const triangle = new fabric.Triangle({ - width: 20, - height: 30 - }); + graphics.stopDrawingMode(); + }); - graphics.add(triangle); - const objectId = snippet.stamp(triangle); - graphics.removeObjectById(objectId); - expect(graphics.getObjects().length).toBe(0); - }); + it('Cropzone must be hidden initially and then redisplayed after completion at toDataURL is executed with a cropzone present', () => { + const cropper = graphics.getComponent(components.CROPPER); + spyOn(cropper, 'changeVisibility'); - it('switches drawing modes', () => { - let modeName; - for (modeName in drawingModes) { - if (drawingModes.hasOwnProperty(modeName)) { - graphics.startDrawingMode(modeName); - expect(graphics.getDrawingMode()).toBe(modeName); - graphics.stopDrawingMode(); - expect(graphics.getDrawingMode()).toBe(drawingModes.NORMAL); - } - } - }); + graphics.startDrawingMode(drawingModes.CROPPER); + graphics.toDataURL(); - it('can get the cropped image data', () => { - graphics.startDrawingMode(drawingModes.CROPPER); - spyOn(graphics.getComponent(components.CROPPER)._cropzone, 'isValid').and.returnValue(true); + expect(cropper.changeVisibility.calls.allArgs()).toEqual([[false], [true]]); + }); - expect(graphics.getCropzoneRect()).toBeTruthy(); - expect(graphics.getCroppedImageData(graphics.getCropzoneRect())).toEqual({ - imageName: jasmine.any(String), - url: jasmine.any(String) - }); + it('can set brush setting into LINE_DRAWING, FREE_DRAWING', () => { + graphics.startDrawingMode(drawingModes.LINE_DRAWING); + graphics.setBrush({ + width: 12, + color: 'FFFF00', + }); + const brush = canvas.freeDrawingBrush; + expect(brush.width).toBe(12); + expect(brush.color).toBe('rgba(255,255,0,1)'); + graphics.stopDrawingMode(); + }); + + it('can change a drawing shape', () => { + const shapeComp = graphics.getComponent(components.SHAPE); + graphics.setDrawingShape('circle', { + fill: 'transparent', + stroke: 'blue', + strokeWidth: 3, + rx: 10, + ry: 100, + }); + expect(shapeComp._type).toBe('circle'); + expect(shapeComp._options).toEqual({ + strokeWidth: 3, + stroke: 'blue', + fill: 'transparent', + width: 1, + height: 1, + rx: 10, + ry: 100, + lockSkewingX: true, + lockSkewingY: true, + bringForward: true, + isRegular: false, + }); + }); - graphics.stopDrawingMode(); + it('can register custom icon', () => { + const iconComp = graphics.getComponent(components.ICON); + graphics.registerPaths({ + customIcon: 'M 0 0 L 20 20 L 10 10 Z', }); - it('Cropzone must be hidden initially and then redisplayed after completion at toDataURL is executed with a cropzone present', () => { - const cropper = graphics.getComponent(components.CROPPER); - spyOn(cropper, 'changeVisibility'); + expect(iconComp._pathMap).toEqual( + jasmine.objectContaining({ + customIcon: 'M 0 0 L 20 20 L 10 10 Z', + }) + ); + }); - graphics.startDrawingMode(drawingModes.CROPPER); - graphics.toDataURL(); + it('has the filter', () => { + expect(graphics.hasFilter('Grayscale')).toBe(false); + }); - expect(cropper.changeVisibility.calls.allArgs()).toEqual([[false], [true]]); - }); + describe('pasteObject()', () => { + let targetObject1, targetObject2; - it('can set brush setting into LINE_DRAWING, FREE_DRAWING', () => { - graphics.startDrawingMode(drawingModes.LINE_DRAWING); - graphics.setBrush({ - width: 12, - color: 'FFFF00' - }); - const brush = canvas.freeDrawingBrush; - expect(brush.width).toBe(12); - expect(brush.color).toBe('rgba(255,255,0,1)'); - graphics.stopDrawingMode(); - }); + beforeEach(() => { + targetObject1 = new fabric.Object({}); + targetObject2 = new fabric.Object({}); - it('can change a drawing shape', () => { - const shapeComp = graphics.getComponent(components.SHAPE); - graphics.setDrawingShape('circle', { - fill: 'transparent', - stroke: 'blue', - strokeWidth: 3, - rx: 10, - ry: 100 - }); - expect(shapeComp._type).toBe('circle'); - expect(shapeComp._options).toEqual({ - strokeWidth: 3, - stroke: 'blue', - fill: 'transparent', - width: 1, - height: 1, - rx: 10, - ry: 100, - lockSkewingX: true, - lockSkewingY: true, - bringForward: true, - isRegular: false - }); + canvas.add(targetObject1); + canvas.add(targetObject2); }); - it('can register custom icon', () => { - const iconComp = graphics.getComponent(components.ICON); - graphics.registerPaths({ - customIcon: 'M 0 0 L 20 20 L 10 10 Z' - }); + it('Group objects must be duplicated as many as the number of objects in the group.', (done) => { + const groupObject = graphics.getActiveSelectionFromObjects(canvas.getObjects()); + graphics.setActiveObject(groupObject); + graphics.resetTargetObjectForCopyPaste(); - expect(iconComp._pathMap).toEqual(jasmine.objectContaining({ - customIcon: 'M 0 0 L 20 20 L 10 10 Z' - })); + graphics.pasteObject().then(() => { + expect(canvas.getObjects().length).toBe(4); + done(); + }); }); - it('has the filter', () => { - expect(graphics.hasFilter('Grayscale')).toBe(false); - }); + it('Only one object should be duplicated.', (done) => { + graphics.setActiveObject(targetObject1); + graphics.resetTargetObjectForCopyPaste(); - describe('pasteObject()', () => { - let targetObject1, targetObject2; - - beforeEach(() => { - targetObject1 = new fabric.Object({}); - targetObject2 = new fabric.Object({}); - - canvas.add(targetObject1); - canvas.add(targetObject2); - }); - - it('Group objects must be duplicated as many as the number of objects in the group.', done => { - const groupObject = graphics.getActiveSelectionFromObjects(canvas.getObjects()); - graphics.setActiveObject(groupObject); - graphics.resetTargetObjectForCopyPaste(); - - graphics.pasteObject().then(() => { - expect(canvas.getObjects().length).toBe(4); - done(); - }); - }); - - it('Only one object should be duplicated.', done => { - graphics.setActiveObject(targetObject1); - graphics.resetTargetObjectForCopyPaste(); - - graphics.pasteObject().then(() => { - expect(canvas.getObjects().length).toBe(3); - done(); - }); - }); + graphics.pasteObject().then(() => { + expect(canvas.getObjects().length).toBe(3); + done(); + }); }); + }); }); diff --git a/test/icon.spec.js b/test/icon.spec.js index 1c7189431..d91d98c94 100644 --- a/test/icon.spec.js +++ b/test/icon.spec.js @@ -8,125 +8,127 @@ import Graphics from '../src/js/graphics'; import Icon from '../src/js/component/icon'; describe('Icon', () => { - let canvas, graphics, mockImage, icon; - - beforeAll(() => { - graphics = new Graphics($('')[0]); - canvas = graphics.getCanvas(); - icon = new Icon(graphics); - }); - - beforeEach(() => { - mockImage = new fabric.Image(); - graphics.setCanvasImage('mockImage', mockImage); + let canvas, graphics, mockImage, icon; + + beforeAll(() => { + graphics = new Graphics($('')[0]); + canvas = graphics.getCanvas(); + icon = new Icon(graphics); + }); + + beforeEach(() => { + mockImage = new fabric.Image(); + graphics.setCanvasImage('mockImage', mockImage); + }); + + afterEach(() => { + canvas.forEachObject((obj) => { + canvas.remove(obj); }); - - afterEach(() => { - canvas.forEachObject(obj => { - canvas.remove(obj); + }); + + describe('_onFabricMouseMove()', () => { + let iconObj, fEvent; + + beforeEach((done) => { + fEvent = { e: {} }; + icon._startPoint = { + x: 300, + y: 300, + }; + icon + .add('arrow', { + left: icon._startPoint.x, + top: icon._startPoint.y, + color: '#000', + }) + .then(() => { + [iconObj] = canvas.getObjects(); + iconObj.set({ + width: 10, + height: 10, + }); + done(); }); }); - describe('_onFabricMouseMove()', () => { - let iconObj, fEvent; - - beforeEach(done => { - fEvent = {e: {}}; - icon._startPoint = { - x: 300, - y: 300 - }; - icon.add('arrow', { - left: icon._startPoint.x, - top: icon._startPoint.y, - color: '#000' - }).then(() => { - [iconObj] = canvas.getObjects(); - iconObj.set({ - width: 10, - height: 10 - }); - done(); - }); - }); - - it('When dragging to the right-down from the starting point, the icon scale value should increase.', () => { - spyOn(canvas, 'getPointer').and.returnValue({ - x: 500, - y: 500 - }); + it('When dragging to the right-down from the starting point, the icon scale value should increase.', () => { + spyOn(canvas, 'getPointer').and.returnValue({ + x: 500, + y: 500, + }); - icon._onFabricMouseMove(fEvent); + icon._onFabricMouseMove(fEvent); - expect(iconObj.scaleX).toBe(40); - expect(iconObj.scaleY).toBe(40); - }); + expect(iconObj.scaleX).toBe(40); + expect(iconObj.scaleY).toBe(40); + }); - it('When dragging to the left-up from the starting point, the icon scale value should increase.', () => { - spyOn(canvas, 'getPointer').and.returnValue({ - x: 100, - y: 100 - }); + it('When dragging to the left-up from the starting point, the icon scale value should increase.', () => { + spyOn(canvas, 'getPointer').and.returnValue({ + x: 100, + y: 100, + }); - icon._onFabricMouseMove(fEvent); + icon._onFabricMouseMove(fEvent); - expect(iconObj.scaleX).toBe(40); - expect(iconObj.scaleY).toBe(40); - }); + expect(iconObj.scaleX).toBe(40); + expect(iconObj.scaleY).toBe(40); }); + }); - it('add() should insert the activated icon object on canvas.', () => { - icon.add('arrow'); + it('add() should insert the activated icon object on canvas.', () => { + icon.add('arrow'); - const activeObj = canvas.getActiveObject(); + const activeObj = canvas.getActiveObject(); - expect(activeObj).not.toEqual(null); - }); + expect(activeObj).not.toEqual(null); + }); - it('add() should insert the icon object on center of canvas image.', () => { - const centerPos = icon.getCanvasImage().getCenterPoint(); + it('add() should insert the icon object on center of canvas image.', () => { + const centerPos = icon.getCanvasImage().getCenterPoint(); - icon.add('arrow'); + icon.add('arrow'); - const activeObj = canvas.getActiveObject(); - const halfStrokeWidth = activeObj.strokeWidth / 2; + const activeObj = canvas.getActiveObject(); + const halfStrokeWidth = activeObj.strokeWidth / 2; - expect(activeObj.left + halfStrokeWidth).toEqual(centerPos.x); - expect(activeObj.top + halfStrokeWidth).toEqual(centerPos.y); - }); + expect(activeObj.left + halfStrokeWidth).toEqual(centerPos.x); + expect(activeObj.top + halfStrokeWidth).toEqual(centerPos.y); + }); - it('add() should create the arrow icon when parameter value is "arrow".', () => { - const path = icon._pathMap.arrow; + it('add() should create the arrow icon when parameter value is "arrow".', () => { + const path = icon._pathMap.arrow; - spyOn(icon, '_createIcon').and.returnValue(new fabric.Object({})); + spyOn(icon, '_createIcon').and.returnValue(new fabric.Object({})); - icon.add('arrow'); + icon.add('arrow'); - expect(icon._createIcon).toHaveBeenCalledWith(path); - }); + expect(icon._createIcon).toHaveBeenCalledWith(path); + }); - it('add() should create the cancel icon when parameter value is "cancel".', () => { - const path = icon._pathMap.cancel; + it('add() should create the cancel icon when parameter value is "cancel".', () => { + const path = icon._pathMap.cancel; - spyOn(icon, '_createIcon').and.returnValue(new fabric.Object({})); + spyOn(icon, '_createIcon').and.returnValue(new fabric.Object({})); - icon.add('cancel'); + icon.add('cancel'); - expect(icon._createIcon).toHaveBeenCalledWith(path); - }); + expect(icon._createIcon).toHaveBeenCalledWith(path); + }); - it('setColor() should change color of next inserted icon.', () => { - let activeObj; - const color = '#ffffff'; + it('setColor() should change color of next inserted icon.', () => { + let activeObj; + const color = '#ffffff'; - icon.add('arrow'); - activeObj = canvas.getActiveObject(); - expect(activeObj.fill).not.toEqual(color); + icon.add('arrow'); + activeObj = canvas.getActiveObject(); + expect(activeObj.fill).not.toEqual(color); - icon.setColor(color); + icon.setColor(color); - icon.add('cancel'); - activeObj = canvas.getActiveObject(); - expect(activeObj.fill).toEqual(color); - }); + icon.add('cancel'); + activeObj = canvas.getActiveObject(); + expect(activeObj.fill).toEqual(color); + }); }); diff --git a/test/imageEditor.spec.js b/test/imageEditor.spec.js index a608abb6f..28cb48c0d 100644 --- a/test/imageEditor.spec.js +++ b/test/imageEditor.spec.js @@ -6,67 +6,67 @@ import snippet from 'tui-code-snippet'; import ImageEditor from '../src/js/imageEditor'; import fabric from 'fabric'; -import {eventNames, keyCodes} from '../src/js/consts'; +import { eventNames, keyCodes } from '../src/js/consts'; -const {OBJECT_ROTATED} = eventNames; +const { OBJECT_ROTATED } = eventNames; describe('ImageEditor', () => { - // hostnameSent module scope variable can not be reset. - // maintain cases with xit as it always fail, if you want to test these cases, change xit to fit one by one - describe('constructor', () => { - let imageEditor, el; - - beforeEach(() => { - el = document.createElement('div'); - spyOn(snippet, 'sendHostname'); - - imageEditor = new ImageEditor(el, { - usageStatistics: false - }); - }); + // hostnameSent module scope variable can not be reset. + // maintain cases with xit as it always fail, if you want to test these cases, change xit to fit one by one + describe('constructor', () => { + let imageEditor, el; + + beforeEach(() => { + el = document.createElement('div'); + spyOn(snippet, 'sendHostname'); + + imageEditor = new ImageEditor(el, { + usageStatistics: false, + }); + }); - afterEach(() => { - imageEditor.destroy(); - }); + afterEach(() => { + imageEditor.destroy(); + }); - xit('should send hostname by default', () => { - imageEditor = new ImageEditor(el); + xit('should send hostname by default', () => { + imageEditor = new ImageEditor(el); - expect(snippet.sendHostname).toHaveBeenCalled(); - }); + expect(snippet.sendHostname).toHaveBeenCalled(); + }); - xit('should not send hostname on usageStatistics option false', () => { - imageEditor = new ImageEditor(el, { - usageStatistics: false - }); + xit('should not send hostname on usageStatistics option false', () => { + imageEditor = new ImageEditor(el, { + usageStatistics: false, + }); - expect(snippet.sendHostname).not.toHaveBeenCalled(); - }); + expect(snippet.sendHostname).not.toHaveBeenCalled(); + }); - it('`preventDefault` of BACKSPACE key events should not be executed when object is selected state.', () => { - const spyCallback = jasmine.createSpy(); + it('`preventDefault` of BACKSPACE key events should not be executed when object is selected state.', () => { + const spyCallback = jasmine.createSpy(); - spyOn(imageEditor._graphics, 'getActiveObject').and.returnValue(null); + spyOn(imageEditor._graphics, 'getActiveObject').and.returnValue(null); - imageEditor._onKeyDown({ - keyCode: keyCodes.BACKSPACE, - preventDefault: spyCallback - }); + imageEditor._onKeyDown({ + keyCode: keyCodes.BACKSPACE, + preventDefault: spyCallback, + }); - expect(spyCallback).not.toHaveBeenCalled(); - }); + expect(spyCallback).not.toHaveBeenCalled(); + }); - it('"objectRotated" event should be fire at object is rotate.', () => { - const canvas = imageEditor._graphics.getCanvas(); - const obj = new fabric.Object({}); - const mock = {target: obj}; - canvas.add(obj); + it('"objectRotated" event should be fire at object is rotate.', () => { + const canvas = imageEditor._graphics.getCanvas(); + const obj = new fabric.Object({}); + const mock = { target: obj }; + canvas.add(obj); - spyOn(imageEditor, 'fire').and.callThrough(); + spyOn(imageEditor, 'fire').and.callThrough(); - canvas.fire('object:rotating', mock); + canvas.fire('object:rotating', mock); - expect(imageEditor.fire.calls.mostRecent().args[0]).toBe(OBJECT_ROTATED); - }); + expect(imageEditor.fire.calls.mostRecent().args[0]).toBe(OBJECT_ROTATED); }); + }); }); diff --git a/test/invoker.spec.js b/test/invoker.spec.js index 0da31eaea..3d3320e81 100644 --- a/test/invoker.spec.js +++ b/test/invoker.spec.js @@ -2,138 +2,174 @@ * @author NHN Ent. FE Development Team * @fileoverview Test cases of "src/js/invoker.js" */ -import {Promise} from '../src/js/util'; +import { Promise } from '../src/js/util'; import Invoker from '../src/js/invoker'; import Command from '../src/js/interface/command'; describe('Invoker', () => { - let invoker, cmd; + let invoker, cmd; - beforeEach(() => { - invoker = new Invoker(); + beforeEach(() => { + invoker = new Invoker(); - cmd = new Command({ - execute: jasmine.createSpy().and.returnValue(Promise.resolve()), - undo: jasmine.createSpy().and.returnValue(Promise.resolve()) - }); + cmd = new Command({ + execute: jasmine.createSpy().and.returnValue(Promise.resolve()), + undo: jasmine.createSpy().and.returnValue(Promise.resolve()), + }); + }); + + it('"redo()" should call "command.execute" again', (done) => { + invoker + .execute(cmd) + .then(() => invoker.undo()) + .then(() => { + cmd.execute.calls.reset(); + + return invoker.redo(); + }) + .then(() => { + expect(cmd.execute).toHaveBeenCalled(); + done(); + }); + }); + + it('should call the "command.executeCallback" after invoke', (done) => { + const spyCallback = jasmine.createSpy(); + + cmd.setExecuteCallback(spyCallback); + invoker.execute(cmd).then(() => { + expect(spyCallback).toHaveBeenCalled(); + done(); }); + }); - it('"redo()" should call "command.execute" again', done => { - invoker.execute(cmd).then(() => invoker.undo()).then(() => { - cmd.execute.calls.reset(); + it('should call the "command.undoCallback" after undo', (done) => { + const spyCallback = jasmine.createSpy(); - return invoker.redo(); - }).then(() => { - expect(cmd.execute).toHaveBeenCalled(); - done(); - }); - }); + cmd.setUndoCallback(spyCallback); + invoker + .execute(cmd) + .then(() => invoker.undo()) + .then(() => { + expect(spyCallback).toHaveBeenCalled(); + done(); + }); + }); - it('should call the "command.executeCallback" after invoke', done => { - const spyCallback = jasmine.createSpy(); + describe('invoker.customEvents', () => { + let spyEvents; - cmd.setExecuteCallback(spyCallback); - invoker.execute(cmd).then(() => { - expect(spyCallback).toHaveBeenCalled(); - done(); - }); + beforeEach(() => { + spyEvents = { + undoStackChanged: jasmine.createSpy(), + redoStackChanged: jasmine.createSpy(), + }; }); - it('should call the "command.undoCallback" after undo', done => { - const spyCallback = jasmine.createSpy(); - - cmd.setUndoCallback(spyCallback); - invoker.execute(cmd).then(() => invoker.undo()).then(() => { - expect(spyCallback).toHaveBeenCalled(); - done(); + it( + '"invoke()" should fire a event - ' + ' "pushUndoStack" (when redoStack is empty before)"', + (done) => { + invoker.on(spyEvents); + invoker.execute(cmd).then(() => { + expect(spyEvents.undoStackChanged).toHaveBeenCalledWith(1); + expect(spyEvents.redoStackChanged).not.toHaveBeenCalled(); + done(); }); - }); + } + ); - describe('invoker.customEvents', () => { - let spyEvents; + it( + '"invoke()" should fire events - ' + + ' "pushUndoStack", "clearRedoStack" (when redoStack is not empty before)', + (done) => { + invoker.pushRedoStack({}); - beforeEach(() => { - spyEvents = { - undoStackChanged: jasmine.createSpy(), - redoStackChanged: jasmine.createSpy() - }; + invoker.on(spyEvents); + invoker.execute(cmd).then(() => { + expect(spyEvents.undoStackChanged).toHaveBeenCalledWith(1); + expect(spyEvents.redoStackChanged).toHaveBeenCalledWith(0); + done(); }); - - it('"invoke()" should fire a event - ' + - ' "pushUndoStack" (when redoStack is empty before)"', done => { + } + ); + + it( + '"undo()" should fire a event - ' + ' "pushRedoStack" (when undoStack is not empty after)', + (done) => { + invoker + .execute(cmd) + .then(() => invoker.execute(cmd)) + .then(() => { invoker.on(spyEvents); - invoker.execute(cmd).then(() => { - expect(spyEvents.undoStackChanged).toHaveBeenCalledWith(1); - expect(spyEvents.redoStackChanged).not.toHaveBeenCalled(); - done(); - }); - }); - - it('"invoke()" should fire events - ' + - ' "pushUndoStack", "clearRedoStack" (when redoStack is not empty before)', done => { - invoker.pushRedoStack({}); + return invoker.undo(); + }) + .then(() => { + expect(spyEvents.undoStackChanged).not.toHaveBeenCalled(); + expect(spyEvents.redoStackChanged).toHaveBeenCalledWith(1); + done(); + }); + } + ); + + it( + '"undo()" should fire events - ' + + ' "pushRedoStack", "emptyUndoStack" (when undoStack is empty after)', + (done) => { + invoker + .execute(cmd) + .then(() => { invoker.on(spyEvents); - invoker.execute(cmd).then(() => { - expect(spyEvents.undoStackChanged).toHaveBeenCalledWith(1); - expect(spyEvents.redoStackChanged).toHaveBeenCalledWith(0); - done(); - }); - }); - it('"undo()" should fire a event - ' + - ' "pushRedoStack" (when undoStack is not empty after)', done => { - invoker.execute(cmd).then(() => invoker.execute(cmd)).then(() => { - invoker.on(spyEvents); - - return invoker.undo(); - }).then(() => { - expect(spyEvents.undoStackChanged).not.toHaveBeenCalled(); - expect(spyEvents.redoStackChanged).toHaveBeenCalledWith(1); - done(); - }); - }); - - it('"undo()" should fire events - ' + - ' "pushRedoStack", "emptyUndoStack" (when undoStack is empty after)', done => { - invoker.execute(cmd).then(() => { - invoker.on(spyEvents); - - return invoker.undo(); - }).then(() => { - expect(spyEvents.redoStackChanged).toHaveBeenCalledWith(1); - expect(spyEvents.undoStackChanged).toHaveBeenCalledWith(0); - done(); - }); - }); + return invoker.undo(); + }) + .then(() => { + expect(spyEvents.redoStackChanged).toHaveBeenCalledWith(1); + expect(spyEvents.undoStackChanged).toHaveBeenCalledWith(0); + done(); + }); + } + ); + + it( + '"redo()" should fire a event - ' + ' "pushUndoStack" (when redoStack is not empty after)', + (done) => { + invoker + .execute(cmd) + .then(() => invoker.execute(cmd)) + .then(() => invoker.undo()) + .then(() => invoker.undo()) + .then(() => { + invoker.on(spyEvents); - it('"redo()" should fire a event - ' + - ' "pushUndoStack" (when redoStack is not empty after)', done => { - invoker.execute(cmd).then(() => invoker.execute(cmd)) - .then(() => invoker.undo()) - .then(() => invoker.undo()) - .then(() => { - invoker.on(spyEvents); - - return invoker.redo(); - }).then(() => { - expect(spyEvents.undoStackChanged).toHaveBeenCalledWith(1); - expect(spyEvents.redoStackChanged).not.toHaveBeenCalled(); - done(); - }); - }); + return invoker.redo(); + }) + .then(() => { + expect(spyEvents.undoStackChanged).toHaveBeenCalledWith(1); + expect(spyEvents.redoStackChanged).not.toHaveBeenCalled(); + done(); + }); + } + ); + + it( + '"redo()" should fire events - ' + + ' "pushUndoStack", "emptyRedoStack" (when undoStack is empty after)', + (done) => { + invoker + .execute(cmd) + .then(() => invoker.undo()) + .then(() => { + invoker.on(spyEvents); - it('"redo()" should fire events - ' + - ' "pushUndoStack", "emptyRedoStack" (when undoStack is empty after)', done => { - invoker.execute(cmd).then(() => invoker.undo()).then(() => { - invoker.on(spyEvents); - - return invoker.redo(cmd); - }).then(() => { - expect(spyEvents.undoStackChanged).toHaveBeenCalledWith(1); - expect(spyEvents.redoStackChanged).toHaveBeenCalledWith(0); - done(); - }); - }); - }); + return invoker.redo(cmd); + }) + .then(() => { + expect(spyEvents.undoStackChanged).toHaveBeenCalledWith(1); + expect(spyEvents.redoStackChanged).toHaveBeenCalledWith(0); + done(); + }); + } + ); + }); }); diff --git a/test/line.spec.js b/test/line.spec.js index 736d630fd..a989cf6d8 100644 --- a/test/line.spec.js +++ b/test/line.spec.js @@ -6,75 +6,75 @@ import fabric from 'fabric'; import $ from 'jquery'; import Graphics from '../src/js/graphics'; import Line from '../src/js/component/line'; -import {eventNames} from '../src/js/consts'; +import { eventNames } from '../src/js/consts'; describe('Line', () => { - let canvas, graphics, mockImage, line, fEvent; - - beforeAll(() => { - graphics = new Graphics($('')[0]); - canvas = graphics.getCanvas(); - line = new Line(graphics); + let canvas, graphics, mockImage, line, fEvent; + + beforeAll(() => { + graphics = new Graphics($('')[0]); + canvas = graphics.getCanvas(); + line = new Line(graphics); + }); + + beforeEach(() => { + mockImage = new fabric.Image(); + graphics.setCanvasImage('mockImage', mockImage); + + fEvent = { + e: {}, + }; + }); + + afterEach(() => { + canvas.forEachObject((obj) => { + canvas.remove(obj); }); + }); - beforeEach(() => { - mockImage = new fabric.Image(); - graphics.setCanvasImage('mockImage', mockImage); + it('_onFabricMouseDown() should insert the line.', () => { + line._onFabricMouseDown(fEvent); - fEvent = { - e: {} - }; - }); + expect(canvas.getObjects().length).toEqual(1); + }); - afterEach(() => { - canvas.forEachObject(obj => { - canvas.remove(obj); - }); - }); + it('_onFabricMouseMove() should draw line located by mouse pointer.', () => { + line._line = new fabric.Line([10, 20, 10, 20]); - it('_onFabricMouseDown() should insert the line.', () => { - line._onFabricMouseDown(fEvent); + canvas.add(line._line); - expect(canvas.getObjects().length).toEqual(1); + spyOn(canvas, 'getPointer').and.returnValue({ + x: 30, + y: 60, }); - it('_onFabricMouseMove() should draw line located by mouse pointer.', () => { - line._line = new fabric.Line([10, 20, 10, 20]); + expect(canvas.getObjects()[0].get('x2')).toEqual(10); + expect(canvas.getObjects()[0].get('y2')).toEqual(20); - canvas.add(line._line); + line._onFabricMouseMove(fEvent); - spyOn(canvas, 'getPointer').and.returnValue({ - x: 30, - y: 60 - }); + expect(canvas.getObjects()[0].get('x2')).toEqual(30); + expect(canvas.getObjects()[0].get('y2')).toEqual(60); + }); - expect(canvas.getObjects()[0].get('x2')).toEqual(10); - expect(canvas.getObjects()[0].get('y2')).toEqual(20); + it('end() should restore all drawing objects activated.', () => { + const path = new fabric.Path(); - line._onFabricMouseMove(fEvent); - - expect(canvas.getObjects()[0].get('x2')).toEqual(30); - expect(canvas.getObjects()[0].get('y2')).toEqual(60); - }); + canvas.add(path); - it('end() should restore all drawing objects activated.', () => { - const path = new fabric.Path(); + line.start(); - canvas.add(path); + expect(canvas.getObjects()[0].get('evented')).toEqual(false); - line.start(); + line.end(); - expect(canvas.getObjects()[0].get('evented')).toEqual(false); + expect(canvas.getObjects()[0].get('evented')).toEqual(true); + }); - line.end(); + it('"objectAdded" event should fire after the line is drawn.', () => { + spyOn(line, 'fire').and.callThrough(); + line._onFabricMouseUp(fEvent); - expect(canvas.getObjects()[0].get('evented')).toEqual(true); - }); - - it('"objectAdded" event should fire after the line is drawn.', () => { - spyOn(line, 'fire').and.callThrough(); - line._onFabricMouseUp(fEvent); - - expect(line.fire.calls.mostRecent().args[0]).toBe(eventNames.OBJECT_ADDED); - }); + expect(line.fire.calls.mostRecent().args[0]).toBe(eventNames.OBJECT_ADDED); + }); }); diff --git a/test/promiseApi.spec.js b/test/promiseApi.spec.js index ec143261f..87a9c449f 100644 --- a/test/promiseApi.spec.js +++ b/test/promiseApi.spec.js @@ -5,396 +5,470 @@ import ImageEditor from '../src/js/imageEditor'; describe('Promise API', () => { - let imageEditor, canvas, activeObjectId; - const imageURL = 'base/test/fixtures/sampleImage.jpg'; + let imageEditor, canvas, activeObjectId; + const imageURL = 'base/test/fixtures/sampleImage.jpg'; - beforeAll(() => { - imageEditor = new ImageEditor(document.createElement('div'), { - cssMaxWidth: 700, - cssMaxHeight: 500 - }); - canvas = imageEditor._graphics.getCanvas(); - - imageEditor.on('objectActivated', objectProps => { - activeObjectId = objectProps.id; - }); + beforeAll(() => { + imageEditor = new ImageEditor(document.createElement('div'), { + cssMaxWidth: 700, + cssMaxHeight: 500, }); + canvas = imageEditor._graphics.getCanvas(); - afterAll(() => { - imageEditor.destroy(); + imageEditor.on('objectActivated', (objectProps) => { + activeObjectId = objectProps.id; }); - - beforeEach(done => { - imageEditor.loadImageFromURL(imageURL, 'sampleImage').then(() => done())['catch'](() => done()); - }); - - it('addIcon() supports Promise', done => { - imageEditor.addIcon('arrow', { - left: 10, - top: 10 - }).then(() => { - expect(canvas.getObjects().length).toBe(1); - done(); - })['catch'](message => { - fail(message); - done(); + }); + + afterAll(() => { + imageEditor.destroy(); + }); + + beforeEach((done) => { + imageEditor + .loadImageFromURL(imageURL, 'sampleImage') + .then(() => done()) + ['catch'](() => done()); + }); + + it('addIcon() supports Promise', (done) => { + imageEditor + .addIcon('arrow', { + left: 10, + top: 10, + }) + .then(() => { + expect(canvas.getObjects().length).toBe(1); + done(); + }) + ['catch']((message) => { + fail(message); + done(); + }); + }); + + it('clearObjects() supports Promise', (done) => { + imageEditor + .addIcon('arrow', { + left: 10, + top: 10, + }) + .then(() => imageEditor.clearObjects()) + .then(() => { + expect(canvas.getObjects().length).toBe(0); + done(); + }) + ['catch']((message) => { + fail(message); + done(); + }); + }); + + it('changeIconColor() supports Promise', (done) => { + imageEditor + .addIcon('arrow', { + left: 10, + top: 10, + }) + .then(() => imageEditor.changeIconColor(activeObjectId, '#FFFF00')) + .then(() => { + expect(canvas.getObjects()[0].fill).toBe('#FFFF00'); + done(); + }) + ['catch']((message) => { + fail(message); + done(); + }); + }); + + it('addShape() supports Promise', (done) => { + imageEditor + .addShape('rect', { + width: 100, + height: 100, + fill: '#FFFF00', + }) + .then(() => { + const [shape] = canvas.getObjects(); + expect(shape.type).toBe('rect'); + expect(shape.width).toBe(100); + expect(shape.height).toBe(100); + expect(shape.fill).toBe('#FFFF00'); + done(); + }) + ['catch']((message) => { + fail(message); + done(); + }); + }); + + it('changeShape() supports Promise', (done) => { + imageEditor + .addShape('rect', { + width: 100, + height: 100, + fill: '#FFFF00', + }) + .then(() => + imageEditor.changeShape(activeObjectId, { + type: 'triangle', + width: 200, + fill: '#FF0000', + }) + ) + .then(() => { + const [shape] = canvas.getObjects(); + expect(shape.type).toBe('triangle'); + expect(shape.width).toBe(200); + expect(shape.fill).toBe('#FF0000'); + done(); + }) + ['catch']((message) => { + fail(message); + done(); + }); + }); + + it('can catch on failure', (done) => { + imageEditor + .addShape('rect', { + width: 100, + height: 100, + fill: '#FFFF00', + }) + .then(() => { + imageEditor.deactivateAll(); + + return imageEditor.changeShape(null, { + type: 'triangle', + widht: 200, + fill: '#FF0000', }); - }); - - it('clearObjects() supports Promise', done => { - imageEditor.addIcon('arrow', { - left: 10, - top: 10 - }).then(() => imageEditor.clearObjects()).then(() => { - expect(canvas.getObjects().length).toBe(0); - done(); - })['catch'](message => { - fail(message); - done(); - }); - }); - - it('changeIconColor() supports Promise', done => { - imageEditor.addIcon('arrow', { - left: 10, - top: 10 - }).then(() => imageEditor.changeIconColor(activeObjectId, '#FFFF00') - ).then(() => { - expect(canvas.getObjects()[0].fill).toBe('#FFFF00'); - done(); - })['catch'](message => { - fail(message); - done(); + }) + .then(() => { + fail(); + done(); + }) + ['catch']((message) => { + expect(message).toBe('The object is not in canvas.'); + done(); + }); + }); + + it('addImageObject() supports Promise', (done) => { + const maskImageURL = 'base/test/fixtures/mask.png'; + imageEditor + .addImageObject(maskImageURL) + .then((objectProps) => { + expect(canvas.getObjects().length).toBe(1); + expect(objectProps.id).toBe(activeObjectId); + done(); + }) + ['catch']((message) => { + fail(message); + done(); + }); + }); + + it('resizeCanvasDimension() supports Promise', (done) => { + imageEditor + .resizeCanvasDimension({ + width: 900, + height: 700, + }) + .then(() => + // There is no way to get canvas dimension + done() + ) + ['catch']((message) => { + fail(message); + done(); + }); + }); + + it('undo() supports Promise', (done) => { + imageEditor + .addShape('rect', { + width: 100, + height: 100, + fill: '#FFFF00', + }) + .then(() => imageEditor.undo()) + .then(() => { + expect(canvas.getObjects().length).toBe(0); + done(); + }) + ['catch']((message) => { + fail(message); + done(); + }); + }); + + it('flipX() supports Promise', (done) => { + imageEditor + .flipX() + .then((obj) => { + expect(obj).toEqual({ + flipX: true, + flipY: false, + angle: 0, }); - }); - - it('addShape() supports Promise', done => { - imageEditor.addShape('rect', { - width: 100, - height: 100, - fill: '#FFFF00' - }).then(() => { - const [shape] = canvas.getObjects(); - expect(shape.type).toBe('rect'); - expect(shape.width).toBe(100); - expect(shape.height).toBe(100); - expect(shape.fill).toBe('#FFFF00'); - done(); - })['catch'](message => { - fail(message); - done(); - }); - }); - - it('changeShape() supports Promise', done => { - imageEditor.addShape('rect', { - width: 100, - height: 100, - fill: '#FFFF00' - }).then(() => imageEditor.changeShape(activeObjectId, { - type: 'triangle', - width: 200, - fill: '#FF0000' - })).then(() => { - const [shape] = canvas.getObjects(); - expect(shape.type).toBe('triangle'); - expect(shape.width).toBe(200); - expect(shape.fill).toBe('#FF0000'); - done(); - })['catch'](message => { - fail(message); - done(); - }); - }); - - it('can catch on failure', done => { - imageEditor.addShape('rect', { - width: 100, - height: 100, - fill: '#FFFF00' - }).then(() => { - imageEditor.deactivateAll(); - - return imageEditor.changeShape(null, { - type: 'triangle', - widht: 200, - fill: '#FF0000' - }); - }).then(() => { - fail(); - done(); - })['catch'](message => { - expect(message).toBe('The object is not in canvas.'); - done(); - }); - }); - - it('addImageObject() supports Promise', done => { - const maskImageURL = 'base/test/fixtures/mask.png'; - imageEditor.addImageObject(maskImageURL).then(objectProps => { - expect(canvas.getObjects().length).toBe(1); - expect(objectProps.id).toBe(activeObjectId); - done(); - })['catch'](message => { - fail(message); - done(); + done(); + }) + ['catch']((message) => { + fail(message); + done(); + }); + }); + + it('flipY() supports Promise', (done) => { + imageEditor + .flipY() + .then((obj) => { + expect(obj).toEqual({ + flipX: false, + flipY: true, + angle: 0, }); - }); - - it('resizeCanvasDimension() supports Promise', done => { - imageEditor.resizeCanvasDimension({ - width: 900, - height: 700 - }).then(() => - // There is no way to get canvas dimension - done() - )['catch'](message => { - fail(message); - done(); + done(); + }) + ['catch']((message) => { + fail(message); + done(); + }); + }); + + it('resetFlip() supports Promise', (done) => { + imageEditor + .resetFlip() + .then((obj) => { + expect(obj).toEqual({ + flipX: false, + flipY: false, + angle: 0, }); + fail(); + done(); + }) + ['catch']((message) => { + expect(message).toBe('The flipX and flipY setting values are not changed.'); + done(); + }); + }); + + it('rotate() supports Promise', (done) => { + imageEditor + .rotate(10) + .then((angle) => { + expect(angle).toBe(10); + done(); + }) + ['catch']((message) => { + fail(message); + done(); + }); + }); + + it('setAngle() supports Promise', (done) => { + imageEditor + .setAngle(10) + .then((angle) => { + expect(angle).toBe(10); + done(); + }) + ['catch']((message) => { + fail(message); + done(); + }); + }); + + it('removeObject() supports Promise', (done) => { + imageEditor + .addShape('rect', { + width: 100, + height: 100, + }) + .then((objectProps) => imageEditor.removeObject(objectProps.id)) + .then(() => { + expect(canvas.getObjects().length).toBe(0); + done(); + }) + ['catch']((message) => { + fail(message); + done(); + }); + }); + + describe('Watermark', () => { + const maskImageURL = 'base/test/fixtures/mask.png'; + const properties = { + fill: 'rgba(255, 255, 0, 0.5)', + left: 150, + top: 30, + }; + + beforeEach((done) => { + imageEditor.addImageObject(maskImageURL).then(() => { + done(); + }); }); - it('undo() supports Promise', done => { - imageEditor.addShape('rect', { - width: 100, - height: 100, - fill: '#FFFF00' - }).then(() => - imageEditor.undo() - ).then(() => { - expect(canvas.getObjects().length).toBe(0); - done(); - })['catch'](message => { - fail(message); - done(); + it("setObjectProperties() should change object's properties", (done) => { + imageEditor + .setObjectProperties(activeObjectId, properties) + .then(() => { + done(); + }) + ['catch']((message) => { + fail(message); + done(); }); }); - it('flipX() supports Promise', done => { - imageEditor.flipX().then(obj => { - expect(obj).toEqual({ - flipX: true, - flipY: false, - angle: 0 - }); - done(); - })['catch'](message => { - fail(message); - done(); + it("getObjectProperties() should return object's properties", (done) => { + imageEditor + .setObjectProperties(activeObjectId, properties) + .then(() => { + const propKeys = { + fill: null, + left: null, + top: null, + }; + const result = imageEditor.getObjectProperties(activeObjectId, propKeys); + + expect(result).not.toBe(null); + expect(result).toEqual( + jasmine.objectContaining({ + fill: 'rgba(255, 255, 0, 0.5)', + left: 150, + top: 30, + }) + ); + done(); + }) + ['catch']((message) => { + fail(message); + done(); }); }); - it('flipY() supports Promise', done => { - imageEditor.flipY().then(obj => { - expect(obj).toEqual({ - flipX: false, - flipY: true, - angle: 0 - }); - done(); - })['catch'](message => { - fail(message); - done(); + it('getObjectProperties(objectKeys) should return false if there is no object', (done) => { + imageEditor + .setObjectProperties(activeObjectId, properties) + .then(() => { + const propKeys = { + fill: null, + width: null, + left: null, + top: null, + height: null, + }; + + imageEditor.deactivateAll(); + + const result = imageEditor.getObjectProperties(null, propKeys); + + expect(result).toBe(null); + done(); + }) + ['catch']((message) => { + fail(message); + done(); }); }); - it('resetFlip() supports Promise', done => { - imageEditor.resetFlip().then(obj => { - expect(obj).toEqual({ - flipX: false, - flipY: false, - angle: 0 - }); - fail(); - done(); - })['catch'](message => { - expect(message).toBe('The flipX and flipY setting values are not changed.'); - done(); + it("getObjectProperties(arrayKeys) should return object's properties", (done) => { + imageEditor + .setObjectProperties(activeObjectId, properties) + .then(() => { + const arrayKeys = ['fill', 'width', 'left', 'top', 'height']; + const result = imageEditor.getObjectProperties(activeObjectId, arrayKeys); + + expect(result).not.toBe(null); + expect(result).toEqual(jasmine.objectContaining(properties)); + done(); + }) + ['catch']((message) => { + fail(message); + done(); }); }); - it('rotate() supports Promise', done => { - imageEditor.rotate(10).then(angle => { - expect(angle).toBe(10); - done(); - })['catch'](message => { - fail(message); - done(); + it("getObjectProperties(stringKey) should return object's property", (done) => { + imageEditor + .setObjectProperties(activeObjectId, properties) + .then(() => { + const result = imageEditor.getObjectProperties(activeObjectId, 'fill'); + + expect(result).not.toBe(null); + expect(result).toEqual( + jasmine.objectContaining({ + fill: 'rgba(255, 255, 0, 0.5)', + }) + ); + done(); + }) + ['catch']((message) => { + fail(message); + done(); }); }); - it('setAngle() supports Promise', done => { - imageEditor.setAngle(10).then(angle => { - expect(angle).toBe(10); - done(); - })['catch'](message => { - fail(message); - done(); - }); + it("getCanvasSize() should return canvas's width, height.", () => { + expect(imageEditor.getCanvasSize()).toEqual( + jasmine.objectContaining({ + width: 1600, + height: 1066, + }) + ); }); - it('removeObject() supports Promise', done => { - imageEditor.addShape('rect', { - width: 100, - height: 100 - }).then(objectProps => - imageEditor.removeObject(objectProps.id) - ).then(() => { - expect(canvas.getObjects().length).toBe(0); - done(); - })['catch'](message => { - fail(message); - done(); - }); + it('getObjectPosition() should return global point by origin.', () => { + // ImageEditor's object has origin('center', 'center'). + const { left, top, width, height } = imageEditor.getObjectProperties(activeObjectId, [ + 'left', + 'top', + 'width', + 'height', + ]); + const ltPoint = imageEditor.getObjectPosition(activeObjectId, 'left', 'top'); + const ccPoint = imageEditor.getObjectPosition(activeObjectId, 'center', 'center'); + const rbPoint = imageEditor.getObjectPosition(activeObjectId, 'right', 'bottom'); + + expect(ltPoint.x).toBe(left - width / 2); + expect(ltPoint.y).toBe(top - height / 2); + expect(ccPoint.x).toBe(left); + expect(ccPoint.y).toBe(top); + expect(rbPoint.x).toBe(left + width / 2); + expect(rbPoint.y).toBe(top + height / 2); }); - describe('Watermark', () => { - const maskImageURL = 'base/test/fixtures/mask.png'; - const properties = { - fill: 'rgba(255, 255, 0, 0.5)', - left: 150, - top: 30 - }; - - beforeEach(done => { - imageEditor.addImageObject(maskImageURL).then(() => { - done(); - }); - }); - - it('setObjectProperties() should change object\'s properties', done => { - imageEditor.setObjectProperties(activeObjectId, properties).then(() => { - done(); - })['catch'](message => { - fail(message); - done(); - }); - }); - - it('getObjectProperties() should return object\'s properties', done => { - imageEditor.setObjectProperties(activeObjectId, properties).then(() => { - const propKeys = { - fill: null, - left: null, - top: null - }; - const result = imageEditor.getObjectProperties(activeObjectId, propKeys); - - expect(result).not.toBe(null); - expect(result).toEqual(jasmine.objectContaining({ - fill: 'rgba(255, 255, 0, 0.5)', - left: 150, - top: 30 - })); - done(); - })['catch'](message => { - fail(message); - done(); - }); - }); - - it('getObjectProperties(objectKeys) should return false if there is no object', done => { - imageEditor.setObjectProperties(activeObjectId, properties).then(() => { - const propKeys = { - fill: null, - width: null, - left: null, - top: null, - height: null - }; - - imageEditor.deactivateAll(); - - const result = imageEditor.getObjectProperties(null, propKeys); - - expect(result).toBe(null); - done(); - })['catch'](message => { - fail(message); - done(); - }); - }); - - it('getObjectProperties(arrayKeys) should return object\'s properties', done => { - imageEditor.setObjectProperties(activeObjectId, properties).then(() => { - const arrayKeys = [ - 'fill', - 'width', - 'left', - 'top', - 'height' - ]; - const result = imageEditor.getObjectProperties(activeObjectId, arrayKeys); - - expect(result).not.toBe(null); - expect(result).toEqual(jasmine.objectContaining(properties)); - done(); - })['catch'](message => { - fail(message); - done(); - }); - }); - - it('getObjectProperties(stringKey) should return object\'s property', done => { - imageEditor.setObjectProperties(activeObjectId, properties).then(() => { - const result = imageEditor.getObjectProperties(activeObjectId, 'fill'); - - expect(result).not.toBe(null); - expect(result).toEqual(jasmine.objectContaining({ - fill: 'rgba(255, 255, 0, 0.5)' - })); - done(); - })['catch'](message => { - fail(message); - done(); - }); - }); - - it('getCanvasSize() should return canvas\'s width, height.', () => { - expect(imageEditor.getCanvasSize()).toEqual(jasmine.objectContaining({ - width: 1600, - height: 1066 - })); - }); - - it('getObjectPosition() should return global point by origin.', () => { - // ImageEditor's object has origin('center', 'center'). - const {left, top, width, height} = imageEditor.getObjectProperties(activeObjectId, - ['left', 'top', 'width', 'height']); - const ltPoint = imageEditor.getObjectPosition(activeObjectId, 'left', 'top'); - const ccPoint = imageEditor.getObjectPosition(activeObjectId, 'center', 'center'); - const rbPoint = imageEditor.getObjectPosition(activeObjectId, 'right', 'bottom'); - - expect(ltPoint.x).toBe(left - (width / 2)); - expect(ltPoint.y).toBe(top - (height / 2)); - expect(ccPoint.x).toBe(left); - expect(ccPoint.y).toBe(top); - expect(rbPoint.x).toBe(left + (width / 2)); - expect(rbPoint.y).toBe(top + (height / 2)); - }); - - it('setObjectPosition() can set object position by origin', done => { - imageEditor.setObjectProperties(activeObjectId, { - width: 200, - height: 100 - }).then(() => - imageEditor.setObjectPosition(activeObjectId, { - x: 0, - y: 0, - originX: 'left', - originY: 'top' - }) - ).then(() => { - const result = imageEditor.getObjectProperties(activeObjectId, ['left', 'top']); - - expect(result.left).toBe(100); - expect(result.top).toBe(50); - - done(); - })['catch'](message => { - fail(message); - done(); - }); + it('setObjectPosition() can set object position by origin', (done) => { + imageEditor + .setObjectProperties(activeObjectId, { + width: 200, + height: 100, + }) + .then(() => + imageEditor.setObjectPosition(activeObjectId, { + x: 0, + y: 0, + originX: 'left', + originY: 'top', + }) + ) + .then(() => { + const result = imageEditor.getObjectProperties(activeObjectId, ['left', 'top']); + + expect(result.left).toBe(100); + expect(result.top).toBe(50); + + done(); + }) + ['catch']((message) => { + fail(message); + done(); }); }); + }); }); diff --git a/test/rotation.spec.js b/test/rotation.spec.js index 3ff2facba..ee1ab5224 100644 --- a/test/rotation.spec.js +++ b/test/rotation.spec.js @@ -8,58 +8,61 @@ import Graphics from '../src/js/graphics'; import Rotation from '../src/js/component/rotation'; describe('Rotation', () => { - let graphics, rotationModule, mockImage, canvas; + let graphics, rotationModule, mockImage, canvas; - beforeAll(() => { - graphics = new Graphics($('')[0]); - canvas = graphics.getCanvas(); - rotationModule = new Rotation(graphics); - }); - - beforeEach(() => { - mockImage = new fabric.Image(); - graphics.setCanvasImage('mockImage', mockImage); - }); + beforeAll(() => { + graphics = new Graphics($('')[0]); + canvas = graphics.getCanvas(); + rotationModule = new Rotation(graphics); + }); - it('"getCurrentAngle()" should return current angle value', () => { - mockImage.angle = 30; + beforeEach(() => { + mockImage = new fabric.Image(); + graphics.setCanvasImage('mockImage', mockImage); + }); - expect(rotationModule.getCurrentAngle()).toEqual(30); - }); + it('"getCurrentAngle()" should return current angle value', () => { + mockImage.angle = 30; - it('"setAngle()" should set angle value', () => { - rotationModule.setAngle(40); + expect(rotationModule.getCurrentAngle()).toEqual(30); + }); - expect(rotationModule.getCurrentAngle()).toEqual(40); - }); + it('"setAngle()" should set angle value', () => { + rotationModule.setAngle(40); - it('"rotate()" should add angle value', () => { - let current = rotationModule.getCurrentAngle(); + expect(rotationModule.getCurrentAngle()).toEqual(40); + }); - rotationModule.rotate(10); - expect(rotationModule.getCurrentAngle()).toBe(current + 10); + it('"rotate()" should add angle value', () => { + let current = rotationModule.getCurrentAngle(); - current = rotationModule.getCurrentAngle(); - rotationModule.rotate(20); - expect(rotationModule.getCurrentAngle()).toBe(current + 20); - }); + rotationModule.rotate(10); + expect(rotationModule.getCurrentAngle()).toBe(current + 10); - it('"rotate()" should add angle value modular 360(===2*PI)', done => { - rotationModule.setAngle(10).then(() => rotationModule.rotate(380)).then(() => { - expect(rotationModule.getCurrentAngle()).toBe(30); - done(); - }); - }); + current = rotationModule.getCurrentAngle(); + rotationModule.rotate(20); + expect(rotationModule.getCurrentAngle()).toBe(current + 20); + }); - // @todo Move this tc to main.spec.js - it('"adjustCanvasDimension()" should set canvas dimension from image-rect', () => { - spyOn(mockImage, 'getBoundingRect').and.returnValue({ - width: 100, - height: 110 - }); + it('"rotate()" should add angle value modular 360(===2*PI)', (done) => { + rotationModule + .setAngle(10) + .then(() => rotationModule.rotate(380)) + .then(() => { + expect(rotationModule.getCurrentAngle()).toBe(30); + done(); + }); + }); - rotationModule.adjustCanvasDimension(); - expect(canvas.getWidth()).toEqual(100); - expect(canvas.getHeight()).toEqual(110); + // @todo Move this tc to main.spec.js + it('"adjustCanvasDimension()" should set canvas dimension from image-rect', () => { + spyOn(mockImage, 'getBoundingRect').and.returnValue({ + width: 100, + height: 110, }); + + rotationModule.adjustCanvasDimension(); + expect(canvas.getWidth()).toEqual(100); + expect(canvas.getHeight()).toEqual(110); + }); }); diff --git a/test/selectionModifyHelper.spec.js b/test/selectionModifyHelper.spec.js index 4af4cd10e..aef37585a 100644 --- a/test/selectionModifyHelper.spec.js +++ b/test/selectionModifyHelper.spec.js @@ -1,64 +1,68 @@ -import {setCachedUndoDataForDimension, getCachedUndoDataForDimension, makeSelectionUndoData, makeSelectionUndoDatum} from '../src/js/helper/selectionModifyHelper'; +import { + setCachedUndoDataForDimension, + getCachedUndoDataForDimension, + makeSelectionUndoData, + makeSelectionUndoDatum, +} from '../src/js/helper/selectionModifyHelper'; import Graphics from '../src/js/graphics'; import fabric from 'fabric'; describe('selectionModifyHelper', () => { - let graphics, obj1, obj2; - const rectOption = { - width: 10, - height: 10, - top: 10, - left: 10, - scaleX: 1, - scaleY: 1, - angle: 0 - }; + let graphics, obj1, obj2; + const rectOption = { + width: 10, + height: 10, + top: 10, + left: 10, + scaleX: 1, + scaleY: 1, + angle: 0, + }; - beforeEach(() => { - graphics = new Graphics(document.createElement('canvas')); - obj1 = new fabric.Rect(rectOption); - obj2 = new fabric.Rect(rectOption); - }); + beforeEach(() => { + graphics = new Graphics(document.createElement('canvas')); + obj1 = new fabric.Rect(rectOption); + obj2 = new fabric.Rect(rectOption); + }); - it('should set/get cached undo data', () => { - const undoData = [{id: 1}]; + it('should set/get cached undo data', () => { + const undoData = [{ id: 1 }]; - setCachedUndoDataForDimension(undoData); + setCachedUndoDataForDimension(undoData); - expect(getCachedUndoDataForDimension()).toEqual(undoData); - }); + expect(getCachedUndoDataForDimension()).toEqual(undoData); + }); - describe('makeSelectionUndoData', () => { - it('should make object undo data', () => { - const result = makeSelectionUndoData(obj1, obj => obj); + describe('makeSelectionUndoData', () => { + it('should make object undo data', () => { + const result = makeSelectionUndoData(obj1, (obj) => obj); - expect(result).toEqual([obj1]); - }); + expect(result).toEqual([obj1]); + }); - it('should make selection undo data', () => { - const selection = graphics.getActiveSelectionFromObjects([obj1, obj2]); + it('should make selection undo data', () => { + const selection = graphics.getActiveSelectionFromObjects([obj1, obj2]); - const result = makeSelectionUndoData(selection, obj => obj); + const result = makeSelectionUndoData(selection, (obj) => obj); - expect(result).toEqual([obj1, obj2]); - }); + expect(result).toEqual([obj1, obj2]); }); + }); - describe('makeSelectionUndoDatum', () => { - it('should return undo datum', () => { - const result = makeSelectionUndoDatum(1, obj1, true); + describe('makeSelectionUndoDatum', () => { + it('should return undo datum', () => { + const result = makeSelectionUndoDatum(1, obj1, true); - expect(result).toEqual({ - id: 1, - width: obj1.width, - height: obj1.height, - top: obj1.top, - left: obj1.left, - angle: obj1.angle, - scaleX: obj1.scaleX, - scaleY: obj1.scaleY - }); - }); + expect(result).toEqual({ + id: 1, + width: obj1.width, + height: obj1.height, + top: obj1.top, + left: obj1.left, + angle: obj1.angle, + scaleX: obj1.scaleX, + scaleY: obj1.scaleY, + }); }); -} -); + }); +}); diff --git a/test/shape.spec.js b/test/shape.spec.js index 097d8f49a..64e418d89 100644 --- a/test/shape.spec.js +++ b/test/shape.spec.js @@ -6,542 +6,567 @@ import fabric from 'fabric'; import $ from 'jquery'; import Graphics from '../src/js/graphics'; import Shape from '../src/js/component/shape'; -import {resize} from '../src/js/helper/shapeResizeHelper'; -import {getFillImageFromShape, getCachedCanvasImageElement} from '../src/js/helper/shapeFilterFillHelper'; +import { resize } from '../src/js/helper/shapeResizeHelper'; +import { + getFillImageFromShape, + getCachedCanvasImageElement, +} from '../src/js/helper/shapeFilterFillHelper'; describe('Shape', () => { - let canvas, graphics, mockImage, fEvent, shape, shapeObj; + let canvas, graphics, mockImage, fEvent, shape, shapeObj; - beforeAll(() => { - graphics = new Graphics($('')[0]); - canvas = graphics.getCanvas(); + beforeAll(() => { + graphics = new Graphics($('')[0]); + canvas = graphics.getCanvas(); - shape = new Shape(graphics); - }); + shape = new Shape(graphics); + }); - beforeEach(() => { - mockImage = new fabric.Image(); - graphics.setCanvasImage('mockImage', mockImage); + beforeEach(() => { + mockImage = new fabric.Image(); + graphics.setCanvasImage('mockImage', mockImage); - fEvent = { - e: {} - }; - }); + fEvent = { + e: {}, + }; + }); - afterEach(() => { - canvas.forEachObject(obj => { - canvas.remove(obj); - }); + afterEach(() => { + canvas.forEachObject((obj) => { + canvas.remove(obj); }); + }); - it('The origin direction and position value initially adjusted at resize must be calculated correctly.', () => { - const pointer = canvas.getPointer(fEvent.e); - const settings = { - strokeWidth: 0, - type: 'rect', - left: 150, - top: 200, - width: 40, - height: 40, - originX: 'center', - originY: 'center' - }; - - shape.add('rect', settings); - [shapeObj] = canvas.getObjects(); + it('The origin direction and position value initially adjusted at resize must be calculated correctly.', () => { + const pointer = canvas.getPointer(fEvent.e); + const settings = { + strokeWidth: 0, + type: 'rect', + left: 150, + top: 200, + width: 40, + height: 40, + originX: 'center', + originY: 'center', + }; - spyOn(shapeObj, 'set').and.callThrough(); + shape.add('rect', settings); + [shapeObj] = canvas.getObjects(); - resize(shapeObj, pointer); + spyOn(shapeObj, 'set').and.callThrough(); - const [{left: resultLeft, top: resultTop}] = shapeObj.set.calls.first().args; + resize(shapeObj, pointer); - expect(resultLeft).toBe(settings.left - (settings.width / 2)); - expect(resultTop).toBe(settings.top - (settings.height / 2)); - }); + const [{ left: resultLeft, top: resultTop }] = shapeObj.set.calls.first().args; - it('The rectagle object is created on canvas.', () => { - shape.add('rect'); + expect(resultLeft).toBe(settings.left - settings.width / 2); + expect(resultTop).toBe(settings.top - settings.height / 2); + }); + + it('The rectagle object is created on canvas.', () => { + shape.add('rect'); - [shapeObj] = canvas.getObjects(); + [shapeObj] = canvas.getObjects(); - expect(shapeObj.type).toBe('rect'); - }); + expect(shapeObj.type).toBe('rect'); + }); + + it('The circle object(ellipse) is created on canvas.', () => { + shape.add('circle'); - it('The circle object(ellipse) is created on canvas.', () => { - shape.add('circle'); + [shapeObj] = canvas.getObjects(); + + expect(shapeObj.type).toBe('circle'); + }); - [shapeObj] = canvas.getObjects(); + it('The triangle object is created on canvas.', () => { + shape.add('triangle'); - expect(shapeObj.type).toBe('circle'); - }); + [shapeObj] = canvas.getObjects(); + + expect(shapeObj.type).toBe('triangle'); + }); + + it('When add() is called with no options, the default options set the rectangle object.', () => { + shape.add('rect'); + + [shapeObj] = canvas.getObjects(); + + expect(shapeObj.width).toBe(1); // strokeWidth: 1, width: 1 + expect(shapeObj.height).toBe(1); // strokeWidth: 1, height: 1 + }); + + it('When add() is called with no options, the default options set the circle object.', () => { + shape.add('circle'); + + [shapeObj] = canvas.getObjects(); + + expect(shapeObj.width).toBe(0); + expect(shapeObj.height).toBe(0); + }); + + it('When add() is called with no options, the default options set the triangle object.', () => { + shape.add('triangle'); + + [shapeObj] = canvas.getObjects(); + + expect(shapeObj.width).toBe(1); // strokeWidth: 1, width: 1 + expect(shapeObj.height).toBe(1); // strokeWidth: 1, height: 1 + }); + + it('When add() is called with the options, this options set the rectagle object.', () => { + const settings = { + fill: 'blue', + stroke: 'red', + strokeWidth: 10, + type: 'rect', + width: 100, + height: 100, + }; + + shape.add('rect', settings); + [shapeObj] = canvas.getObjects(); + + expect(shapeObj.fill).toBe('blue'); + expect(shapeObj.stroke).toBe('red'); + expect(shapeObj.strokeWidth).toBe(10); + expect(shapeObj.width).toBe(100); // width + storkeWidth + expect(shapeObj.height).toBe(100); // height + storkeWidth + }); + + it('When add() is called with the options, this options set the circle object.', () => { + const settings = { + fill: 'blue', + stroke: 'red', + strokeWidth: 3, + type: 'circle', + rx: 100, + ry: 50, + }; - it('The triangle object is created on canvas.', () => { - shape.add('triangle'); + shape.add('circle', settings); + [shapeObj] = canvas.getObjects(); - [shapeObj] = canvas.getObjects(); + expect(shapeObj.fill).toBe('blue'); + expect(shapeObj.stroke).toBe('red'); + expect(shapeObj.strokeWidth).toBe(3); + expect(shapeObj.width).toBe(200); // rx * 2 + stokeWidth + expect(shapeObj.height).toBe(100); // ry * 2 + stokeWidth + }); - expect(shapeObj.type).toBe('triangle'); + it('When add() is called with the options, this options set the triangle object.', () => { + const settings = { + fill: 'blue', + stroke: 'red', + strokeWidth: 0, + type: 'triangle', + width: 100, + height: 100, + }; + + shape.add('triangle', settings); + [shapeObj] = canvas.getObjects(); + + expect(shapeObj.fill).toBe('blue'); + expect(shapeObj.stroke).toBe('red'); + expect(shapeObj.strokeWidth).toBe(0); + expect(shapeObj.width).toBe(100); + expect(shapeObj.height).toBe(100); + }); + + it('When change() is called, the style of the rectagle object is changed.', () => { + shape.add('rect'); + + [shapeObj] = canvas.getObjects(); + + shape.change(shapeObj, { + fill: 'blue', + stroke: 'red', + width: 10, + height: 20, }); - it('When add() is called with no options, the default options set the rectangle object.', () => { - shape.add('rect'); + expect(shapeObj.fill).toBe('blue'); + expect(shapeObj.stroke).toBe('red'); + expect(shapeObj.width).toBe(10); + expect(shapeObj.height).toBe(20); + }); - [shapeObj] = canvas.getObjects(); + it('When change() is called, the style of the circle object is changed.', () => { + shape.add('circle'); - expect(shapeObj.width).toBe(1); // strokeWidth: 1, width: 1 - expect(shapeObj.height).toBe(1); // strokeWidth: 1, height: 1 + [shapeObj] = canvas.getObjects(); + + shape.change(shapeObj, { + fill: 'blue', + stroke: 'red', + rx: 10, + ry: 20, }); - it('When add() is called with no options, the default options set the circle object.', () => { - shape.add('circle'); + expect(shapeObj.fill).toBe('blue'); + expect(shapeObj.stroke).toBe('red'); + expect(shapeObj.width).toBe(20); + expect(shapeObj.height).toBe(40); + }); - [shapeObj] = canvas.getObjects(); + it('When change() is called, the style of the triangle object is changed.', () => { + shape.add('triangle'); + + [shapeObj] = canvas.getObjects(); - expect(shapeObj.width).toBe(0); - expect(shapeObj.height).toBe(0); + shape.change(shapeObj, { + width: 10, + height: 20, }); - it('When add() is called with no options, the default options set the triangle object.', () => { - shape.add('triangle'); + expect(shapeObj.fill).toBe('#ffffff'); + expect(shapeObj.stroke).toBe('#000000'); + expect(shapeObj.width).toBe(10); + expect(shapeObj.height).toBe(20); + }); - [shapeObj] = canvas.getObjects(); + describe('Fill - filter type', () => { + beforeEach((done) => { + const imageURL = 'base/test/fixtures/sampleImage.jpg'; - expect(shapeObj.width).toBe(1); // strokeWidth: 1, width: 1 - expect(shapeObj.height).toBe(1); // strokeWidth: 1, height: 1 - }); + getCachedCanvasImageElement(canvas, true); - it('When add() is called with the options, this options set the rectagle object.', () => { - const settings = { - fill: 'blue', - stroke: 'red', - strokeWidth: 10, - type: 'rect', - width: 100, - height: 100 - }; - - shape.add('rect', settings); + fabric.Image.fromURL(imageURL, (sampleImage) => { + graphics.setCanvasImage('', sampleImage); + shape.add('rect', { + strokeWidth: 0, + left: 20, + top: 30, + width: 100, + height: 80, + fill: { + type: 'filter', + filter: [{ pixelate: 20 }], + }, + }); [shapeObj] = canvas.getObjects(); - expect(shapeObj.fill).toBe('blue'); - expect(shapeObj.stroke).toBe('red'); - expect(shapeObj.strokeWidth).toBe(10); - expect(shapeObj.width).toBe(100); // width + storkeWidth - expect(shapeObj.height).toBe(100); // height + storkeWidth + done(); + }); }); - it('When add() is called with the options, this options set the circle object.', () => { - const settings = { - fill: 'blue', - stroke: 'red', - strokeWidth: 3, - type: 'circle', - rx: 100, - ry: 50 - }; - - shape.add('circle', settings); - [shapeObj] = canvas.getObjects(); - - expect(shapeObj.fill).toBe('blue'); - expect(shapeObj.stroke).toBe('red'); - expect(shapeObj.strokeWidth).toBe(3); - expect(shapeObj.width).toBe(200); // rx * 2 + stokeWidth - expect(shapeObj.height).toBe(100); // ry * 2 + stokeWidth + it('"_resetPositionFillFilter" should be executed when a movement, rotation, and scaling event of a filter type fill is applied.', () => { + spyOn(canvas, 'getPointer').and.returnValue({ + x: 10, + y: 10, + }); + spyOn(shape, '_resetPositionFillFilter'); + shapeObj.fire('moving'); + shapeObj.fire('rotating'); + shapeObj.fire('scaling'); + + expect(shape._resetPositionFillFilter.calls.count()).toBe(3); }); - it('When add() is called with the options, this options set the triangle object.', () => { - const settings = { - fill: 'blue', - stroke: 'red', - strokeWidth: 0, - type: 'triangle', - width: 100, - height: 100 - }; - - shape.add('triangle', settings); - [shapeObj] = canvas.getObjects(); + it('cropX and cropY values of the image filled with the shape background must be changed to match the canvas background exactly.', () => { + shape._resetPositionFillFilter(shapeObj); + const { cropX, cropY } = getFillImageFromShape(shapeObj); - expect(shapeObj.fill).toBe('blue'); - expect(shapeObj.stroke).toBe('red'); - expect(shapeObj.strokeWidth).toBe(0); - expect(shapeObj.width).toBe(100); - expect(shapeObj.height).toBe(100); + expect(cropX).toBe(-30); + expect(cropY).toBe(-10); }); - it('When change() is called, the style of the rectagle object is changed.', () => { - shape.add('rect'); - - [shapeObj] = canvas.getObjects(); - - shape.change(shapeObj, { - fill: 'blue', - stroke: 'red', - width: 10, - height: 20 - }); + it('The fill image should be the same size as the shape.', () => { + shape._resetPositionFillFilter(shapeObj); + const { width, height } = getFillImageFromShape(shapeObj); - expect(shapeObj.fill).toBe('blue'); - expect(shapeObj.stroke).toBe('red'); - expect(shapeObj.width).toBe(10); - expect(shapeObj.height).toBe(20); + expect(width).toBe(100); + expect(height).toBe(80); }); - it('When change() is called, the style of the circle object is changed.', () => { - shape.add('circle'); - - [shapeObj] = canvas.getObjects(); - - shape.change(shapeObj, { - fill: 'blue', - stroke: 'red', - rx: 10, - ry: 20 - }); + it('The rotated object fill image must be the same size as the rectangle that draws the rotated object border.', () => { + shapeObj.set({ + angle: 40, + }); + shape._resetPositionFillFilter(shapeObj); + const { width, height } = getFillImageFromShape(shapeObj); - expect(shapeObj.fill).toBe('blue'); - expect(shapeObj.stroke).toBe('red'); - expect(shapeObj.width).toBe(20); - expect(shapeObj.height).toBe(40); + expect(Math.round(width)).toBe(128); + expect(Math.round(height)).toBe(126); }); - it('When change() is called, the style of the triangle object is changed.', () => { - shape.add('triangle'); + it('If repositioning is performed while the angle is changed, the angle value of the fill image must have the shape reverse rotation value.', () => { + shapeObj.set({ + angle: 40, + }); + shape._resetPositionFillFilter(shapeObj); + const { angle } = getFillImageFromShape(shapeObj); - [shapeObj] = canvas.getObjects(); - - shape.change(shapeObj, { - width: 10, - height: 20 - }); - - expect(shapeObj.fill).toBe('#ffffff'); - expect(shapeObj.stroke).toBe('#000000'); - expect(shapeObj.width).toBe(10); - expect(shapeObj.height).toBe(20); + expect(angle).toBe(-40); }); - describe('Fill - filter type', () => { - beforeEach(done => { - const imageURL = 'base/test/fixtures/sampleImage.jpg'; - - getCachedCanvasImageElement(canvas, true); - - fabric.Image.fromURL(imageURL, sampleImage => { - graphics.setCanvasImage('', sampleImage); - shape.add('rect', { - strokeWidth: 0, - left: 20, - top: 30, - width: 100, - height: 80, - fill: { - type: 'filter', - filter: [{pixelate: 20}] - } - }); - [shapeObj] = canvas.getObjects(); - - done(); - }); - }); - - it('"_resetPositionFillFilter" should be executed when a movement, rotation, and scaling event of a filter type fill is applied.', () => { - spyOn(canvas, 'getPointer').and.returnValue({ - x: 10, - y: 10 - }); - spyOn(shape, '_resetPositionFillFilter'); - shapeObj.fire('moving'); - shapeObj.fire('rotating'); - shapeObj.fire('scaling'); - - expect(shape._resetPositionFillFilter.calls.count()).toBe(3); - }); - - it('cropX and cropY values of the image filled with the shape background must be changed to match the canvas background exactly.', () => { - shape._resetPositionFillFilter(shapeObj); - const {cropX, cropY} = getFillImageFromShape(shapeObj); - - expect(cropX).toBe(-30); - expect(cropY).toBe(-10); - }); - - it('The fill image should be the same size as the shape.', () => { - shape._resetPositionFillFilter(shapeObj); - const {width, height} = getFillImageFromShape(shapeObj); - - expect(width).toBe(100); - expect(height).toBe(80); - }); - - it('The rotated object fill image must be the same size as the rectangle that draws the rotated object border.', () => { - shapeObj.set({ - angle: 40 - }); - shape._resetPositionFillFilter(shapeObj); - const {width, height} = getFillImageFromShape(shapeObj); - - expect(Math.round(width)).toBe(128); - expect(Math.round(height)).toBe(126); - }); - - it('If repositioning is performed while the angle is changed, the angle value of the fill image must have the shape reverse rotation value.', () => { - shapeObj.set({ - angle: 40 - }); - shape._resetPositionFillFilter(shapeObj); - const {angle} = getFillImageFromShape(shapeObj); - - expect(angle).toBe(-40); - }); - - it('For shapes that go outside the bottom right area of the canvas, the size and position of the image position should give the expected result.', done => { - shape.add('rect', { - strokeWidth: 0, - left: 250, - top: 100, - width: 200, - height: 200, - fill: { - type: 'filter', - filter: [{pixelate: 20}] - } - }).then(props => { - shapeObj = graphics.getObject(props.id); - const fillImage = getFillImageFromShape(shapeObj); - const {top, height, left, width} = fillImage; - expect(top).toBe(75); - expect(left).toBe(75); - expect(height).toBe(150); - expect(width).toBe(150); - - done(); - }); - }); - - it('For shapes that go outside the top left area of the canvas, the size and position of the image position should give the expected result.', done => { - shape.add('rect', { - strokeWidth: 0, - left: 50, - top: 30, - width: 200, - height: 70, - fill: { - type: 'filter', - filter: [{pixelate: 20}] - } - }).then(props => { - shapeObj = graphics.getObject(props.id); - const fillImage = getFillImageFromShape(shapeObj); - const {top, height, left, width} = fillImage; - expect(Math.round(top)).toBe(40); - expect(left).toBe(150); - expect(height).toBe(70); - expect(width).toBe(200); - - done(); - }); - }); - - it('Background image of the shape to which the filter fill is applied must have the filter applied.', () => { - const fillImage = getFillImageFromShape(shapeObj); - - expect(fillImage.filters.length).toBeGreaterThan(0); + it('For shapes that go outside the bottom right area of the canvas, the size and position of the image position should give the expected result.', (done) => { + shape + .add('rect', { + strokeWidth: 0, + left: 250, + top: 100, + width: 200, + height: 200, + fill: { + type: 'filter', + filter: [{ pixelate: 20 }], + }, + }) + .then((props) => { + shapeObj = graphics.getObject(props.id); + const fillImage = getFillImageFromShape(shapeObj); + const { top, height, left, width } = fillImage; + expect(top).toBe(75); + expect(left).toBe(75); + expect(height).toBe(150); + expect(width).toBe(150); + + done(); }); }); - describe('_onFabricMouseMove()', () => { - beforeEach(() => { - shape.add('rect', { - left: 100, - top: 100 - }); - - [shapeObj] = canvas.getObjects(); - shape._shapeObj = shapeObj; - }); - - it('When the mouse direction is in 1th quadrant,' + - 'the origin values of shape set to "left" and "top".', () => { - spyOn(canvas, 'getPointer').and.returnValue({ - x: 200, - y: 120 - }); - - shape._onFabricMouseMove(fEvent); - - expect(shapeObj.originX).toBe('left'); - expect(shapeObj.originY).toBe('top'); + it('For shapes that go outside the top left area of the canvas, the size and position of the image position should give the expected result.', (done) => { + shape + .add('rect', { + strokeWidth: 0, + left: 50, + top: 30, + width: 200, + height: 70, + fill: { + type: 'filter', + filter: [{ pixelate: 20 }], + }, + }) + .then((props) => { + shapeObj = graphics.getObject(props.id); + const fillImage = getFillImageFromShape(shapeObj); + const { top, height, left, width } = fillImage; + expect(Math.round(top)).toBe(40); + expect(left).toBe(150); + expect(height).toBe(70); + expect(width).toBe(200); + + done(); }); + }); - it('When the mouse direction is in 2th quadrant,' + - 'the origin values of shape set to "right" and "top".', () => { - spyOn(canvas, 'getPointer').and.returnValue({ - x: 80, - y: 100 - }); - - shape._onFabricMouseMove(fEvent); + it('Background image of the shape to which the filter fill is applied must have the filter applied.', () => { + const fillImage = getFillImageFromShape(shapeObj); - expect(shapeObj.originX).toBe('right'); - expect(shapeObj.originY).toBe('top'); - }); + expect(fillImage.filters.length).toBeGreaterThan(0); + }); + }); - it('When the mouse direction is in 3th quadrant,' + - 'the origin values of shape set to "right" and "bottom".', () => { - spyOn(canvas, 'getPointer').and.returnValue({ - x: 80, - y: 80 - }); + describe('_onFabricMouseMove()', () => { + beforeEach(() => { + shape.add('rect', { + left: 100, + top: 100, + }); - shape._onFabricMouseMove(fEvent); + [shapeObj] = canvas.getObjects(); + shape._shapeObj = shapeObj; + }); - expect(shapeObj.originX).toBe('right'); - expect(shapeObj.originY).toBe('bottom'); + it( + 'When the mouse direction is in 1th quadrant,' + + 'the origin values of shape set to "left" and "top".', + () => { + spyOn(canvas, 'getPointer').and.returnValue({ + x: 200, + y: 120, }); - it('When the mouse direction is in 4th quadrant,' + - 'the origin values of shape set to "left" and "bottom".', () => { - spyOn(canvas, 'getPointer').and.returnValue({ - x: 200, - y: 80 - }); + shape._onFabricMouseMove(fEvent); - shape._onFabricMouseMove(fEvent); + expect(shapeObj.originX).toBe('left'); + expect(shapeObj.originY).toBe('top'); + } + ); - expect(shapeObj.originX).toBe('left'); - expect(shapeObj.originY).toBe('bottom'); + it( + 'When the mouse direction is in 2th quadrant,' + + 'the origin values of shape set to "right" and "top".', + () => { + spyOn(canvas, 'getPointer').and.returnValue({ + x: 80, + y: 100, }); - }); - describe('_onFabricMouseUp()', () => { - let startPoint, expectedPoint; + shape._onFabricMouseMove(fEvent); - beforeEach(() => { - shape.add('circle', { - left: 100, - top: 100 - }); + expect(shapeObj.originX).toBe('right'); + expect(shapeObj.originY).toBe('top'); + } + ); - [shapeObj] = canvas.getObjects(); - shape._shapeObj = shapeObj; + it( + 'When the mouse direction is in 3th quadrant,' + + 'the origin values of shape set to "right" and "bottom".', + () => { + spyOn(canvas, 'getPointer').and.returnValue({ + x: 80, + y: 80, }); - it('When the drawing shape is in 1th quadrant, "left" and "top" are the same as start point.', () => { - spyOn(canvas, 'getPointer').and.returnValue({ - x: 200, - y: 120 - }); - - startPoint = shapeObj.getPointByOrigin('left', 'top'); - - shape._onFabricMouseMove(fEvent); - shape._onFabricMouseUp(); + shape._onFabricMouseMove(fEvent); - expectedPoint = shapeObj.getPointByOrigin('left', 'top'); + expect(shapeObj.originX).toBe('right'); + expect(shapeObj.originY).toBe('bottom'); + } + ); - expect(expectedPoint.x).toBe(startPoint.x); - expect(expectedPoint.y).toBe(startPoint.y); + it( + 'When the mouse direction is in 4th quadrant,' + + 'the origin values of shape set to "left" and "bottom".', + () => { + spyOn(canvas, 'getPointer').and.returnValue({ + x: 200, + y: 80, }); - it('When the drawing shape is in 2th quadrant, "right" and "top" are the same as start point.', () => { - spyOn(canvas, 'getPointer').and.returnValue({ - x: 80, - y: 120 - }); + shape._onFabricMouseMove(fEvent); - startPoint = shapeObj.getPointByOrigin('right', 'top'); + expect(shapeObj.originX).toBe('left'); + expect(shapeObj.originY).toBe('bottom'); + } + ); + }); - shape._onFabricMouseMove(fEvent); - shape._onFabricMouseUp(); + describe('_onFabricMouseUp()', () => { + let startPoint, expectedPoint; - expectedPoint = shapeObj.getPointByOrigin('right', 'top'); + beforeEach(() => { + shape.add('circle', { + left: 100, + top: 100, + }); - expect(expectedPoint.x).toBe(startPoint.x); - expect(expectedPoint.y).toBe(startPoint.y); - }); + [shapeObj] = canvas.getObjects(); + shape._shapeObj = shapeObj; + }); - it('When the drawing shape is in 3th quadrant, "right" and "bottom" are the same as start point.', () => { - spyOn(canvas, 'getPointer').and.returnValue({ - x: 80, - y: 80 - }); + it('When the drawing shape is in 1th quadrant, "left" and "top" are the same as start point.', () => { + spyOn(canvas, 'getPointer').and.returnValue({ + x: 200, + y: 120, + }); - startPoint = shapeObj.getPointByOrigin('right', 'bottom'); + startPoint = shapeObj.getPointByOrigin('left', 'top'); - shape._onFabricMouseMove(fEvent); - shape._onFabricMouseUp(); + shape._onFabricMouseMove(fEvent); + shape._onFabricMouseUp(); - expectedPoint = shapeObj.getPointByOrigin('right', 'bottom'); + expectedPoint = shapeObj.getPointByOrigin('left', 'top'); - expect(expectedPoint.x).toBe(startPoint.x); - expect(expectedPoint.y).toBe(startPoint.y); - }); + expect(expectedPoint.x).toBe(startPoint.x); + expect(expectedPoint.y).toBe(startPoint.y); + }); - it('When the drawing shape is in 4th quadrant, "left" and "bottom" are the same as start point.', () => { - spyOn(canvas, 'getPointer').and.returnValue({ - x: 120, - y: 80 - }); + it('When the drawing shape is in 2th quadrant, "right" and "top" are the same as start point.', () => { + spyOn(canvas, 'getPointer').and.returnValue({ + x: 80, + y: 120, + }); - startPoint = shapeObj.getPointByOrigin('left', 'bottom'); + startPoint = shapeObj.getPointByOrigin('right', 'top'); - shape._onFabricMouseMove(fEvent); - shape._onFabricMouseUp(); + shape._onFabricMouseMove(fEvent); + shape._onFabricMouseUp(); - expectedPoint = shapeObj.getPointByOrigin('left', 'bottom'); + expectedPoint = shapeObj.getPointByOrigin('right', 'top'); - expect(expectedPoint.x).toBe(startPoint.x); - expect(expectedPoint.y).toBe(startPoint.y); - }); + expect(expectedPoint.x).toBe(startPoint.x); + expect(expectedPoint.y).toBe(startPoint.y); }); - it('When drawing the shape with mouse and the "isRegular" option set to true, ' + - 'the created rectangle shape has the same "width" and "height" values.', () => { - shape.add('rect', { - left: 0, - top: 0 - }); + it('When the drawing shape is in 3th quadrant, "right" and "bottom" are the same as start point.', () => { + spyOn(canvas, 'getPointer').and.returnValue({ + x: 80, + y: 80, + }); - shape._withShiftKey = true; - [shapeObj] = canvas.getObjects(); - shape._shapeObj = shapeObj; + startPoint = shapeObj.getPointByOrigin('right', 'bottom'); - spyOn(canvas, 'getPointer').and.returnValue({ - x: 200, - y: 100 - }); + shape._onFabricMouseMove(fEvent); + shape._onFabricMouseUp(); - shape._onFabricMouseMove(fEvent); - shape._onFabricMouseUp(); + expectedPoint = shapeObj.getPointByOrigin('right', 'bottom'); - expect(shapeObj.width).toBe(200); // has 1 storkeWidth - expect(shapeObj.height).toBe(200); // has 1 storkeWidth + expect(expectedPoint.x).toBe(startPoint.x); + expect(expectedPoint.y).toBe(startPoint.y); }); - it('When drawing the shape with mouse and the "isRegular" option set to true, ' + - 'the created rectangle shape has the same "width" and "height" values.', () => { - shape.add('rect', { - left: 0, - top: 0 - }); + it('When the drawing shape is in 4th quadrant, "left" and "bottom" are the same as start point.', () => { + spyOn(canvas, 'getPointer').and.returnValue({ + x: 120, + y: 80, + }); - shape._withShiftKey = true; - [shapeObj] = canvas.getObjects(); - shape._shapeObj = shapeObj; + startPoint = shapeObj.getPointByOrigin('left', 'bottom'); - spyOn(canvas, 'getPointer').and.returnValue({ - x: 100, - y: 200 - }); + shape._onFabricMouseMove(fEvent); + shape._onFabricMouseUp(); - shape._onFabricMouseMove(fEvent); - shape._onFabricMouseUp(); + expectedPoint = shapeObj.getPointByOrigin('left', 'bottom'); - expect(shapeObj.width).toBe(200); // has 1 storkeWidth - expect(shapeObj.height).toBe(200); // has 1 storkeWidth + expect(expectedPoint.x).toBe(startPoint.x); + expect(expectedPoint.y).toBe(startPoint.y); }); + }); + + it( + 'When drawing the shape with mouse and the "isRegular" option set to true, ' + + 'the created rectangle shape has the same "width" and "height" values.', + () => { + shape.add('rect', { + left: 0, + top: 0, + }); + + shape._withShiftKey = true; + [shapeObj] = canvas.getObjects(); + shape._shapeObj = shapeObj; + + spyOn(canvas, 'getPointer').and.returnValue({ + x: 200, + y: 100, + }); + + shape._onFabricMouseMove(fEvent); + shape._onFabricMouseUp(); + + expect(shapeObj.width).toBe(200); // has 1 storkeWidth + expect(shapeObj.height).toBe(200); // has 1 storkeWidth + } + ); + + it( + 'When drawing the shape with mouse and the "isRegular" option set to true, ' + + 'the created rectangle shape has the same "width" and "height" values.', + () => { + shape.add('rect', { + left: 0, + top: 0, + }); + + shape._withShiftKey = true; + [shapeObj] = canvas.getObjects(); + shape._shapeObj = shapeObj; + + spyOn(canvas, 'getPointer').and.returnValue({ + x: 100, + y: 200, + }); + + shape._onFabricMouseMove(fEvent); + shape._onFabricMouseUp(); + + expect(shapeObj.width).toBe(200); // has 1 storkeWidth + expect(shapeObj.height).toBe(200); // has 1 storkeWidth + } + ); }); diff --git a/test/text.spec.js b/test/text.spec.js index 27318b9b0..b44286265 100644 --- a/test/text.spec.js +++ b/test/text.spec.js @@ -8,147 +8,147 @@ import Graphics from '../src/js/graphics'; import Text from '../src/js/component/text'; describe('Text', () => { - let canvas, graphics, mockImage, text; - - beforeAll(() => { - graphics = new Graphics($('')[0]); - canvas = graphics.getCanvas(); - text = new Text(graphics); + let canvas, graphics, mockImage, text; + + beforeAll(() => { + graphics = new Graphics($('')[0]); + canvas = graphics.getCanvas(); + text = new Text(graphics); + }); + + beforeEach(() => { + mockImage = new fabric.Image(); + graphics.setCanvasImage('mockImage', mockImage); + }); + + afterEach(() => { + canvas.forEachObject((obj) => { + canvas.remove(obj); }); + }); + + describe('add()', () => { + let activeObj; beforeEach(() => { - mockImage = new fabric.Image(); - graphics.setCanvasImage('mockImage', mockImage); - }); + text.add('', {}); - afterEach(() => { - canvas.forEachObject(obj => { - canvas.remove(obj); - }); + activeObj = canvas.getActiveObject(); }); - describe('add()', () => { - let activeObj; - - beforeEach(() => { - text.add('', {}); + it('should make the blank text object when text parameter is empty string.', () => { + const newText = activeObj.text; - activeObj = canvas.getActiveObject(); - }); + expect(newText).toEqual(''); + }); - it('should make the blank text object when text parameter is empty string.', () => { - const newText = activeObj.text; + it('should make the text object set default option when parameter has not "styles" property.', () => { + const newTextStyle = activeObj.fontWeight; - expect(newText).toEqual(''); - }); + expect(newTextStyle).toEqual('normal'); + }); - it('should make the text object set default option when parameter has not "styles" property.', () => { - const newTextStyle = activeObj.fontWeight; + it('should create the text object on center of canvas when parameter has not "position" property.', () => { + const mockImagePos = mockImage.getCenterPoint(); - expect(newTextStyle).toEqual('normal'); - }); + expect(activeObj.left).toEqual(mockImagePos.x); + expect(activeObj.top).toEqual(mockImagePos.y); + }); - it('should create the text object on center of canvas when parameter has not "position" property.', () => { - const mockImagePos = mockImage.getCenterPoint(); + it('Default option for autofocus should be true when adding text.', (done) => { + text.add('default', {}).then((info) => { + const newText = graphics.getObject(info.id); - expect(activeObj.left).toEqual(mockImagePos.x); - expect(activeObj.top).toEqual(mockImagePos.y); - }); + expect(newText.selectionStart).toBe(0); + expect(newText.selectionEnd).toBe(7); + expect(newText.isEditing).toBe(true); - it('Default option for autofocus should be true when adding text.', done => { - text.add('default', {}).then(info => { - const newText = graphics.getObject(info.id); + done(); + }); + }); + }); + + it('Rotated text elements must also maintain consistent left and top positions after entering and exiting drawing mode.', () => { + const left = 10; + const top = 20; + const newText = new fabric.IText('testString', { + left, + top, + width: 30, + height: 50, + angle: 40, + originX: 'center', + originY: 'center', + }); + text.useItext = true; + canvas.add(newText); - expect(newText.selectionStart).toBe(0); - expect(newText.selectionEnd).toBe(7); - expect(newText.isEditing).toBe(true); + text.start(); + text.end(); - done(); - }); - }); - }); + expect(newText.left).toEqual(left); + expect(newText.top).toEqual(top); + }); - it('Rotated text elements must also maintain consistent left and top positions after entering and exiting drawing mode.', () => { - const left = 10; - const top = 20; - const newText = new fabric.IText('testString', { - left, - top, - width: 30, - height: 50, - angle: 40, - originX: 'center', - originY: 'center' - }); - text.useItext = true; - canvas.add(newText); - - text.start(); - text.end(); - - expect(newText.left).toEqual(left); - expect(newText.top).toEqual(top); - }); + it('change() should change contents in the text object as input.', () => { + text.add('text123', {}); - it('change() should change contents in the text object as input.', () => { - text.add('text123', {}); + const activeObj = canvas.getActiveObject(); - const activeObj = canvas.getActiveObject(); + text.change(activeObj, 'abc'); - text.change(activeObj, 'abc'); + expect(activeObj.text).toEqual('abc'); - expect(activeObj.text).toEqual('abc'); + text.change(activeObj, 'def'); - text.change(activeObj, 'def'); + expect(activeObj.text).toEqual('def'); + }); - expect(activeObj.text).toEqual('def'); + describe('setStyle()', () => { + beforeEach(() => { + text.add('new text', { + styles: { + fontWeight: 'bold', + }, + }); }); - describe('setStyle()', () => { - beforeEach(() => { - text.add('new text', { - styles: { - fontWeight: 'bold' - } - }); - }); + it('should unlock style when a selected style already apply on the activated text object.', () => { + const activeObj = canvas.getActiveObject(); - it('should unlock style when a selected style already apply on the activated text object.', () => { - const activeObj = canvas.getActiveObject(); + text.setStyle(activeObj, { + fontWeight: 'bold', + }); - text.setStyle(activeObj, { - fontWeight: 'bold' - }); - - expect(activeObj.fontWeight).not.toEqual('bold'); - }); + expect(activeObj.fontWeight).not.toEqual('bold'); + }); - it('should apply style when the activated text object has not a selected style.', () => { - const activeObj = canvas.getActiveObject(); + it('should apply style when the activated text object has not a selected style.', () => { + const activeObj = canvas.getActiveObject(); - text.setStyle(activeObj, { - fontStyle: 'italic' - }); + text.setStyle(activeObj, { + fontStyle: 'italic', + }); - expect(activeObj.fontStyle).toEqual('italic'); - }); + expect(activeObj.fontStyle).toEqual('italic'); }); + }); - it('_onFabricScaling() should change size of selected text object.', () => { - const obj = new fabric.Text('test'); - const mock = { - target: obj - }; - const scale = 10; - const originSize = obj.fontSize; + it('_onFabricScaling() should change size of selected text object.', () => { + const obj = new fabric.Text('test'); + const mock = { + target: obj, + }; + const scale = 10; + const originSize = obj.fontSize; - text.start({}); + text.start({}); - canvas.add(obj); - obj.scaleY = scale; + canvas.add(obj); + obj.scaleY = scale; - canvas.fire('object:scaling', mock); + canvas.fire('object:scaling', mock); - expect(obj.fontSize).toEqual(originSize * scale); - }); + expect(obj.fontSize).toEqual(originSize * scale); + }); }); diff --git a/test/theme.spec.js b/test/theme.spec.js index aa5e192a8..d9cce52bf 100644 --- a/test/theme.spec.js +++ b/test/theme.spec.js @@ -7,95 +7,104 @@ import Theme from '../src/js/ui/theme/theme'; import defaultTheme from '../src/js/ui/theme/standard'; describe('Theme', () => { - let theme; - beforeEach(() => { - theme = new Theme(defaultTheme); - }); - describe('getStyle()', () => { - - it('When the user sets the icon file location, the path and name information must be included.', () => { - const addUserIconPath = 'base/test/fixtures/icon-d.svg'; - const addUserIconName = 'icon-d'; - const themeForIconPathSet = new Theme(snippet.extend({}, defaultTheme, { - 'menu.normalIcon.path': addUserIconPath, - 'menu.normalIcon.name': addUserIconName - })); - const {normal: {path, name}} = themeForIconPathSet.getStyle('menu.icon'); + let theme; + beforeEach(() => { + theme = new Theme(defaultTheme); + }); + describe('getStyle()', () => { + it('When the user sets the icon file location, the path and name information must be included.', () => { + const addUserIconPath = 'base/test/fixtures/icon-d.svg'; + const addUserIconName = 'icon-d'; + const themeForIconPathSet = new Theme( + snippet.extend({}, defaultTheme, { + 'menu.normalIcon.path': addUserIconPath, + 'menu.normalIcon.name': addUserIconName, + }) + ); + const { + normal: { path, name }, + } = themeForIconPathSet.getStyle('menu.icon'); - expect(path).toEqual('base/test/fixtures/icon-d.svg'); - expect(name).toEqual('icon-d'); - }); + expect(path).toEqual('base/test/fixtures/icon-d.svg'); + expect(name).toEqual('icon-d'); + }); - it('should return default icon color information.', () => { - const {normal, active, disabled, hover} = theme.getStyle('menu.icon'); + it('should return default icon color information.', () => { + const { normal, active, disabled, hover } = theme.getStyle('menu.icon'); - expect(normal.color).toEqual('#8a8a8a'); - expect(active.color).toEqual('#555555'); - expect(disabled.color).toEqual('#434343'); - expect(hover.color).toEqual('#e9e9e9'); - }); + expect(normal.color).toEqual('#8a8a8a'); + expect(active.color).toEqual('#555555'); + expect(disabled.color).toEqual('#434343'); + expect(hover.color).toEqual('#e9e9e9'); + }); - it('In normal types, cssText should be returned.', () => { - theme.styles.normal = { - backgroundColor: '#fdba3b', - border: '1px solid #fdba3b', - color: '#fff', - fontFamily: 'NotoSans, sans-serif', - fontSize: '12px' - }; + it('In normal types, cssText should be returned.', () => { + theme.styles.normal = { + backgroundColor: '#fdba3b', + border: '1px solid #fdba3b', + color: '#fff', + fontFamily: 'NotoSans, sans-serif', + fontSize: '12px', + }; - const expected = 'background-color: #fdba3b;border: 1px solid #fdba3b;color: #fff;font-family: NotoSans, sans-serif;font-size: 12px'; - expect(theme.getStyle('normal')).toBe(expected); - }); + const expected = + 'background-color: #fdba3b;border: 1px solid #fdba3b;color: #fff;font-family: NotoSans, sans-serif;font-size: 12px'; + expect(theme.getStyle('normal')).toBe(expected); + }); - it('If all members are objects, you must leave the structure intact and return cssText.', () => { - theme.styles['submenu.normalLabel'] = { - color: '#858585', - fontWeight: 'normal' - }; - theme.styles['submenu.activeLabel'] = { - color: '#000', - fontWeight: 'normal' - }; + it('If all members are objects, you must leave the structure intact and return cssText.', () => { + theme.styles['submenu.normalLabel'] = { + color: '#858585', + fontWeight: 'normal', + }; + theme.styles['submenu.activeLabel'] = { + color: '#000', + fontWeight: 'normal', + }; - const expected = { - normal: 'color: #858585;font-weight: normal', - active: 'color: #000;font-weight: normal' - }; - expect(theme.getStyle('submenu.label')).toEqual(expected); - }); + const expected = { + normal: 'color: #858585;font-weight: normal', + active: 'color: #000;font-weight: normal', + }; + expect(theme.getStyle('submenu.label')).toEqual(expected); }); + }); - describe('_makeCssText()', () => { - it('Should return the cssText of the expected value for the object.', () => { - const styleObject = { - backgroundColor: '#fff', - backgroundImage: './img/bg.png', - border: '1px solid #ddd', - color: '#222', - fontFamily: 'NotoSans, sans-serif', - fontSize: '12px' - }; - const expected = 'background-color: #fff;background-image: url(./img/bg.png);border: 1px solid #ddd;color: #222;font-family: NotoSans, sans-serif;font-size: 12px'; - expect(theme._makeCssText(styleObject)).toBe(expected); - }); + describe('_makeCssText()', () => { + it('Should return the cssText of the expected value for the object.', () => { + const styleObject = { + backgroundColor: '#fff', + backgroundImage: './img/bg.png', + border: '1px solid #ddd', + color: '#222', + fontFamily: 'NotoSans, sans-serif', + fontSize: '12px', + }; + const expected = + 'background-color: #fff;background-image: url(./img/bg.png);border: 1px solid #ddd;color: #222;font-family: NotoSans, sans-serif;font-size: 12px'; + expect(theme._makeCssText(styleObject)).toBe(expected); }); + }); - describe('_makeSvgItem()', () => { - it('When using the default icon, a svg set with the path prefix and no use-default class should be created.', () => { - const useTagString = theme._makeSvgItem(['normal'], 'crop'); + describe('_makeSvgItem()', () => { + it('When using the default icon, a svg set with the path prefix and no use-default class should be created.', () => { + const useTagString = theme._makeSvgItem(['normal'], 'crop'); - expect(useTagString).toBe(''); - }); + expect(useTagString).toBe(''); + }); - it('Setting the icon file should create a svg path with the prefix.', () => { - const themeForIconPathSet = new Theme(snippet.extend({}, defaultTheme, { - 'menu.normalIcon.path': 'base/test/fixtures/icon-d.svg', - 'menu.normalIcon.name': 'icon-d' - })); - const useTagString = themeForIconPathSet._makeSvgItem(['normal'], 'crop'); + it('Setting the icon file should create a svg path with the prefix.', () => { + const themeForIconPathSet = new Theme( + snippet.extend({}, defaultTheme, { + 'menu.normalIcon.path': 'base/test/fixtures/icon-d.svg', + 'menu.normalIcon.name': 'icon-d', + }) + ); + const useTagString = themeForIconPathSet._makeSvgItem(['normal'], 'crop'); - expect(useTagString).toBe(''); - }); + expect(useTagString).toBe( + '' + ); }); + }); }); diff --git a/test/types/tsconfig.json b/test/types/tsconfig.json index 3b93829ac..143948099 100644 --- a/test/types/tsconfig.json +++ b/test/types/tsconfig.json @@ -1,10 +1,7 @@ { - "compilerOptions": { - "noEmit": true, - "noImplicitAny": false - }, - "include": [ - "../../index.d.ts", - "./type-tests.ts" - ] + "compilerOptions": { + "noEmit": true, + "noImplicitAny": false + }, + "include": ["../../index.d.ts", "./type-tests.ts"] } diff --git a/test/types/type-tests.ts b/test/types/type-tests.ts index c6c921cc8..ab588ba02 100644 --- a/test/types/type-tests.ts +++ b/test/types/type-tests.ts @@ -1,172 +1,180 @@ import ImageEditor = require('tui-image-editor'); const blackTheme = { - 'common.bi.image': 'https://uicdn.toast.com/toastui/img/tui-image-editor-bi.png', - 'common.bisize.width': '251px', - 'common.bisize.height': '21px', - 'common.backgroundImage': 'none', - 'common.backgroundColor': '#1e1e1e', - 'common.border': '0px', + 'common.bi.image': 'https://uicdn.toast.com/toastui/img/tui-image-editor-bi.png', + 'common.bisize.width': '251px', + 'common.bisize.height': '21px', + 'common.backgroundImage': 'none', + 'common.backgroundColor': '#1e1e1e', + 'common.border': '0px', - // header - 'header.backgroundImage': 'none', - 'header.backgroundColor': 'transparent', - 'header.border': '0px', + // header + 'header.backgroundImage': 'none', + 'header.backgroundColor': 'transparent', + 'header.border': '0px', - // load button - 'loadButton.backgroundColor': '#fff', - 'loadButton.border': '1px solid #ddd', - 'loadButton.color': '#222', - 'loadButton.fontFamily': 'NotoSans, sans-serif', - 'loadButton.fontSize': '12px', + // load button + 'loadButton.backgroundColor': '#fff', + 'loadButton.border': '1px solid #ddd', + 'loadButton.color': '#222', + 'loadButton.fontFamily': 'NotoSans, sans-serif', + 'loadButton.fontSize': '12px', - // download button - 'downloadButton.backgroundColor': '#fdba3b', - 'downloadButton.border': '1px solid #fdba3b', - 'downloadButton.color': '#fff', - 'downloadButton.fontFamily': 'NotoSans, sans-serif', - 'downloadButton.fontSize': '12px', + // download button + 'downloadButton.backgroundColor': '#fdba3b', + 'downloadButton.border': '1px solid #fdba3b', + 'downloadButton.color': '#fff', + 'downloadButton.fontFamily': 'NotoSans, sans-serif', + 'downloadButton.fontSize': '12px', - // main icons - 'menu.normalIcon.path': '../dist/svg/icon-b.svg', - 'menu.normalIcon.name': 'icon-b', - 'menu.activeIcon.path': '../dist/svg/icon-a.svg', - 'menu.activeIcon.name': 'icon-a', - 'menu.iconSize.width': '24px', - 'menu.iconSize.height': '24px', + // main icons + 'menu.normalIcon.path': '../dist/svg/icon-b.svg', + 'menu.normalIcon.name': 'icon-b', + 'menu.activeIcon.path': '../dist/svg/icon-a.svg', + 'menu.activeIcon.name': 'icon-a', + 'menu.iconSize.width': '24px', + 'menu.iconSize.height': '24px', - // submenu primary color - 'submenu.backgroundColor': '#1e1e1e', - 'submenu.partition.color': '#858585', + // submenu primary color + 'submenu.backgroundColor': '#1e1e1e', + 'submenu.partition.color': '#858585', - // submenu icons - 'submenu.normalIcon.path': '../dist/svg/icon-a.svg', - 'submenu.normalIcon.name': 'icon-a', - 'submenu.activeIcon.path': '../dist/svg/icon-c.svg', - 'submenu.activeIcon.name': 'icon-c', - 'submenu.iconSize.width': '32px', - 'submenu.iconSize.height': '32px', + // submenu icons + 'submenu.normalIcon.path': '../dist/svg/icon-a.svg', + 'submenu.normalIcon.name': 'icon-a', + 'submenu.activeIcon.path': '../dist/svg/icon-c.svg', + 'submenu.activeIcon.name': 'icon-c', + 'submenu.iconSize.width': '32px', + 'submenu.iconSize.height': '32px', - // submenu labels - 'submenu.normalLabel.color': '#858585', - 'submenu.normalLabel.fontWeight': 'lighter', - 'submenu.activeLabel.color': '#fff', - 'submenu.activeLabel.fontWeight': 'lighter', + // submenu labels + 'submenu.normalLabel.color': '#858585', + 'submenu.normalLabel.fontWeight': 'lighter', + 'submenu.activeLabel.color': '#fff', + 'submenu.activeLabel.fontWeight': 'lighter', - // checkbox style - 'checkbox.border': '1px solid #ccc', - 'checkbox.backgroundColor': '#fff', + // checkbox style + 'checkbox.border': '1px solid #ccc', + 'checkbox.backgroundColor': '#fff', - // rango style - 'range.pointer.color': '#fff', - 'range.bar.color': '#666', - 'range.subbar.color': '#d1d1d1', - 'range.value.color': '#fff', - 'range.value.fontWeight': 'lighter', - 'range.value.fontSize': '11px', - 'range.value.border': '1px solid #353535', - 'range.value.backgroundColor': '#151515', - 'range.title.color': '#fff', - 'range.title.fontWeight': 'lighter', + // rango style + 'range.pointer.color': '#fff', + 'range.bar.color': '#666', + 'range.subbar.color': '#d1d1d1', + 'range.value.color': '#fff', + 'range.value.fontWeight': 'lighter', + 'range.value.fontSize': '11px', + 'range.value.border': '1px solid #353535', + 'range.value.backgroundColor': '#151515', + 'range.title.color': '#fff', + 'range.title.fontWeight': 'lighter', - // colorpicker style - 'colorpicker.button.border': '1px solid #1e1e1e', - 'colorpicker.title.color': '#fff' + // colorpicker style + 'colorpicker.button.border': '1px solid #1e1e1e', + 'colorpicker.title.color': '#fff', }; const imageEditor = new ImageEditor('#container', { - includeUI: { - loadImage: { - path: 'img/sampleImage.jpg', - name: 'SampleImage' - }, - theme: blackTheme, - menu: ['shape', 'filter'], - initMenu: 'filter', - uiSize: { - width: '1000px', - height: '700px' - }, - menuBarPosition: 'bottom' + includeUI: { + loadImage: { + path: 'img/sampleImage.jpg', + name: 'SampleImage', }, - cssMaxWidth: 700, - cssMaxHeight: 500, - selectionStyle: { - cornerSize: 20, - rotatingPointOffset: 70 - } + theme: blackTheme, + menu: ['shape', 'filter'], + initMenu: 'filter', + uiSize: { + width: '1000px', + height: '700px', + }, + menuBarPosition: 'bottom', + }, + cssMaxWidth: 700, + cssMaxHeight: 500, + selectionStyle: { + cornerSize: 20, + rotatingPointOffset: 70, + }, }); imageEditor.addIcon('arrow'); -imageEditor.addIcon('cancel', { +imageEditor + .addIcon('cancel', { left: 100, - top: 100 -}).then(objectProps => { + top: 100, + }) + .then((objectProps) => { console.log(objectProps.id); -}); + }); -imageEditor.addImageObject('path/fileName.jpg').then(objectProps => { - console.log(objectProps); +imageEditor.addImageObject('path/fileName.jpg').then((objectProps) => { + console.log(objectProps); }); imageEditor.addShape('rect', { - fill: 'red', - stroke: 'blue', - strokeWidth: 3, - width: 100, - height: 200, - left: 10, - top: 10, - isRegular: true + fill: 'red', + stroke: 'blue', + strokeWidth: 3, + width: 100, + height: 200, + left: 10, + top: 10, + isRegular: true, }); -imageEditor.addShape('circle', { +imageEditor + .addShape('circle', { fill: 'red', stroke: 'blue', strokeWidth: 3, rx: 10, ry: 100, - isRegular: false -}).then(objectProps => { + isRegular: false, + }) + .then((objectProps) => { console.log(objectProps.id); -}); + }); -imageEditor.addText('initText', { +imageEditor + .addText('initText', { styles: { - fill: '#000', - fontSize: 20, - fontWeight: 'bold' + fill: '#000', + fontSize: 20, + fontWeight: 'bold', }, position: { - x: 10, - y: 10 - } -}).then(objectProps => { + x: 10, + y: 10, + }, + }) + .then((objectProps) => { console.log(objectProps.id); -}); + }); imageEditor.applyFilter('Grayscale'); -imageEditor.applyFilter('mask', { - maskObjId: 0 -}).then(obj => { +imageEditor + .applyFilter('mask', { + maskObjId: 0, + }) + .then((obj) => { console.log(`filterType: ${obj.type}`); console.log(`actType: ${obj.action}`); -}); + }); imageEditor.changeCursor('crosshair'); imageEditor.changeIconColor(0, '#000000'); imageEditor.changeSelectableAll(false); imageEditor.changeShape(0, { - fill: 'red', - stroke: 'blue', - strokeWidth: 3, - rx: 10, - ry: 100 + fill: 'red', + stroke: 'blue', + strokeWidth: 3, + rx: 10, + ry: 100, }); imageEditor.changeText(0, 'change text'); imageEditor.changeTextStyle(0, { - fontStyle: 'italic' + fontStyle: 'italic', }); imageEditor.clearObjects(); @@ -177,13 +185,16 @@ imageEditor.crop(imageEditor.getCropzoneRect()); imageEditor.deactivateAll(); imageEditor.destroy(); imageEditor.discardSelection(); -imageEditor.flipX().then(status => { +imageEditor + .flipX() + .then((status) => { console.log(`flipX: ${status.flipX}`); console.log(`flipY: ${status.flipY}`); console.log(`angle: ${status.angle}`); -}).catch(message => { + }) + .catch((message) => { console.log(`error: ${message}`); -}); + }); imageEditor.flipY(); imageEditor.getCanvasSize(); imageEditor.getCropzoneRect(); @@ -193,107 +204,114 @@ imageEditor.getObjectPosition(0, 'left', 'top'); imageEditor.getObjectProperties(0, 'left'); imageEditor.getObjectProperties(0, ['left', 'top', 'width', 'height']); imageEditor.getObjectProperties(0, { - left: null, - top: null, - height: null, - opacity: null + left: null, + top: null, + height: null, + opacity: null, }); imageEditor.hasFilter('filterType'); imageEditor.isEmptyRedoStack(); imageEditor.isEmptyUndoStack(); let fileObj: any; -imageEditor.loadImageFromFile(fileObj, 'SampleImage').then(result => { - console.log(`old: ${result.oldWidth}, ${result.oldHeight}`); - console.log(`new: ${result.newWidth}, ${result.newHeight}`); +imageEditor.loadImageFromFile(fileObj, 'SampleImage').then((result) => { + console.log(`old: ${result.oldWidth}, ${result.oldHeight}`); + console.log(`new: ${result.newWidth}, ${result.newHeight}`); }); -imageEditor.loadImageFromURL('http://url/testImage.png', 'lena').then(result => { - console.log(`old: ${result.oldWidth}, ${result.oldHeight}`); - console.log(`new: ${result.newWidth}, ${result.newHeight}`); +imageEditor.loadImageFromURL('http://url/testImage.png', 'lena').then((result) => { + console.log(`old: ${result.oldWidth}, ${result.oldHeight}`); + console.log(`new: ${result.newWidth}, ${result.newHeight}`); }); imageEditor.redo(); imageEditor.registerIcons({ - customIcon: 'M 0 0 L 20 20 L 10 10 Z', - customArrow: 'M 60 0 L 120 60 H 90 L 75 45 V 180 H 45 V 45 L 30 60 H 0 Z' + customIcon: 'M 0 0 L 20 20 L 10 10 Z', + customArrow: 'M 60 0 L 120 60 H 90 L 75 45 V 180 H 45 V 45 L 30 60 H 0 Z', }); imageEditor.removeActiveObject(); -imageEditor.removeFilter('Grayscale').then(obj => { +imageEditor + .removeFilter('Grayscale') + .then((obj) => { console.log(`filterType: ${obj.type}`); console.log(`actType: ${obj.action}`); -}).catch(message => { + }) + .catch((message) => { console.log(`error : ${message}`); -}); + }); imageEditor.removeObject(0); -imageEditor.resetFlip().then(status => { - console.log(`filpX : ${status.flipX}`); - console.log(`flipY : ${status.flipY}`); - console.log(`angle : ${status.angle}`); +imageEditor.resetFlip().then((status) => { + console.log(`filpX : ${status.flipX}`); + console.log(`flipY : ${status.flipY}`); + console.log(`angle : ${status.angle}`); }); imageEditor.resizeCanvasDimension({ - width: 300, - height: 300 + width: 300, + height: 300, }); imageEditor.rotate(10); imageEditor.setAngle(45); imageEditor.setBrush({ - width: 12, - color: 'rgba(0, 0, 0, 0.5)' + width: 12, + color: 'rgba(0, 0, 0, 0.5)', }); imageEditor.setBrush({ - width: 20, - color: '#FFFFFF' + width: 20, + color: '#FFFFFF', }); -imageEditor.setCropzoneRect(1/1); +imageEditor.setCropzoneRect(1 / 1); imageEditor.setDrawingShape('rect', { - fill: 'red', - width: 100, - height: 200 + fill: 'red', + width: 100, + height: 200, }); imageEditor.setDrawingShape('circle', { - rx: 10, - ry: 10, - isRegular: true + rx: 10, + ry: 10, + isRegular: true, }); imageEditor.setObjectPosition(0, { - x: 0, - y: 0, - originX: 'left', - originY: 'top' + x: 0, + y: 0, + originX: 'left', + originY: 'top', }); -imageEditor.setObjectProperties(0, { +imageEditor + .setObjectProperties(0, { left: 100, top: 100, width: 200, height: 200, - opacity: 0.5 -}).then(arg => { + opacity: 0.5, + }) + .then((arg) => { console.log(arg); -}); -imageEditor.setObjectPropertiesQuietly(0, { + }); +imageEditor + .setObjectPropertiesQuietly(0, { left: 100, top: 100, width: 200, height: 200, - opacity: 0.5 -}).then(arg => { + opacity: 0.5, + }) + .then((arg) => { console.log(arg); -}); + }); imageEditor.startDrawingMode('FREE_DRWARING', { - width: 10, - color: 'rgba(255, 0, 0, 0.5)' + width: 10, + color: 'rgba(255, 0, 0, 0.5)', }); imageEditor.stopDrawingMode(); imageEditor.toDataURL(); imageEditor.undo(); -imageEditor.on('addText', pos => { - imageEditor.addText('Double Click', { - position: pos.originPosition - }); +imageEditor.on('addText', (pos) => { + imageEditor.addText('Double Click', { + position: pos.originPosition, + }); - console.log(`text position on canvas : ${pos.originPosition}`); - console.log(`text position on browser : ${pos.clientPosition}`); + console.log(`text position on canvas : ${pos.originPosition}`); + console.log(`text position on browser : ${pos.clientPosition}`); }); -imageEditor.ui.resizeEditor({uiSize: {width: '600px', height: '1200px'}}); -imageEditor.ui.resizeEditor({imageSize: {newWidth: 300, newHeight: 140}}); \ No newline at end of file +imageEditor.ui.resizeEditor({ uiSize: { width: '600px', height: '1200px' } }); +imageEditor.ui.resizeEditor({ imageSize: { newWidth: 300, newHeight: 140 } }); diff --git a/test/ui.spec.js b/test/ui.spec.js index a8ef274b6..99384b268 100644 --- a/test/ui.spec.js +++ b/test/ui.spec.js @@ -3,178 +3,179 @@ * @fileoverview Test cases of "src/js/ui.js" */ import snippet from 'tui-code-snippet'; -import {Promise} from '../src/js/util'; +import { Promise } from '../src/js/util'; import UI from '../src/js/ui'; -import {HELP_MENUS} from '../src/js/consts'; +import { HELP_MENUS } from '../src/js/consts'; describe('UI', () => { - let ui; - let uiOptions; + let ui; + let uiOptions; + beforeEach(() => { + uiOptions = { + loadImage: { + path: 'mockImagePath', + name: '', + }, + menu: ['crop', 'flip', 'rotate', 'draw', 'shape', 'icon', 'text', 'mask', 'filter'], + initMenu: 'shape', + menuBarPosition: 'bottom', + }; + ui = new UI(document.createElement('div'), uiOptions, {}); + }); + + describe('Destroy()', () => { + it('"_destroyAllMenu()" The "destroy" function of all menu instances must be executed.', () => { + snippet.forEach(uiOptions.menu, (menuName) => { + spyOn(ui[menuName], 'destroy'); + }); + + ui._destroyAllMenu(); + + snippet.forEach(uiOptions.menu, (menuName) => { + expect(ui[menuName].destroy).toHaveBeenCalled(); + }); + }); + + it('"_removeUiEvent()" must execute "removeEventListener" of all menus.', () => { + const allUiButtonElementName = [...uiOptions.menu, ...HELP_MENUS]; + snippet.forEach(allUiButtonElementName, (elementName) => { + spyOn(ui._buttonElements[elementName], 'removeEventListener'); + }); + + ui._removeUiEvent(); + + snippet.forEach(allUiButtonElementName, (elementName) => { + expect(ui._buttonElements[elementName].removeEventListener).toHaveBeenCalled(); + }); + }); + }); + + describe('_changeMenu()', () => { + beforeEach(() => { + ui.submenu = 'shape'; + spyOn(ui, 'resizeEditor'); + spyOn(ui.shape, 'changeStandbyMode'); + spyOn(ui.filter, 'changeStartMode'); + ui._actions.main = { + changeSelectableAll: jasmine.createSpy('changeSelectableAll'), + }; + ui._changeMenu('filter', false, false); + }); + it('When the menu changes, the changeStartMode () of the menu instance to be changed must be executed.', () => { + expect(ui.shape.changeStandbyMode).toHaveBeenCalled(); + }); + + it('When the menu changes, the changeStandbyMode () of the existing menu instance must be executed.', () => { + expect(ui.filter.changeStartMode).toHaveBeenCalled(); + }); + }); + + describe('_makeSubMenu()', () => { + it('MakeMenuElement should be executed for the number of menus specified in the option.', () => { + spyOn(ui, '_makeMenuElement'); + + ui._makeSubMenu(); + expect(ui._makeMenuElement.calls.count()).toBe(uiOptions.menu.length); + }); + + it('Instance of the menu specified in the option must be created.', () => { + spyOn(ui, '_makeMenuElement'); + const getConstructorName = (constructor) => + constructor.toString().match(/^function\s(.+?)\(/)[1]; + + ui._makeSubMenu(); + snippet.forEach(uiOptions.menu, (menuName) => { + const constructorNameOfInstance = getConstructorName(ui[menuName].constructor); + const expected = menuName.replace(/^[a-z]/, ($0) => $0.toUpperCase()); + expect(constructorNameOfInstance).toBe(expected); + }); + }); + }); + + describe('initCanvas()', () => { + let promise; + + beforeEach(() => { + promise = new Promise((resolve) => { + resolve(); + }); + ui._editorElement = { + querySelector: jasmine + .createSpy('querySelector') + .and.returnValue(document.createElement('div')), + }; + ui._actions.main = { + initLoadImage: jasmine.createSpy('initLoadImage').and.returnValue(promise), + }; + }); + + it('When initCanvas is executed, some internal methods must be run as required.', (done) => { + spyOn(ui, 'activeMenuEvent'); + spyOn(ui, '_addLoadEvent'); + + ui.initCanvas(); + promise.then(() => { + expect(ui.activeMenuEvent).toHaveBeenCalled(); + expect(ui._addLoadEvent).toHaveBeenCalled(); + done(); + }); + }); + + it('`initLoadImage()` should not be run when has not image path.', () => { + spyOn(ui, '_getLoadImage').and.returnValue({ path: '' }); + + ui.initCanvas(); + + expect(ui._actions.main.initLoadImage).not.toHaveBeenCalled(); + }); + + it('`_AddLoadEvent()` should be executed even if there is no image path.', () => { + spyOn(ui, '_getLoadImage').and.returnValue({ path: '' }); + spyOn(ui, '_addLoadEvent'); + + ui.initCanvas(); + + expect(ui._addLoadEvent).toHaveBeenCalled(); + }); + }); + + describe('_setEditorPosition()', () => { beforeEach(() => { - uiOptions = { - loadImage: { - path: 'mockImagePath', - name: '' - }, - menu: ['crop', 'flip', 'rotate', 'draw', 'shape', 'icon', 'text', 'mask', 'filter'], - initMenu: 'shape', - menuBarPosition: 'bottom' - }; - ui = new UI(document.createElement('div'), uiOptions, {}); - }); - - describe('Destroy()', () => { - it('"_destroyAllMenu()" The "destroy" function of all menu instances must be executed.', () => { - snippet.forEach(uiOptions.menu, menuName => { - spyOn(ui[menuName], 'destroy'); - }); - - ui._destroyAllMenu(); - - snippet.forEach(uiOptions.menu, menuName => { - expect(ui[menuName].destroy).toHaveBeenCalled(); - }); - }); - - it('"_removeUiEvent()" must execute "removeEventListener" of all menus.', () => { - const allUiButtonElementName = [...uiOptions.menu, ...HELP_MENUS]; - snippet.forEach(allUiButtonElementName, elementName => { - spyOn(ui._buttonElements[elementName], 'removeEventListener'); - }); - - ui._removeUiEvent(); - - snippet.forEach(allUiButtonElementName, elementName => { - expect(ui._buttonElements[elementName].removeEventListener).toHaveBeenCalled(); - }); - }); - }); - - describe('_changeMenu()', () => { - beforeEach(() => { - ui.submenu = 'shape'; - spyOn(ui, 'resizeEditor'); - spyOn(ui.shape, 'changeStandbyMode'); - spyOn(ui.filter, 'changeStartMode'); - ui._actions.main = { - changeSelectableAll: jasmine.createSpy('changeSelectableAll') - }; - ui._changeMenu('filter', false, false); - }); - it('When the menu changes, the changeStartMode () of the menu instance to be changed must be executed.', () => { - expect(ui.shape.changeStandbyMode).toHaveBeenCalled(); - }); - - it('When the menu changes, the changeStandbyMode () of the existing menu instance must be executed.', () => { - expect(ui.filter.changeStartMode).toHaveBeenCalled(); - }); - }); - - describe('_makeSubMenu()', () => { - it('MakeMenuElement should be executed for the number of menus specified in the option.', () => { - spyOn(ui, '_makeMenuElement'); - - ui._makeSubMenu(); - expect(ui._makeMenuElement.calls.count()).toBe(uiOptions.menu.length); - }); - - it('Instance of the menu specified in the option must be created.', () => { - spyOn(ui, '_makeMenuElement'); - const getConstructorName = constructor => ( - constructor.toString().match(/^function\s(.+?)\(/)[1] - ); - - ui._makeSubMenu(); - snippet.forEach(uiOptions.menu, menuName => { - const constructorNameOfInstance = getConstructorName(ui[menuName].constructor); - const expected = menuName.replace(/^[a-z]/, $0 => $0.toUpperCase()); - expect(constructorNameOfInstance).toBe(expected); - }); - }); - }); - - describe('initCanvas()', () => { - let promise; - - beforeEach(() => { - promise = new Promise(resolve => { - resolve(); - }); - ui._editorElement = { - querySelector: jasmine.createSpy('querySelector').and.returnValue(document.createElement('div')) - }; - ui._actions.main = { - initLoadImage: jasmine.createSpy('initLoadImage').and.returnValue(promise) - }; - }); - - it('When initCanvas is executed, some internal methods must be run as required.', done => { - spyOn(ui, 'activeMenuEvent'); - spyOn(ui, '_addLoadEvent'); - - ui.initCanvas(); - promise.then(() => { - expect(ui.activeMenuEvent).toHaveBeenCalled(); - expect(ui._addLoadEvent).toHaveBeenCalled(); - done(); - }); - }); - - it('`initLoadImage()` should not be run when has not image path.', () => { - spyOn(ui, '_getLoadImage').and.returnValue({path: ''}); - - ui.initCanvas(); - - expect(ui._actions.main.initLoadImage).not.toHaveBeenCalled(); - }); - - it('`_AddLoadEvent()` should be executed even if there is no image path.', () => { - spyOn(ui, '_getLoadImage').and.returnValue({path: ''}); - spyOn(ui, '_addLoadEvent'); - - ui.initCanvas(); - - expect(ui._addLoadEvent).toHaveBeenCalled(); - }); - }); - - describe('_setEditorPosition()', () => { - beforeEach(() => { - ui._editorElement = document.createElement('div'); - spyOn(ui, '_getCanvasMaxDimension').and.returnValue({ - width: 300, - height: 300 - }); - }); - - it('Position is bottom, it should be reflected in the bottom of the editor position.', () => { - ui.submenu = true; - ui._setEditorPosition('bottom'); - - expect(ui._editorElement.style.top).toBe('150px'); - expect(ui._editorElement.style.left).toBe('0px'); - }); - - it('Position is top, it should be reflected in the top of the editor position.', () => { - ui.submenu = true; - ui._setEditorPosition('top'); - - expect(ui._editorElement.style.top).toBe('-150px'); - expect(ui._editorElement.style.left).toBe('0px'); - }); - it('Position is left, it should be reflected in the left, right of the editor position.', () => { - ui.submenu = true; - ui._setEditorPosition('left'); - - expect(ui._editorElement.style.top).toBe('0px'); - expect(ui._editorElement.style.left).toBe('-150px'); - }); - it('Position is right, it should be reflected in the right of the editor position.', () => { - ui.submenu = true; - ui._setEditorPosition('right'); - - expect(ui._editorElement.style.top).toBe('0px'); - expect(ui._editorElement.style.left).toBe('150px'); - }); + ui._editorElement = document.createElement('div'); + spyOn(ui, '_getCanvasMaxDimension').and.returnValue({ + width: 300, + height: 300, + }); + }); + + it('Position is bottom, it should be reflected in the bottom of the editor position.', () => { + ui.submenu = true; + ui._setEditorPosition('bottom'); + + expect(ui._editorElement.style.top).toBe('150px'); + expect(ui._editorElement.style.left).toBe('0px'); + }); + + it('Position is top, it should be reflected in the top of the editor position.', () => { + ui.submenu = true; + ui._setEditorPosition('top'); + + expect(ui._editorElement.style.top).toBe('-150px'); + expect(ui._editorElement.style.left).toBe('0px'); + }); + it('Position is left, it should be reflected in the left, right of the editor position.', () => { + ui.submenu = true; + ui._setEditorPosition('left'); + + expect(ui._editorElement.style.top).toBe('0px'); + expect(ui._editorElement.style.left).toBe('-150px'); + }); + it('Position is right, it should be reflected in the right of the editor position.', () => { + ui.submenu = true; + ui._setEditorPosition('right'); + + expect(ui._editorElement.style.top).toBe('0px'); + expect(ui._editorElement.style.left).toBe('150px'); }); + }); }); diff --git a/test/uiRange.spec.js b/test/uiRange.spec.js index e3dc99fc4..de0bfbb7a 100644 --- a/test/uiRange.spec.js +++ b/test/uiRange.spec.js @@ -1,46 +1,49 @@ import Range from '../src/js/ui/tools/range'; -import {defaultRotateRangeValus} from '../src/js/consts'; +import { defaultRotateRangeValus } from '../src/js/consts'; describe('Range', () => { - let range, input, slider; - beforeEach(() => { - input = document.createElement('input'); - slider = document.createElement('div'); - range = new Range({ - slider, - input - }, defaultRotateRangeValus); - }); + let range, input, slider; + beforeEach(() => { + input = document.createElement('input'); + slider = document.createElement('div'); + range = new Range( + { + slider, + input, + }, + defaultRotateRangeValus + ); + }); - it('The value must be incremented by 1, when keyCode 38 is found in the event handler with changeInputWithArrow.', () => { - const ev = { - target: input, - keyCode: 38 - }; - input.value = '3'; - range.eventHandler.changeInputWithArrow(ev); + it('The value must be incremented by 1, when keyCode 38 is found in the event handler with changeInputWithArrow.', () => { + const ev = { + target: input, + keyCode: 38, + }; + input.value = '3'; + range.eventHandler.changeInputWithArrow(ev); - expect(range.value).toBe(4); - }); - it('The value must be decremented by 1, when keyCode 40 is found in the event handler with changeInputWithArrow.', () => { - const ev = { - target: input, - keyCode: 40 - }; - input.value = '3'; - range.eventHandler.changeInputWithArrow(ev); + expect(range.value).toBe(4); + }); + it('The value must be decremented by 1, when keyCode 40 is found in the event handler with changeInputWithArrow.', () => { + const ev = { + target: input, + keyCode: 40, + }; + input.value = '3'; + range.eventHandler.changeInputWithArrow(ev); - expect(range.value).toBe(2); - }); + expect(range.value).toBe(2); + }); - it('The `changeInput` event handler should filter out any invalid input values.', () => { - const ev = { - target: input, - keyCode: 83 - }; - input.value = '-3!!6s0s'; + it('The `changeInput` event handler should filter out any invalid input values.', () => { + const ev = { + target: input, + keyCode: 83, + }; + input.value = '-3!!6s0s'; - range.eventHandler.changeInput(ev); - expect(range.value).toBe(-360); - }); + range.eventHandler.changeInput(ev); + expect(range.value).toBe(-360); + }); }); diff --git a/tsBannerGenerator.js b/tsBannerGenerator.js index bd098907e..584310951 100644 --- a/tsBannerGenerator.js +++ b/tsBannerGenerator.js @@ -3,27 +3,27 @@ var fs = require('fs'); var path = require('path'); var pkg = require('./package.json'); -var tsVersion = (/[0-9.]+/).exec(pkg.devDependencies.typescript)[0]; +var tsVersion = /[0-9.]+/.exec(pkg.devDependencies.typescript)[0]; var declareFilePath = path.join(__dirname, 'index.d.ts'); var declareRows = []; var TS_BANNER = [ - '// Type definitions for TOAST UI Image Editor v' + pkg.version, - '// TypeScript Version: ' + tsVersion + '// Type definitions for TOAST UI Image Editor v' + pkg.version, + '// TypeScript Version: ' + tsVersion, ].join('\n'); -fs.readFile(declareFilePath, 'utf8', function(error, data) { - if (error) { - throw error; - } +fs.readFile(declareFilePath, 'utf8', function (error, data) { + if (error) { + throw error; + } - declareRows = data.toString().split('\n'); - declareRows.splice(0, 2, TS_BANNER); + declareRows = data.toString().split('\n'); + declareRows.splice(0, 2, TS_BANNER); - fs.writeFile(declareFilePath, declareRows.join('\n'), 'utf8', function(error, data) { - if (error) { - throw error; - } + fs.writeFile(declareFilePath, declareRows.join('\n'), 'utf8', function (error, data) { + if (error) { + throw error; + } - console.log('Completed Write Banner for Typescript!'); - }); + console.log('Completed Write Banner for Typescript!'); + }); }); diff --git a/tslint.json b/tslint.json index 96cd2a970..10f1305f5 100644 --- a/tslint.json +++ b/tslint.json @@ -1,12 +1,9 @@ { - "extends": "tslint:recommended", - "rules": { - "quotemark": [ - true, - "single" - ], - "trailing-comma": false, - "max-classes-per-file": false, - "no-namespace": false - } + "extends": "tslint:recommended", + "rules": { + "quotemark": [true, "single"], + "trailing-comma": false, + "max-classes-per-file": false, + "no-namespace": false + } } diff --git a/tuidoc.config.json b/tuidoc.config.json index 5cd6aa92d..48b7c5589 100644 --- a/tuidoc.config.json +++ b/tuidoc.config.json @@ -1,38 +1,38 @@ { - "header": { - "logo": { - "src": "https://uicdn.toast.com/toastui/img/tui-image-editor-bi-white.png" - }, - "title": { - "text": "repo", - "linkUrl": "https://github.com/nhn/tui.image-editor" - } + "header": { + "logo": { + "src": "https://uicdn.toast.com/toastui/img/tui-image-editor-bi-white.png" }, - "footer": [ - { - "title": "NHN", - "linkUrl": "https://github.com/nhn" - }, - { - "title": "FE Development Lab", - "linkUrl": "https://github.com/nhn/fe.javascript" - } - ], - "main": { - "filePath": "README.md" + "title": { + "text": "repo", + "linkUrl": "https://github.com/nhn/tui.image-editor" + } + }, + "footer": [ + { + "title": "NHN", + "linkUrl": "https://github.com/nhn" }, - "api": { - "filePath": "src/js/**", - "fileLink": true + { + "title": "FE Development Lab", + "linkUrl": "https://github.com/nhn/fe.javascript" + } + ], + "main": { + "filePath": "README.md" + }, + "api": { + "filePath": "src/js/**", + "fileLink": true + }, + "examples": { + "filePath": "examples", + "titles": { + "example01-includeUi": "1. Include ui", + "example02-useApiDirect": "2. Use api direct (basic)", + "example03-mobile": "3. Mobile" }, - "examples": { - "filePath": "examples", - "titles": { - "example01-includeUi": "1. Include ui", - "example02-useApiDirect": "2. Use api direct (basic)", - "example03-mobile": "3. Mobile" - }, - "globalErrorLogVariable": "errorLogs" - }, - "pathPrefix": "tui.image-editor" + "globalErrorLogVariable": "errorLogs" + }, + "pathPrefix": "tui.image-editor" } diff --git a/webpack.config.js b/webpack.config.js index dbf8bd950..e813b89e8 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -14,111 +14,116 @@ const isProduction = process.argv.indexOf('-p') > -1; const FILENAME = pkg.name + (isProduction ? '.min' : ''); const BANNER = [ - `${FILENAME}.js`, - `@version ${pkg.version}`, - `@author ${pkg.author}`, - `@license ${pkg.license}` + `${FILENAME}.js`, + `@version ${pkg.version}`, + `@author ${pkg.author}`, + `@license ${pkg.license}`, ].join('\n'); module.exports = { - mode: isProduction ? 'production' : 'development', - entry: './src/index.js', - output: { - library: ['tui', 'ImageEditor'], - libraryTarget: 'umd', - path: path.resolve(__dirname, 'dist'), - publicPath: '/dist', - filename: `${FILENAME}.js` + mode: isProduction ? 'production' : 'development', + entry: './src/index.js', + output: { + library: ['tui', 'ImageEditor'], + libraryTarget: 'umd', + path: path.resolve(__dirname, 'dist'), + publicPath: '/dist', + filename: `${FILENAME}.js`, + }, + externals: [ + { + 'tui-code-snippet': { + commonjs: 'tui-code-snippet', + commonjs2: 'tui-code-snippet', + amd: 'tui-code-snippet', + root: ['tui', 'util'], + }, + 'tui-color-picker': { + commonjs: 'tui-color-picker', + commonjs2: 'tui-color-picker', + amd: 'tui-color-picker', + root: ['tui', 'colorPicker'], + }, + fabric: { + commonjs: ['fabric', 'fabric'], + commonjs2: ['fabric', 'fabric'], + amd: 'fabric', + root: 'fabric', + }, }, - externals: [{ - 'tui-code-snippet': { - 'commonjs': 'tui-code-snippet', - 'commonjs2': 'tui-code-snippet', - 'amd': 'tui-code-snippet', - 'root': ['tui', 'util'] + ], + module: { + rules: [ + { + test: /\.js$/, + exclude: /node_modules/, + loader: 'eslint-loader', + enforce: 'pre', + options: { + failOnWarning: false, + failOnError: false, }, - 'tui-color-picker': { - 'commonjs': 'tui-color-picker', - 'commonjs2': 'tui-color-picker', - 'amd': 'tui-color-picker', - 'root': ['tui', 'colorPicker'] + }, + { + test: /\.js$/, + exclude: /node_modules/, + loader: 'babel-loader?cacheDirectory', + options: { + babelrc: true, }, - 'fabric': { - 'commonjs': ['fabric', 'fabric'], - 'commonjs2': ['fabric', 'fabric'], - 'amd': 'fabric', - 'root': 'fabric' - } - }], - module: { - rules: [ - { - test: /\.js$/, - exclude: /node_modules/, - loader: 'eslint-loader', - enforce: 'pre', - options: { - failOnWarning: false, - failOnError: false - } - }, { - test: /\.js$/, - exclude: /node_modules/, - loader: 'babel-loader?cacheDirectory', - options: { - babelrc: true - } - }, { - test: /\.styl$/, - use: [ - MiniCssExtractPlugin.loader, - { - loader: 'css-loader', - options: { - sourceMap: true - } - }, - { - loader: 'stylus-loader', - options: { - sourceMap: true - } - } - ] - }, { - test: /\.svg$/, - loader: 'svg-inline-loader' - } - ] - }, - plugins: [ - new webpack.BannerPlugin(BANNER), - new MiniCssExtractPlugin({ - filename: `${FILENAME}.css` - }), - new SafeUmdPlugin() + }, + { + test: /\.styl$/, + use: [ + MiniCssExtractPlugin.loader, + { + loader: 'css-loader', + options: { + sourceMap: true, + }, + }, + { + loader: 'stylus-loader', + options: { + sourceMap: true, + }, + }, + ], + }, + { + test: /\.svg$/, + loader: 'svg-inline-loader', + }, ], - optimization: { - minimizer: [ - new UglifyJsPlugin({ - cache: true, - parallel: true, - sourceMap: true - }), - new OptimizaeCSSAssetsPlugin({ - cssProcessorOptions: { - map: { - inline: false - } - } - }) - ] - }, - devServer: { - historyApiFallback: false, - progress: true, - inline: true, - host: '0.0.0.0', - disableHostCheck: true - } + }, + plugins: [ + new webpack.BannerPlugin(BANNER), + new MiniCssExtractPlugin({ + filename: `${FILENAME}.css`, + }), + new SafeUmdPlugin(), + ], + optimization: { + minimizer: [ + new UglifyJsPlugin({ + cache: true, + parallel: true, + sourceMap: true, + }), + new OptimizaeCSSAssetsPlugin({ + cssProcessorOptions: { + map: { + inline: false, + }, + }, + }), + ], + }, + devServer: { + historyApiFallback: false, + progress: true, + inline: true, + host: '0.0.0.0', + disableHostCheck: true, + }, };