MinsoftK

open source test

Showing 361 changed files with 4856 additions and 0 deletions
1 +{
2 + "presets": ["es2015"],
3 + "plugins": [
4 + ["transform-es2015-destructuring", { "loose": true }],
5 + ["transform-es2015-for-of", { "loose": true }],
6 + ["transform-es2015-spread", { "loose": true }]
7 + ]
8 +}
1 +[*]
2 +charset = utf-8
3 +indent_style = space
4 +indent_size = 4
5 +end_of_line = lf
...\ No newline at end of file ...\ No newline at end of file
1 +# Ignore polyfill
2 +src/js/polyfill.js
1 +module.exports = {
2 + extends: ['tui/es6', 'plugin:prettier/recommended'],
3 + plugins: ['prettier'],
4 + env: {
5 + browser: true,
6 + amd: true,
7 + node: true,
8 + jasmine: true,
9 + jquery: true,
10 + es6: true,
11 + },
12 + globals: {
13 + fabric: true,
14 + tui: true,
15 + loadFixtures: true,
16 + },
17 + parserOptions: {
18 + sourceType: 'module',
19 + },
20 + rules: {
21 + 'prefer-destructuring': [
22 + 'error',
23 + {
24 + VariableDeclarator: { array: true, object: true },
25 + AssignmentExpression: { array: false, object: false },
26 + },
27 + ],
28 + 'prettier/prettier': 'error',
29 + },
30 +};
1 +---
2 +name: Bug report
3 +about: Create a report to help us improve
4 +title: ''
5 +labels: Bug
6 +assignees: ''
7 +---
8 +
9 +**Describe the bug**
10 +A clear and concise description of what the bug is.
11 +
12 +**To Reproduce**
13 +Steps to reproduce the behavior:
14 +
15 +1. Go to '...'
16 +2. Click on '....'
17 +3. Scroll down to '....'
18 +4. See error
19 +
20 +**Expected behavior**
21 +A clear and concise description of what you expected to happen.
22 +
23 +**Screenshots**
24 +If applicable, add screenshots to help explain your problem.
25 +
26 +**Desktop (please complete the following information):**
27 +
28 +- OS: [e.g. iOS]
29 +- Browser [e.g. chrome, safari]
30 +- Version [e.g. 22]
31 +
32 +**Smartphone (please complete the following information):**
33 +
34 +- Device: [e.g. iPhone6]
35 +- OS: [e.g. iOS8.1]
36 +- Browser [e.g. stock browser, safari]
37 +- Version [e.g. 22]
38 +
39 +**Additional context**
40 +Add any other context about the problem here.
1 +---
2 +name: Feature request
3 +about: Suggest an idea for this project
4 +title: ''
5 +labels: Enhancement, Need Discussion
6 +assignees: ''
7 +---
8 +
9 +<!--
10 +Thank you for your contribution.
11 +
12 +When it comes to write an issue, please, use the template below.
13 +To use the template is mandatory for submit new issue and we won't reply the issue that without the template.
14 +
15 +And you can write template's contents in Korean also.
16 +-->
17 +
18 +<!-- TEMPLATE -->
19 +
20 +## Version
21 +
22 +<!-- Write the version of the imageEditor you are currently using. -->
23 +
24 +## Development Environment
25 +
26 +<!-- Write the browser type, OS and so on -->
27 +
28 +## Current Behavior
29 +
30 +<!-- Write a description of the current operation. You can add sample code, 'CodePen' or 'jsfiddle' links. -->
31 +
32 +```js
33 +// Write example code
34 +```
35 +
36 +## Expected Behavior
37 +
38 +<!-- Write a description of the future action. -->
1 +---
2 +name: Question
3 +about: Create a question about imageEditor
4 +title: ''
5 +labels: Question
6 +assignees: ''
7 +---
8 +
9 +<!--
10 + To make it easier for us to help you, please include as much useful information as possible.
11 +
12 + Useful Links:
13 + - tutorial: https://github.com/nhn/tui.image-editor/tree/master/docs
14 + - API/Example: https://nhn.github.io/tui.image-editor/latest
15 +
16 + Before opening a new issue, please search existing issues https://github.com/nhn/tui.image-editor/issues
17 +-->
18 +
19 +**Summary**
20 +A clear and concise description of what the question is.
21 +
22 +**Screenshots**
23 +If applicable, add screenshots to help explain your question.
24 +
25 +**Version**
26 +Write the version of the imageEditor you are currently using.
27 +
28 +**Additional context**
29 +Add any other context about the problem here.
1 +# Comment to a new issue.
2 +issuesOpened: >
3 + Thank you for raising an issue.
4 +
5 + We will try and get back to you as soon as possible.
6 +
7 +
8 + Please make sure you have filled out issue respecting our form **in English** and given us as much context as possible.
9 + **If not, the issue will be closed or not replied.**
10 +
1 +# Configuration for probot-stale - https://github.com/probot/stale
2 +
3 +# Number of days of inactivity before an Issue or Pull Request becomes stale
4 +daysUntilStale: 30
5 +
6 +# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
7 +# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
8 +daysUntilClose: 7
9 +
10 +# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
11 +exemptLabels:
12 + - Feature
13 + - Enhancement
14 + - Bug
15 + - NHN
16 +
17 +# Label to use when marking as stale
18 +staleLabel: inactive
19 +
20 +# Comment to post when marking as stale. Set to `false` to disable
21 +markComment: >
22 + This issue has been automatically marked as inactive because there hasn’t been much going on it lately.
23 + It is going to be closed after 7 days. Thanks!
24 +# Comment to post when removing the stale label.
25 +# unmarkComment: >
26 +# Your comment here.
27 +
28 +# Comment to post when closing a stale Issue or Pull Request.
29 +closeComment: >
30 + This issue will be closed due to inactivity. Thanks for your contribution!
1 +name: detect runtime error
2 +
3 +on:
4 + schedule:
5 + - cron: "0 22 * * *"
6 +jobs:
7 + detectError:
8 + runs-on: ubuntu-latest
9 + steps:
10 + - name: checkout repository
11 + uses: actions/checkout@v2
12 + - name: create config variable
13 + run: |
14 + node createConfigVariable.js
15 + - name: set global error variable
16 + run: |
17 + errorVariable=`cat ./errorVariable.txt`
18 + echo "ERROR_VARIABLE=$errorVariable" >> $GITHUB_ENV
19 + - name: set url
20 + shell: bash
21 + run: |
22 + url=`cat ./url.txt`
23 + echo "URLS=$url" >> $GITHUB_ENV
24 + - name: detect runtime error
25 + uses: nhn/toast-ui.detect-runtime-error-actions@v1.0.1
26 + with:
27 + global-error-log-variable: ${{ env.ERROR_VARIABLE }}
28 + urls: ${{ env.URLS }}
29 + env:
30 + BROWSERSTACK_USERNAME: ${{secrets.BROWSERSTACK_USERNAME}}
31 + BROWSERSTACK_ACCESS_KEY: ${{secrets.BROWSERSTACK_ACCESS_KEY}}
1 +{
2 + "singleQuote": true,
3 + "printWidth": 100,
4 + "tabWidth": 2,
5 + "useTabs": false,
6 + "semi": true,
7 + "quoteProps": "as-needed",
8 + "trailingComma": "es5",
9 + "arrowParens": "always",
10 + "endOfLine": "lf",
11 + "bracketSpacing": true,
12 + "proseWrap": "preserve"
13 +}
1 +# Contributor Covenant Code of Conduct
2 +
3 +## Our Pledge
4 +
5 +In the interest of fostering an open and welcoming environment, we as
6 +contributors and maintainers pledge to making participation in our project and
7 +our community a harassment-free experience for everyone, regardless of age, body
8 +size, disability, ethnicity, gender identity and expression, level of experience,
9 +education, socio-economic status, nationality, personal appearance, race,
10 +religion, or sexual identity and orientation.
11 +
12 +## Our Standards
13 +
14 +Examples of behavior that contributes to creating a positive environment
15 +include:
16 +
17 +- Using welcoming and inclusive language
18 +- Being respectful of differing viewpoints and experiences
19 +- Gracefully accepting constructive criticism
20 +- Focusing on what is best for the community
21 +- Showing empathy towards other community members
22 +
23 +Examples of unacceptable behavior by participants include:
24 +
25 +- The use of sexualized language or imagery and unwelcome sexual attention or
26 + advances
27 +- Trolling, insulting/derogatory comments, and personal or political attacks
28 +- Public or private harassment
29 +- Publishing others' private information, such as a physical or electronic
30 + address, without explicit permission
31 +- Other conduct which could reasonably be considered inappropriate in a
32 + professional setting
33 +
34 +## Our Responsibilities
35 +
36 +Project maintainers are responsible for clarifying the standards of acceptable
37 +behavior and are expected to take appropriate and fair corrective action in
38 +response to any instances of unacceptable behavior.
39 +
40 +Project maintainers have the right and responsibility to remove, edit, or
41 +reject comments, commits, code, wiki edits, issues, and other contributions
42 +that are not aligned to this Code of Conduct, or to ban temporarily or
43 +permanently any contributor for other behaviors that they deem inappropriate,
44 +threatening, offensive, or harmful.
45 +
46 +## Scope
47 +
48 +This Code of Conduct applies both within project spaces and in public spaces
49 +when an individual is representing the project or its community. Examples of
50 +representing a project or community include using an official project e-mail
51 +address, posting via an official social media account, or acting as an appointed
52 +representative at an online or offline event. Representation of a project may be
53 +further defined and clarified by project maintainers.
54 +
55 +## Enforcement
56 +
57 +Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 +reported by contacting the project team at dl_javascript@nhn.com. All
59 +complaints will be reviewed and investigated and will result in a response that
60 +is deemed necessary and appropriate to the circumstances. The project team is
61 +obligated to maintain confidentiality with regard to the reporter of an incident.
62 +Further details of specific enforcement policies may be posted separately.
63 +
64 +Project maintainers who do not follow or enforce the Code of Conduct in good
65 +faith may face temporary or permanent repercussions as determined by other
66 +members of the project's leadership.
67 +
68 +## Attribution
69 +
70 +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 +
73 +[homepage]: https://www.contributor-covenant.org
1 +# Contributing to TOAST UI
2 +
3 +First off, thanks for taking the time to contribute! 🎉 😘 ✨
4 +
5 +The following is a set of guidelines for contributing to TOAST UI. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request.
6 +
7 +## Reporting Bugs
8 +
9 +Bugs are tracked as GitHub issues. Search the list and try reproduce on [demo][demo] before you create an issue. When you create an issue, please provide the following information by filling in the template.
10 +
11 +Explain the problem and include additional details to help maintainers reproduce the problem:
12 +
13 +- **Use a clear and descriptive title** for the issue to identify the problem.
14 +- **Describe the exact steps which reproduce the problem** in as many details as possible. Don't just say what you did, but explain how you did it. For example, if you moved the cursor to the end of a line, explain if you used a mouse or a keyboard.
15 +- **Provide specific examples to demonstrate the steps.** Include links to files or GitHub projects, or copy/pasteable snippets, which you use in those examples. If you're providing snippets on the issue, use Markdown code blocks.
16 +- **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior.
17 +- **Explain which behavior you expected to see instead and why.**
18 +- **Include screenshots and animated GIFs** which show you following the described steps and clearly demonstrate the problem.
19 +
20 +## Suggesting Enhancements
21 +
22 +In case you want to suggest for TOAST UI ImageEditor, please follow this guideline to help maintainers and the community understand your suggestion.
23 +Before creating suggestions, please check [issue list](../../labels/enhancement) if there's already a request.
24 +
25 +Create an issue and provide the following information:
26 +
27 +- **Use a clear and descriptive title** for the issue to identify the suggestion.
28 +- **Provide a step-by-step description of the suggested enhancement** in as many details as possible.
29 +- **Provide specific examples to demonstrate the steps.** Include copy/pasteable snippets which you use in those examples, as Markdown code blocks.
30 +- **Include screenshots and animated GIFs** which helps demonstrate the steps or point out the part of TOAST UI ImageEditor which the suggestion is related to.
31 +- **Explain why this enhancement would be useful** to most TOAST UI users.
32 +- **List some other image editors or applications where this enhancement exists.**
33 +
34 +## First Code Contribution
35 +
36 +Unsure where to begin contributing to TOAST UI? You can start by looking through these `document`, `good first issue` and `help wanted` issues:
37 +
38 +- **document issues**: issues which should be reviewed or improved.
39 +- **good first issues**: issues which should only require a few lines of code, and a test or two.
40 +- **help wanted issues**: issues which should be a bit more involved than beginner issues.
41 +
42 +## Pull Requests
43 +
44 +### Development WorkFlow
45 +
46 +- Set up your development environment
47 +- Make change from a right branch
48 +- Be sure the code passes `npm run lint`, `npm run test`
49 +- Make a pull request
50 +
51 +### Development environment
52 +
53 +- Prepare your machine node and it's packages installed.
54 +- Checkout our repository
55 +- Install dependencies by `npm install && bower install`
56 +- Start webpack-dev-server by `npm run serve`
57 +
58 +### Make changes
59 +
60 +#### Checkout a branch
61 +
62 +- **develop**: PR base branch. merge features, updates for next minor or major release
63 +- **master**: bug fix or document update for next patch release. develop branch will rebase every time master branch update. so keep code change to a minimum.
64 +- **production**: lastest release branch with distribution files. never make a PR on this
65 +- **gh-pages**: API docs, examples and demo
66 +
67 +#### Check Code Style
68 +
69 +Run `npm run eslint` and make sure all the tests pass.
70 +
71 +#### Test
72 +
73 +Run `npm run test` and verify all the tests pass.
74 +If you are adding new commands or features, they must include tests.
75 +If you are changing functionality, update the tests if you need to.
76 +
77 +#### Commit
78 +
79 +Follow our [commit message conventions](./docs/COMMIT_MESSAGE_CONVENTION.md).
80 +
81 +### Yes! Pull request
82 +
83 +Make your pull request, then describe your changes.
84 +
85 +#### Title
86 +
87 +Follow other PR title format on below.
88 +
89 +```
90 + <Type>: Short Description (fix #111)
91 + <Type>: Short Description (fix #123, #111, #122)
92 + <Type>: Short Description (ref #111)
93 +```
94 +
95 +- capitalize first letter of Type
96 +- use present tense: 'change' not 'changed' or 'changes'
97 +
98 +#### Description
99 +
100 +If it has related to issues, add links to the issues(like `#123`) in the description.
101 +Fill in the [Pull Request Template](./docs/PULL_REQUEST_TEMPLATE.md) by check your case.
102 +
103 +## Code of Conduct
104 +
105 +This project and everyone participating in it is governed by the [Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to dl_javascript@nhn.com.
106 +
107 +> This Guide is base on [atom contributing guide](https://github.com/atom/atom/blob/master/CONTRIBUTING.md), [CocoaPods](http://guides.cocoapods.org/contributing/contribute-to-cocoapods.html) and [ESLint](http://eslint.org/docs/developer-guide/contributing/pull-requests)
1 +<!--
2 +Thank you for your contribution.
3 +
4 +When it comes to write an issue, please, use the template below.
5 +To use the template is mandatory for submit new issue and we won't reply the issue that without the template.
6 +
7 +And you can write template's contents in Korean also.
8 +-->
9 +
10 +<!-- TEMPLATE -->
11 +
12 +## Version
13 +
14 +<!-- Write the version of the grid you are currently using. -->
15 +
16 +## Development Environment
17 +
18 +<!-- Write the browser type, OS and so on -->
19 +
20 +## Current Behavior
21 +
22 +<!-- Write a description of the current operation. You can add example code, 'CodePen' or 'jsfiddle' links. -->
23 +
24 +```js
25 +// Write example code
26 +```
27 +
28 +## Expected Behavior
29 +
30 +<!-- Write a description of the future action. -->
1 +
2 +The MIT License
3 +
4 +Copyright (c) 2019 NHN Corp.
5 +
6 +Permission is hereby granted, free of charge, to any person obtaining a copy
7 +of this software and associated documentation files (the "Software"), to deal
8 +in the Software without restriction, including without limitation the rights
9 +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 +copies of the Software, and to permit persons to whom the Software is
11 +furnished to do so, subject to the following conditions:
12 +
13 +The above copyright notice and this permission notice shall be included in
14 +all copies or substantial portions of the Software.
15 +
16 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 +THE SOFTWARE.
This diff is collapsed. Click to expand it.
1 +{
2 + "name": "tui-image-editor",
3 + "authors": ["NHN FE Dev Lab <dl_javascript@nhn.com>"],
4 + "license": "MIT",
5 + "main": ["dist/tui-image-editor.js"],
6 + "ignore": [
7 + "**/.*",
8 + "node_modules",
9 + "bower_components",
10 + "test",
11 + "tests",
12 + "src",
13 + "server",
14 + "data.js",
15 + "Gruntfile.js",
16 + "gulpfile.js",
17 + "karma.*.js",
18 + "conf.json",
19 + "package.json",
20 + ".gitignore",
21 + "samples",
22 + "index.js",
23 + "jsdoc.conf.json",
24 + "webpack.*.js"
25 + ],
26 + "dependencies": {
27 + "fabric": "4.2.0",
28 + "tui-code-snippet": "^1.5.0",
29 + "tui-color-picker": "^2.2.0"
30 + },
31 + "devDependencies": {
32 + "tui-component-colorpicker": "~1.0.1",
33 + "filesaver": "*"
34 + },
35 + "resolutions": {
36 + "tui-code-snippet": "^1.5.0",
37 + "tui-color-picker": "^2.2.0"
38 + }
39 +}
1 +const fs = require('fs');
2 +const path = require('path');
3 +const config = require(path.resolve(process.cwd(), 'tuidoc.config.json'));
4 +const examples = config.examples || {};
5 +const { filePath, globalErrorLogVariable } = examples;
6 +
7 +/**
8 + * Get Examples Url
9 + */
10 +function getTestUrls() {
11 + if (!filePath) {
12 + throw Error('not exist examples path at tuidoc.config.json');
13 + }
14 +
15 + const urlPrefix = 'http://nhn.github.io/tui.image-editor/latest';
16 +
17 + const testUrls = fs.readdirSync(filePath).reduce((urls, fileName) => {
18 + if (/html$/.test(fileName)) {
19 + urls.push(`${urlPrefix}/${filePath}/${fileName}`);
20 + }
21 +
22 + return urls;
23 + }, []);
24 +
25 + fs.writeFileSync('url.txt', testUrls.join(', '));
26 +}
27 +
28 +function getGlobalVariable() {
29 + if (!globalErrorLogVariable) {
30 + throw Error('not exist examples path at tuidoc.config.json');
31 + }
32 +
33 + fs.writeFileSync('errorVariable.txt', globalErrorLogVariable);
34 +}
35 +
36 +getTestUrls();
37 +getGlobalVariable();
1 +.border {
2 + border: 1px solid black;
3 +}
4 +.body-container {
5 + width: 1000px;
6 +}
7 +.tui-image-editor-controls {
8 + min-height: 250px;
9 +}
10 +.menu {
11 + padding: 0;
12 + margin-bottom: 5px;
13 + text-align: center;
14 + color: #544b61;
15 + font-weight: 400;
16 + list-style-type: none;
17 + user-select: none;
18 + -moz-user-select: none;
19 + -ms-user-select: none;
20 + -webkit-user-select: none;
21 +}
22 +.logo {
23 + margin: 0 auto;
24 + width: 300px;
25 + vertical-align: middle;
26 +}
27 +.header .name {
28 + padding: 10px;
29 + line-height: 50px;
30 + font-size: 30px;
31 + font-weight: 100;
32 + vertical-align: middle;
33 +}
34 +.header .menu {
35 + display: inline-block;
36 +}
37 +.menu-item {
38 + padding: 10px;
39 + display: inline-block;
40 + cursor: pointer;
41 + vertical-align: middle;
42 +}
43 +.menu-item a {
44 + text-decoration: none;
45 +}
46 +.menu-item.no-pointer {
47 + cursor: default;
48 +}
49 +.menu-item.active,
50 +.menu-item:hover {
51 + background-color: #f3f3f3;
52 +}
53 +.menu-item.disabled {
54 + cursor: default;
55 + color: #bfbebe;
56 +}
57 +.align-left-top {
58 + text-align: left;
59 + vertical-align: top;
60 +}
61 +.range-narrow {
62 + width: 80px;
63 +}
64 +.sub-menu-container {
65 + font-size: 14px;
66 + margin-bottom: 1em;
67 + display: none;
68 +}
69 +.tui-image-editor {
70 + height: 500px;
71 +}
72 +.tui-image-editor-canvas-container {
73 + margin: 0 auto;
74 + top: 50%;
75 + transform: translateY(-50%);
76 + -ms-transform: translateY(-50%);
77 + -moz-transform: translateY(-50%);
78 + -webkit-transform: translateY(-50%);
79 + border: 1px dashed black;
80 + overflow: hidden;
81 +}
82 +.tui-colorpicker-container {
83 + margin: 5px auto 0;
84 +}
85 +.tui-colorpicker-palette-toggle-slider {
86 + display: none;
87 +}
88 +.input-wrapper {
89 + position: relative;
90 +}
91 +.input-wrapper input {
92 + cursor: pointer;
93 + position: absolute;
94 + font-size: 999px;
95 + left: 0;
96 + top: 0;
97 + opacity: 0;
98 + width: 100%;
99 + height: 100%;
100 + overflow: hidden;
101 +}
102 +.btn-text-style {
103 + padding: 5px;
104 + margin: 3px 1px;
105 + border: 1px dashed #bfbebe;
106 + outline: 0;
107 + background-color: #eee;
108 + cursor: pointer;
109 +}
110 +.icon-text {
111 + font-size: 20px;
112 +}
113 +.select-line-type {
114 + outline: 0;
115 + vertical-align: middle;
116 +}
117 +#tui-color-picker {
118 + display: inline-block;
119 + vertical-align: middle;
120 +}
121 +#tui-text-palette {
122 + display: none;
123 + position: absolute;
124 + padding: 10px;
125 + border: 1px solid #bfbebe;
126 + background-color: #fff;
127 + z-index: 9999;
128 +}
1 +html,
2 +body {
3 + margin: 0;
4 + padding: 0;
5 + height: 100%;
6 + overflow: hidden;
7 + background-color: #383838;
8 + font-family: Sans-Serif;
9 +}
10 +ul,
11 +li {
12 + list-style: none;
13 + margin: 0;
14 + padding: 0;
15 +}
16 +input[type='button'],
17 +button {
18 + -webkit-appearance: none;
19 + -moz-appearance: none;
20 + background-color: #fff;
21 +}
22 +input[type='file'] {
23 + position: absolute;
24 + margin: 0;
25 + padding: 0;
26 + top: 0;
27 + left: 0;
28 + width: 100%;
29 + height: 100%;
30 + cursor: pointer;
31 + opacity: 0;
32 + filter: alpha(opacity=0);
33 +}
34 +.header {
35 + position: fixed;
36 + left: 0;
37 + top: 0;
38 + width: 100%;
39 + background-color: #fff;
40 + text-align: center;
41 + z-index: 9999;
42 +}
43 +.header .logo {
44 + margin: 10px 5px;
45 + width: 180px;
46 + vertical-align: middle;
47 +}
48 +.header .name {
49 + font-size: 16px;
50 + font-weight: bold;
51 +}
52 +.header .menu {
53 + padding: 10px;
54 + background-color: #000;
55 +}
56 +.header .menu input {
57 + opacity: 0;
58 +}
59 +.header .menu img {
60 + width: 20px;
61 + height: 20px;
62 + vertical-align: middle;
63 +}
64 +.header .button {
65 + position: relative;
66 + display: inline-block;
67 + margin: 0 5px;
68 + padding: 0;
69 + border-radius: 5px 5px;
70 + width: 30px;
71 + height: 30px;
72 + border: 0;
73 + background-color: #fff;
74 + vertical-align: middle;
75 +}
76 +.header .button.disabled img {
77 + opacity: 0.5;
78 +}
79 +.tui-image-editor {
80 + height: 100%;
81 +}
82 +.tui-image-editor-canvas-container {
83 + margin: 0 auto;
84 + top: 50%;
85 + transform: translateY(-50%);
86 + -ms-transform: translateY(-50%);
87 + -moz-transform: translateY(-50%);
88 + -webkit-transform: translateY(-50%);
89 + overflow: hidden;
90 +}
91 +.tui-image-editor-controls {
92 + position: fixed;
93 + width: 100%;
94 + left: 0;
95 + bottom: 0;
96 + background-color: #fff;
97 +}
98 +.tui-image-editor-controls .scrollable {
99 + display: inline-block;
100 + overflow-x: auto;
101 + width: 100%;
102 + height: 100%;
103 + white-space: nowrap;
104 + font-size: 0;
105 + background-color: #000;
106 + vertical-align: middle;
107 +}
108 +.tui-image-editor-controls .no-scrollable {
109 + overflow-x: hidden;
110 +}
111 +.tui-image-editor-controls .menu-item {
112 + display: inline-block;
113 + height: 80px;
114 + border-right: 1px solid #383838;
115 + background-color: #ddd;
116 + vertical-align: middle;
117 +}
118 +.tui-image-editor-controls .menu-button {
119 + width: 80px;
120 + height: 80px;
121 + border: none;
122 + vertical-align: middle;
123 + background-color: #000;
124 + color: #fff;
125 + font-size: 12px;
126 + font-weight: bold;
127 + outline: 0;
128 +}
129 +.tui-image-editor-controls .submenu-button {
130 + width: 80px;
131 + height: 80px;
132 + border: none;
133 + background-color: #ddd;
134 + vertical-align: middle;
135 +}
136 +.tui-image-editor-controls .hiddenmenu-button {
137 + margin: 0 10px;
138 + padding: 5px;
139 + border: none;
140 + color: #fff;
141 + background-color: rgba(255, 255, 255, 0);
142 +}
143 +.tui-image-editor-controls .submenu {
144 + display: none;
145 + position: absolute;
146 + top: 0;
147 + left: 0;
148 + width: 100%;
149 + font-size: 0;
150 +}
151 +.tui-image-editor-controls .submenu.show {
152 + display: block;
153 +}
154 +.tui-image-editor-controls .submenu .menu-item:last-child {
155 + margin-right: 50px;
156 +}
157 +.tui-image-editor-controls .hiddenmenu {
158 + position: absolute;
159 + display: none;
160 + padding: 40px;
161 + width: 100%;
162 + left: 0;
163 + bottom: 80px;
164 + background-color: rgba(0, 0, 0, 0.7);
165 + text-align: center;
166 + -webkit-box-sizing: border-box;
167 + -moz-box-sizing: border-box;
168 + box-sizing: border-box;
169 + z-index: 9999;
170 +}
171 +.tui-image-editor-controls .hiddenmenu.show {
172 + display: block;
173 +}
174 +.tui-image-editor-controls .hiddenmenu .top {
175 + font-size: 12px;
176 + color: #fff;
177 + margin-bottom: 20px;
178 +}
179 +.tui-image-editor-controls .btn-prev {
180 + display: inline-block;
181 + width: 30px;
182 + height: 80px;
183 + background-color: #000;
184 + color: #fff;
185 + border: none;
186 + vertical-align: middle;
187 +}
188 +.tui-image-editor-controls .tui-colorpicker-container {
189 + display: inline-block;
190 +}
191 +.tui-image-editor-controls .msg {
192 + position: absolute;
193 + margin-left: 50%;
194 + padding: 5px 10px;
195 + left: -86px;
196 + top: -50px;
197 + border-radius: 5px 5px;
198 + background-color: rgba(255, 255, 255, 0.5);
199 + font-size: 12px;
200 +}
201 +.tui-image-editor-controls .msg.hide {
202 + display: none;
203 +}
1 +body {
2 + margin: 0;
3 + padding: 0;
4 +}
5 +
6 +.code-description {
7 + padding: 22px 52px;
8 + background-color: rgba(81, 92, 230, 0.1);
9 + line-height: 1.4em;
10 +}
11 +
12 +.code-description,
13 +.code-description a {
14 + font-family: Arial;
15 + font-size: 14px;
16 + color: #515ce6;
17 +}
18 +
19 +.code-html {
20 + padding: 20px 52px;
21 +}
1 +<!DOCTYPE html>
2 +<html>
3 + <head>
4 + <meta charset="UTF-8" />
5 + <title>0. Design</title>
6 + <link
7 + type="text/css"
8 + href="https://uicdn.toast.com/tui-color-picker/v2.2.6/tui-color-picker.css"
9 + rel="stylesheet"
10 + />
11 + <link type="text/css" href="../dist/tui-image-editor.css" rel="stylesheet" />
12 + <style>
13 + @import url(http://fonts.googleapis.com/css?family=Noto+Sans);
14 + html,
15 + body {
16 + height: 100%;
17 + margin: 0;
18 + }
19 + </style>
20 + </head>
21 + <body>
22 + <div id="tui-image-editor-container"></div>
23 + <script
24 + type="text/javascript"
25 + src="https://api-storage.cloud.toast.com/v1/AUTH_e18353c4ea5746c097143946d0644e61/toast-ui-cdn/tui-image-editor/v3.11.0/example/fabric-v4.2.0.js"
26 + ></script>
27 + <script
28 + type="text/javascript"
29 + src="https://uicdn.toast.com/tui.code-snippet/v1.5.0/tui-code-snippet.min.js"
30 + ></script>
31 + <script
32 + type="text/javascript"
33 + src="https://uicdn.toast.com/tui-color-picker/v2.2.6/tui-color-picker.js"
34 + ></script>
35 + <script
36 + type="text/javascript"
37 + src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.3/FileSaver.min.js"
38 + ></script>
39 + <script type="text/javascript" src="../dist/tui-image-editor.js"></script>
40 + <script type="text/javascript" src="./js/theme/white-theme.js"></script>
41 + <script type="text/javascript" src="./js/theme/black-theme.js"></script>
42 + <script>
43 + // Image editor
44 + var imageEditor = new tui.ImageEditor('#tui-image-editor-container', {
45 + includeUI: {
46 + loadImage: {
47 + path: 'img/sampleImage2.png',
48 + name: 'SampleImage',
49 + },
50 + theme: blackTheme, // or whiteTheme
51 + initMenu: 'filter',
52 + menuBarPosition: 'bottom',
53 + },
54 + cssMaxWidth: 700,
55 + cssMaxHeight: 500,
56 + usageStatistics: false,
57 + });
58 + window.onresize = function () {
59 + imageEditor.ui.resizeEditor();
60 + };
61 + </script>
62 + </body>
63 +</html>
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
1 +{
2 + "example01-includeUi": {
3 + "title": "1. Include ui"
4 + },
5 + "example02-useApiDirect": {
6 + "title": "2. Use api direct (basic)"
7 + },
8 + "example03-mobile": {
9 + "title": "3. Mobile"
10 + }
11 +}
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
1 +var blackTheme = {
2 + 'common.bi.image': 'https://uicdn.toast.com/toastui/img/tui-image-editor-bi.png',
3 + 'common.bisize.width': '251px',
4 + 'common.bisize.height': '21px',
5 + 'common.backgroundImage': 'none',
6 + 'common.backgroundColor': '#1e1e1e',
7 + 'common.border': '0px',
8 +
9 + // header
10 + 'header.backgroundImage': 'none',
11 + 'header.backgroundColor': 'transparent',
12 + 'header.border': '0px',
13 +
14 + // load button
15 + 'loadButton.backgroundColor': '#fff',
16 + 'loadButton.border': '1px solid #ddd',
17 + 'loadButton.color': '#222',
18 + 'loadButton.fontFamily': "'Noto Sans', sans-serif",
19 + 'loadButton.fontSize': '12px',
20 +
21 + // download button
22 + 'downloadButton.backgroundColor': '#fdba3b',
23 + 'downloadButton.border': '1px solid #fdba3b',
24 + 'downloadButton.color': '#fff',
25 + 'downloadButton.fontFamily': "'Noto Sans', sans-serif",
26 + 'downloadButton.fontSize': '12px',
27 +
28 + // main icons
29 + 'menu.normalIcon.color': '#8a8a8a',
30 + 'menu.activeIcon.color': '#555555',
31 + 'menu.disabledIcon.color': '#434343',
32 + 'menu.hoverIcon.color': '#e9e9e9',
33 + 'menu.iconSize.width': '24px',
34 + 'menu.iconSize.height': '24px',
35 +
36 + // submenu icons
37 + 'submenu.normalIcon.color': '#8a8a8a',
38 + 'submenu.activeIcon.color': '#e9e9e9',
39 + 'submenu.iconSize.width': '32px',
40 + 'submenu.iconSize.height': '32px',
41 +
42 + // submenu primary color
43 + 'submenu.backgroundColor': '#1e1e1e',
44 + 'submenu.partition.color': '#3c3c3c',
45 +
46 + // submenu labels
47 + 'submenu.normalLabel.color': '#8a8a8a',
48 + 'submenu.normalLabel.fontWeight': 'lighter',
49 + 'submenu.activeLabel.color': '#fff',
50 + 'submenu.activeLabel.fontWeight': 'lighter',
51 +
52 + // checkbox style
53 + 'checkbox.border': '0px',
54 + 'checkbox.backgroundColor': '#fff',
55 +
56 + // range style
57 + 'range.pointer.color': '#fff',
58 + 'range.bar.color': '#666',
59 + 'range.subbar.color': '#d1d1d1',
60 +
61 + 'range.disabledPointer.color': '#414141',
62 + 'range.disabledBar.color': '#282828',
63 + 'range.disabledSubbar.color': '#414141',
64 +
65 + 'range.value.color': '#fff',
66 + 'range.value.fontWeight': 'lighter',
67 + 'range.value.fontSize': '11px',
68 + 'range.value.border': '1px solid #353535',
69 + 'range.value.backgroundColor': '#151515',
70 + 'range.title.color': '#fff',
71 + 'range.title.fontWeight': 'lighter',
72 +
73 + // colorpicker style
74 + 'colorpicker.button.border': '1px solid #1e1e1e',
75 + 'colorpicker.title.color': '#fff',
76 +};
1 +var whiteTheme = {
2 + 'common.bi.image': 'https://uicdn.toast.com/toastui/img/tui-image-editor-bi.png',
3 + 'common.bisize.width': '251px',
4 + 'common.bisize.height': '21px',
5 + 'common.backgroundImage': './img/bg.png',
6 + 'common.backgroundColor': '#fff',
7 + 'common.border': '1px solid #c1c1c1',
8 +
9 + // header
10 + 'header.backgroundImage': 'none',
11 + 'header.backgroundColor': 'transparent',
12 + 'header.border': '0px',
13 +
14 + // load button
15 + 'loadButton.backgroundColor': '#fff',
16 + 'loadButton.border': '1px solid #ddd',
17 + 'loadButton.color': '#222',
18 + 'loadButton.fontFamily': "'Noto Sans', sans-serif",
19 + 'loadButton.fontSize': '12px',
20 +
21 + // download button
22 + 'downloadButton.backgroundColor': '#fdba3b',
23 + 'downloadButton.border': '1px solid #fdba3b',
24 + 'downloadButton.color': '#fff',
25 + 'downloadButton.fontFamily': "'Noto Sans', sans-serif",
26 + 'downloadButton.fontSize': '12px',
27 +
28 + // main icons
29 + 'menu.normalIcon.color': '#8a8a8a',
30 + 'menu.activeIcon.color': '#555555',
31 + 'menu.disabledIcon.color': '#434343',
32 + 'menu.hoverIcon.color': '#e9e9e9',
33 + 'menu.iconSize.width': '24px',
34 + 'menu.iconSize.height': '24px',
35 +
36 + // submenu icons
37 + 'submenu.normalIcon.color': '#8a8a8a',
38 + 'submenu.activeIcon.color': '#555555',
39 + 'submenu.iconSize.width': '32px',
40 + 'submenu.iconSize.height': '32px',
41 +
42 + // submenu primary color
43 + 'submenu.backgroundColor': 'transparent',
44 + 'submenu.partition.color': '#e5e5e5',
45 +
46 + // submenu labels
47 + 'submenu.normalLabel.color': '#858585',
48 + 'submenu.normalLabel.fontWeight': 'normal',
49 + 'submenu.activeLabel.color': '#000',
50 + 'submenu.activeLabel.fontWeight': 'normal',
51 +
52 + // checkbox style
53 + 'checkbox.border': '1px solid #ccc',
54 + 'checkbox.backgroundColor': '#fff',
55 +
56 + // rango style
57 + 'range.pointer.color': '#333',
58 + 'range.bar.color': '#ccc',
59 + 'range.subbar.color': '#606060',
60 +
61 + 'range.disabledPointer.color': '#d3d3d3',
62 + 'range.disabledBar.color': 'rgba(85,85,85,0.06)',
63 + 'range.disabledSubbar.color': 'rgba(51,51,51,0.2)',
64 +
65 + 'range.value.color': '#000',
66 + 'range.value.fontWeight': 'normal',
67 + 'range.value.fontSize': '11px',
68 + 'range.value.border': '0',
69 + 'range.value.backgroundColor': '#f5f5f5',
70 + 'range.title.color': '#000',
71 + 'range.title.fontWeight': 'lighter',
72 +
73 + // colorpicker style
74 + 'colorpicker.button.border': '0px',
75 + 'colorpicker.title.color': '#000',
76 +};
1 +// Type definitions for TOAST UI Image Editor v3.9.0
2 +// TypeScript Version: 3.2.2
3 +
4 +declare namespace tuiImageEditor {
5 + type AngleType = number;
6 +
7 + interface IThemeConfig {
8 + 'common.bi.image'?: string;
9 + 'common.bisize.width'?: string;
10 + 'common.bisize.height'?: string;
11 + 'common.backgroundImage'?: string;
12 + 'common.backgroundColor'?: string;
13 + 'common.border'?: string;
14 + 'header.backgroundImage'?: string;
15 + 'header.backgroundColor'?: string;
16 + 'header.border'?: string;
17 + 'loadButton.backgroundColor'?: string;
18 + 'loadButton.border'?: string;
19 + 'loadButton.color'?: string;
20 + 'loadButton.fontFamily'?: string;
21 + 'loadButton.fontSize'?: string;
22 + 'downloadButton.backgroundColor'?: string;
23 + 'downloadButton.border'?: string;
24 + 'downloadButton.color'?: string;
25 + 'downloadButton.fontFamily'?: string;
26 + 'downloadButton.fontSize'?: string;
27 + 'menu.normalIcon.path'?: string;
28 + 'menu.normalIcon.name'?: string;
29 + 'menu.activeIcon.path'?: string;
30 + 'menu.activeIcon.name'?: string;
31 + 'menu.iconSize.width'?: string;
32 + 'menu.iconSize.height'?: string;
33 + 'submenu.backgroundColor'?: string;
34 + 'submenu.partition.color'?: string;
35 + 'submenu.normalIcon.path'?: string;
36 + 'submenu.normalIcon.name'?: string;
37 + 'submenu.activeIcon.path'?: string;
38 + 'submenu.activeIcon.name'?: string;
39 + 'submenu.iconSize.width'?: string;
40 + 'submenu.iconSize.height'?: string;
41 + 'submenu.normalLabel.color'?: string;
42 + 'submenu.normalLabel.fontWeight'?: string;
43 + 'submenu.activeLabel.color'?: string;
44 + 'submenu.activeLabel.fontWeight'?: string;
45 + 'checkbox.border'?: string;
46 + 'checkbox.backgroundColor'?: string;
47 + 'range.pointer.color'?: string;
48 + 'range.bar.color'?: string;
49 + 'range.subbar.color'?: string;
50 + 'range.value.color'?: string;
51 + 'range.value.fontWeight'?: string;
52 + 'range.value.fontSize'?: string;
53 + 'range.value.border'?: string;
54 + 'range.value.backgroundColor'?: string;
55 + 'range.title.color'?: string;
56 + 'range.title.fontWeight'?: string;
57 + 'colorpicker.button.border'?: string;
58 + 'colorpicker.title.color'?: string;
59 + }
60 +
61 + interface IIconInfo {
62 + [propName: string]: string;
63 + }
64 +
65 + interface IIconOptions {
66 + fill?: string;
67 + left?: number;
68 + top?: number;
69 + }
70 +
71 + interface IShapeOptions {
72 + fill?: string;
73 + stroke?: string;
74 + strokeWidth?: number;
75 + width?: number;
76 + height?: number;
77 + rx?: number;
78 + ry?: number;
79 + left?: number;
80 + top?: number;
81 + isRegular?: boolean;
82 + }
83 +
84 + interface IGenerateTextOptions {
85 + styles?: ITextStyleConfig;
86 + position?: {
87 + x: number;
88 + y: number;
89 + };
90 + }
91 +
92 + interface ITextStyleConfig {
93 + fill?: string;
94 + fontFamily?: string;
95 + fontSize?: number;
96 + fontStyle?: string;
97 + fontWeight?: string;
98 + textAlign?: string;
99 + textDecoration?: string;
100 + }
101 +
102 + interface IRectConfig {
103 + left: number;
104 + top: number;
105 + width: number;
106 + height: number;
107 + }
108 +
109 + interface ICanvasSize {
110 + width: number;
111 + height: number;
112 + }
113 +
114 + interface IBrushOptions {
115 + width: number;
116 + color: string;
117 + }
118 +
119 + interface IPositionConfig {
120 + x: number;
121 + y: number;
122 + originX: string;
123 + originY: string;
124 + }
125 +
126 + interface IToDataURLOptions {
127 + format?: string;
128 + quality?: number;
129 + multiplier?: number;
130 + left?: number;
131 + top?: number;
132 + width?: number;
133 + height?: number;
134 + }
135 +
136 + interface IGraphicObjectProps {
137 + id?: number;
138 + type?: string;
139 + text?: string;
140 + left?: string | number;
141 + top?: string | number;
142 + width?: string | number;
143 + height?: string | number;
144 + fill?: string;
145 + stroke?: string;
146 + strokeWidth?: string | number;
147 + fontFamily?: string;
148 + fontSize?: number;
149 + fontStyle?: string;
150 + fontWeight?: string;
151 + textAlign?: string;
152 + textDecoration?: string;
153 + opacity?: number;
154 + [propName: string]: number | string | boolean | undefined;
155 + }
156 +
157 + interface IIncludeUIOptions {
158 + loadImage?: {
159 + path: string;
160 + name: string;
161 + };
162 + theme?: IThemeConfig;
163 + menu?: string[];
164 + initMenu?: string;
165 + uiSize?: {
166 + width: string;
167 + height: string;
168 + };
169 + menuBarPosition?: string;
170 + usageStatistics?: boolean;
171 + }
172 +
173 + interface ISelectionStyleConfig {
174 + cornerStyle?: string;
175 + cornerSize?: number;
176 + cornerColor?: string;
177 + cornerStrokeColor?: string;
178 + transparentCorners?: boolean;
179 + lineWidth?: number;
180 + borderColor?: string;
181 + rotatingPointOffset?: number;
182 + }
183 +
184 + interface IObjectProps {
185 + // icon, shape
186 + fill: string;
187 + height: number;
188 + id: number;
189 + left: number;
190 + opacity: number;
191 + stroke: string | null;
192 + strokeWidth: number | null;
193 + top: number;
194 + type: string;
195 + width: number;
196 + }
197 +
198 + interface ITextObjectProps extends IObjectProps {
199 + fontFamily: string;
200 + fontSize: string;
201 + fontStyle: string;
202 + text: string;
203 + textAlign: string;
204 + textDecoration: string;
205 + }
206 +
207 + interface IFilterResolveObject {
208 + type: string;
209 + action: string;
210 + }
211 +
212 + interface ICropResolveObject {
213 + oldWidth: number;
214 + oldHeight: number;
215 + newWidth: number;
216 + newHeight: number;
217 + }
218 +
219 + interface IFlipXYResolveObject {
220 + flipX: boolean;
221 + flipY: boolean;
222 + angle: AngleType;
223 + }
224 +
225 + interface IOptions {
226 + includeUI?: IIncludeUIOptions;
227 + cssMaxWidth?: number;
228 + cssMaxHeight?: number;
229 + usageStatistics?: boolean;
230 + selectionStyle?: ISelectionStyleConfig;
231 + }
232 +
233 + interface IUIDimension {
234 + height?: string;
235 + width?: string;
236 + }
237 +
238 + interface IImageDimension {
239 + oldHeight?: number;
240 + oldWidth?: number;
241 + newHeight?: number;
242 + newWidth?: number;
243 + }
244 +
245 + interface IEditorSize {
246 + uiSize?: IUIDimension;
247 + imageSize?: IImageDimension;
248 + }
249 +
250 + interface UI {
251 + resizeEditor(dimension: IEditorSize): Promise<void>;
252 + }
253 +
254 + class ImageEditor {
255 + constructor(wrapper: string | Element, options: IOptions);
256 + public ui: UI;
257 +
258 + public addIcon(type: string, options?: IIconOptions): Promise<IObjectProps>;
259 + public addImageObject(imgUrl: string): Promise<void>;
260 + public addShape(type: string, options?: IShapeOptions): Promise<IObjectProps>;
261 + public addText(text: string, options?: IGenerateTextOptions): Promise<ITextObjectProps>;
262 + public applyFilter(
263 + type: string,
264 + options?: {
265 + maskObjId: number;
266 + },
267 + isSilent?: boolean
268 + ): Promise<IFilterResolveObject>;
269 + public changeCursor(cursorType: string): void;
270 + public changeIconColor(id: number, color: string): Promise<void>;
271 + public changeSelectableAll(selectable: boolean): void;
272 + public changeShape(id: number, options?: IShapeOptions, isSilent?: boolean): Promise<void>;
273 + public changeText(id: number, text?: string): Promise<void>;
274 + public changeTextStyle(
275 + id: number,
276 + styleObj: ITextStyleConfig,
277 + isSilent?: boolean
278 + ): Promise<void>;
279 + public clearObjects(): Promise<void>;
280 + public clearRedoStack(): void;
281 + public clearUndoStack(): void;
282 + public crop(rect: IRectConfig): Promise<ICropResolveObject>;
283 + public deactivateAll(): void;
284 + public destroy(): void;
285 + public discardSelection(): void;
286 + public flipX(): Promise<IFlipXYResolveObject>;
287 + public flipY(): Promise<IFlipXYResolveObject>;
288 + public getCanvasSize(): ICanvasSize;
289 + public getCropzoneRect(): IRectConfig;
290 + public getDrawingMode(): string;
291 + public getImageName(): string;
292 + public getObjectPosition(id: number, originX: string, originY: string): ICanvasSize;
293 + public getObjectProperties(
294 + id: number,
295 + keys: string | string[] | IGraphicObjectProps
296 + ): IGraphicObjectProps;
297 + public hasFilter(type: string): boolean;
298 + public isEmptyRedoStack(): boolean;
299 + public isEmptyUndoStack(): boolean;
300 + public loadImageFromFile(imgFile: File, imageName?: string): Promise<ICropResolveObject>;
301 + public loadImageFromURL(url: string, imageName?: string): Promise<ICropResolveObject>;
302 + public redo(): Promise<any>;
303 + public registerIcons(infos: IIconInfo): void;
304 + public removeActiveObject(): void;
305 + public removeFilter(type?: string): Promise<IFilterResolveObject>;
306 + public removeObject(id: number): Promise<void>;
307 + public resetFlip(): Promise<IFlipXYResolveObject>;
308 + public resizeCanvasDimension(dimension: ICanvasSize): Promise<void>;
309 + public rotate(angle: AngleType, isSilent?: boolean): Promise<AngleType>;
310 + public setAngle(angle: AngleType, isSilent?: boolean): Promise<AngleType>;
311 + public setBrush(option: IBrushOptions): void;
312 + public setCropzoneRect(mode?: number): void;
313 + public setDrawingShape(type: string, options?: IShapeOptions): void;
314 + public setObjectPosition(id: number, posInfo?: IPositionConfig): Promise<void>;
315 + public setObjectProperties(id: number, keyValue?: IGraphicObjectProps): Promise<void>;
316 + public setObjectPropertiesQuietly(id: number, keyValue?: IGraphicObjectProps): Promise<void>;
317 + public startDrawingMode(mode: string, option?: { width?: number; color?: string }): boolean;
318 + public stopDrawingMode(): void;
319 + public toDataURL(options?: IToDataURLOptions): string;
320 + public undo(): Promise<any>;
321 + public on(eventName: string, handler: (...args: any[]) => void): void;
322 + }
323 +}
324 +
325 +declare module 'tui-image-editor' {
326 + export = tuiImageEditor.ImageEditor;
327 +}
1 +{
2 + "source": {
3 + "include": ["src", "README.md"],
4 + "exclude": [],
5 + "includePattern": ".+\\.js(doc)?$",
6 + "excludePattern": "(^|\\/|\\\\)_"
7 + },
8 + "plugins": ["plugins/markdown"],
9 + "templates": {
10 + "name": "ImageEditor",
11 + "logo": {
12 + "url": "https://user-images.githubusercontent.com/35218826/40895380-0b9f4cd6-67ea-11e8-982f-18121daa3a04.png",
13 + "width": "150px",
14 + "height": "13px",
15 + "link": "https://github.com/nhn/tui.jsdoc-template"
16 + }
17 + },
18 + "opts": {
19 + "private": false,
20 + "recurse": true,
21 + "destination": "doc",
22 + "tutorials": "examples",
23 + "template": "./node_modules/tui-jsdoc-template",
24 + "package": "package.json"
25 + }
26 +}
1 +/* eslint-disable consts-on-top, no-process-env, require-jsdoc */
2 +/* eslint-disable no-process-env, require-jsdoc */
3 +const webdriverConfig = {
4 + hostname: 'fe.nhnent.com',
5 + port: 4444,
6 + remoteHost: true,
7 +};
8 +
9 +function setConfig(defaultConfig, server) {
10 + if (server === 'ne') {
11 + defaultConfig.customLaunchers = {
12 + IE9: {
13 + base: 'WebDriver',
14 + config: webdriverConfig,
15 + browserName: 'internet explorer',
16 + version: '9',
17 + },
18 + IE10: {
19 + base: 'WebDriver',
20 + config: webdriverConfig,
21 + browserName: 'internet explorer',
22 + version: '10',
23 + },
24 + IE11: {
25 + base: 'WebDriver',
26 + config: webdriverConfig,
27 + browserName: 'internet explorer',
28 + version: '11',
29 + },
30 + Edge: {
31 + base: 'WebDriver',
32 + config: webdriverConfig,
33 + browserName: 'MicrosoftEdge',
34 + },
35 + 'Chrome-WebDriver': {
36 + base: 'WebDriver',
37 + config: webdriverConfig,
38 + browserName: 'chrome',
39 + },
40 + 'Firefox-WebDriver': {
41 + base: 'WebDriver',
42 + config: webdriverConfig,
43 + browserName: 'firefox',
44 + },
45 + 'Safari-WebDriver': {
46 + base: 'WebDriver',
47 + config: webdriverConfig,
48 + browserName: 'safari',
49 + },
50 + };
51 + defaultConfig.browsers = [
52 + 'IE9',
53 + 'IE10',
54 + // 'IE11',
55 + // 'Edge',
56 + 'Chrome-WebDriver',
57 + 'Firefox-WebDriver',
58 + // 'Safari-WebDriver'
59 + ];
60 + defaultConfig.reporters.push('coverage');
61 + defaultConfig.reporters.push('junit');
62 + defaultConfig.coverageReporter = {
63 + dir: 'report/coverage/',
64 + reporters: [
65 + {
66 + type: 'html',
67 + subdir(browser) {
68 + return `report-html/${browser}`;
69 + },
70 + },
71 + {
72 + type: 'cobertura',
73 + subdir(browser) {
74 + return `report-cobertura/${browser}`;
75 + },
76 + file: 'cobertura.txt',
77 + },
78 + ],
79 + };
80 + defaultConfig.junitReporter = {
81 + outputDir: 'report/junit',
82 + suite: '',
83 + };
84 + } else {
85 + defaultConfig.browsers = ['ChromeHeadless'];
86 + }
87 +}
88 +
89 +module.exports = function (config) {
90 + const defaultConfig = {
91 + basePath: './',
92 + frameworks: ['jasmine', 'jquery-3.2.1', 'es5-shim'],
93 + files: [
94 + // reason for not using karma-jasmine-jquery framework is that including older jasmine-karma file
95 + // included jasmine-karma version is 2.0.5 and this version don't support ie8
96 + 'node_modules/jasmine-jquery/lib/jasmine-jquery.js',
97 + 'node_modules/fabric/dist/fabric.js',
98 + 'test/index.js',
99 + {
100 + pattern: 'test/fixtures/*.jpg',
101 + watched: false,
102 + included: false,
103 + served: true,
104 + },
105 + {
106 + pattern: 'test/fixtures/*.png',
107 + watched: false,
108 + included: false,
109 + served: true,
110 + },
111 + {
112 + pattern: 'test/fixtures/*.svg',
113 + watched: false,
114 + included: false,
115 + served: true,
116 + },
117 + ],
118 + preprocessors: {
119 + 'test/index.js': ['webpack', 'sourcemap'],
120 + },
121 + reporters: ['dots'],
122 + webpack: {
123 + mode: 'development',
124 + devtool: 'inline-source-map',
125 + externals: {
126 + fabric: 'fabric',
127 + },
128 + module: {
129 + rules: [
130 + {
131 + test: /\.js$/,
132 + include: /src/,
133 + exclude: /node_modules/,
134 + loader: 'eslint-loader',
135 + enforce: 'pre',
136 + },
137 + {
138 + test: /\.js$/,
139 + exclude: /(test|node_modules)/,
140 + loader: 'istanbul-instrumenter-loader',
141 + query: {
142 + esModules: true,
143 + },
144 + },
145 + {
146 + test: /\.js$/,
147 + exclude: /node_modules/,
148 + loader: 'babel-loader?cacheDirectory',
149 + options: {
150 + babelrc: true,
151 + },
152 + },
153 + {
154 + test: /\.styl$/,
155 + use: ['css-loader', 'stylus-loader'],
156 + },
157 + {
158 + test: /\.svg$/,
159 + loader: 'svg-inline-loader',
160 + },
161 + ],
162 + },
163 + },
164 + port: 9876,
165 + colors: true,
166 + logLevel: config.LOG_INFO,
167 + autoWatch: true,
168 + singleRun: true,
169 + };
170 +
171 + /* eslint-disable */
172 + setConfig(defaultConfig, process.env.KARMA_SERVER);
173 + config.set(defaultConfig);
174 +};
1 +const fs = require('fs');
2 +const mkdirp = require('mkdirp');
3 +const svgstore = require('svgstore');
4 +const svgDir = './src/svg';
5 +
6 +function getFileList(dir) {
7 + const targetDir = `${svgDir}/${dir}`;
8 + const sprites = svgstore();
9 + fs.readdir(targetDir, (err, files) => {
10 + if (!files) return;
11 + files.forEach((file) => {
12 + if (file.match(/^\./)) return;
13 + const id = `${dir}-${file.replace(/\.svg$/, '')}`;
14 + const svg = fs.readFileSync(`${targetDir}/${file}`);
15 + sprites.add(id, svg);
16 + });
17 + fs.writeFileSync(`./dist/svg/${dir}.svg`, sprites);
18 + });
19 +}
20 +
21 +mkdirp('./dist/svg', (mkdirpErr) => {
22 + if (mkdirpErr) {
23 + console.error(mkdirpErr);
24 + } else {
25 + fs.readdir(svgDir, (err, dirs) => {
26 + dirs.forEach((dir) => {
27 + getFileList(dir);
28 + });
29 + });
30 + }
31 +});
This diff is collapsed. Click to expand it.
...@@ -6,7 +6,9 @@ ...@@ -6,7 +6,9 @@
6 "@testing-library/jest-dom": "^5.11.6", 6 "@testing-library/jest-dom": "^5.11.6",
7 "@testing-library/react": "^11.2.2", 7 "@testing-library/react": "^11.2.2",
8 "@testing-library/user-event": "^12.5.0", 8 "@testing-library/user-event": "^12.5.0",
9 + "@toast-ui/react-image-editor": "^1.3.0",
9 "antd": "^4.9.2", 10 "antd": "^4.9.2",
11 + "eslint-plugin-prettier": "^3.2.0",
10 "react": "^17.0.1", 12 "react": "^17.0.1",
11 "react-dom": "^17.0.1", 13 "react-dom": "^17.0.1",
12 "react-scripts": "4.0.1", 14 "react-scripts": "4.0.1",
...@@ -35,5 +37,9 @@ ...@@ -35,5 +37,9 @@
35 "last 1 firefox version", 37 "last 1 firefox version",
36 "last 1 safari version" 38 "last 1 safari version"
37 ] 39 ]
40 + },
41 + "devDependencies": {
42 + "eslint": "^7.15.0",
43 + "eslint-config-tui": "^3.1.0"
38 } 44 }
39 } 45 }
......
1 +/* ICON BUTTON */
2 +.tie-icon-add-button
3 + &.icon-bubble .{prefix}-button[data-icontype="icon-bubble"] svg > use.active,
4 + &.icon-heart .{prefix}-button[data-icontype="icon-heart"] svg > use.active,
5 + &.icon-location .{prefix}-button[data-icontype="icon-location"] svg > use.active,
6 + &.icon-polygon .{prefix}-button[data-icontype="icon-polygon"] svg > use.active,
7 + &.icon-star .{prefix}-button[data-icontype="icon-star"] svg > use.active,
8 + &.icon-star-2 .{prefix}-button[data-icontype="icon-star-2"] svg > use.active,
9 + &.icon-arrow-3 .{prefix}-button[data-icontype="icon-arrow-3"] svg > use.active,
10 + &.icon-arrow-2 .{prefix}-button[data-icontype="icon-arrow-2"] svg > use.active,
11 + &.icon-arrow .{prefix}-button[data-icontype="icon-arrow"] svg > use.active,
12 + &.icon-bubble .{prefix}-button[data-icontype="icon-bubble"] svg > use.active
13 + display: block;
14 +
15 +/* DRAW BUTTON */
16 +.tie-draw-line-select-button
17 + &.line .{prefix}-button.line svg > use.normal,
18 + &.free .{prefix}-button.free svg > use.normal
19 + display: none;
20 +
21 + &.line .{prefix}-button.line svg > use.active,
22 + &.free .{prefix}-button.free svg > use.active
23 + display: block;
24 +
25 +/* FLIP BUTTON */
26 +.tie-flip-button
27 + &.resetFlip .{prefix}-button.resetFlip,
28 + &.flipX .{prefix}-button.flipX,
29 + &.flipY .{prefix}-button.flipY
30 + svg > use.normal
31 + display: none;
32 + svg > use.active
33 + display: block;
34 +
35 +/* MASK BUTTON */
36 +.tie-mask-apply.apply.active .{prefix}-button.apply
37 + label
38 + color: #fff;
39 + svg > use.active
40 + display: block;
41 +
42 +/* CROP BUTTON */
43 +.tie-crop-button,
44 +.tie-crop-preset-button
45 + .{prefix}-button.apply
46 + margin-right: 24px;
47 + .{prefix}-button.preset.active svg > use.active
48 + display: block;
49 + .{prefix}-button.apply.active svg > use.active
50 + display: block;
51 +
52 +
53 +/* SHAPE BUTTON */
54 +.tie-shape-button
55 + &.rect .{prefix}-button.rect,
56 + &.circle .{prefix}-button.circle,
57 + &.triangle .{prefix}-button.triangle
58 + svg > use.normal
59 + display: none;
60 + svg > use.active
61 + display: block;
62 +
63 +/* TEXT BUTTON */
64 +.tie-text-effect-button
65 + .{prefix}-button.active svg > use.active
66 + display: block;
67 +.tie-text-align-button
68 + &.left .{prefix}-button.left svg > use.active,
69 + &.center .{prefix}-button.center svg > use.active,
70 + &.right .{prefix}-button.right svg > use.active
71 + display: block;
72 +.tie-mask-image-file,
73 +.tie-icon-image-file
74 + opacity: 0;
75 + position: absolute;
76 + width: 100%;
77 + height: 100%;
78 + border: 1px solid green;
79 + cursor: inherit;
80 + left: 0;
81 + top: 0;
1 +/* VIRTUAL CHECKBOX */
2 +.{prefix}-container
3 + .filter-color-item
4 + display: inline-block;
5 + .tui-image-editor-checkbox
6 + display: block;
7 + .{prefix}-checkbox-wrap
8 + display: inline-block !important;
9 + text-align: left;
10 + .{prefix}-checkbox-wrap.fixed-width
11 + width: 187px;
12 + white-space: normal;
13 + .{prefix}-checkbox
14 + display: inline-block;
15 + margin: 1px 0 1px 0;
16 + input
17 + width: 14px;
18 + height: 14px;
19 + opacity: 0;
20 + > label > span
21 + color: #fff;
22 + height: 14px;
23 + position: relative;
24 + input + label:before,
25 + > label > span:before
26 + content: '';
27 + position: absolute;
28 + width: 14px;
29 + height: 14px;
30 + background-color: #fff;
31 + top: 6px;
32 + left: -19px;
33 + display: inline-block;
34 + margin: 0;
35 + text-align: center;
36 + font-size: 11px;
37 + border: 0;
38 + border-radius: 2px;
39 + padding-top: 1px;
40 + box-sizing: border-box;
41 + input[type='checkbox']:checked + span:before
42 + background-size: cover;
43 + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAMBJREFUKBWVkjEOwjAMRe2WgZW7IIHEDdhghhuwcQ42rlJugAQS54Cxa5cq1QM5TUpByZfS2j9+dlJVt/tX5ZxbS4ZU9VLkQvSHKTIGRaVJYFmKrBbTCJxE2UgCdDzMZDkHrOV6b95V0US6UmgKodujEZbJg0B0ZgEModO5lrY1TMQf1TpyJGBEjD+E2NPN7ukIUDiF/BfEXgRiGEw8NgkffYGYwCi808fpn/6OvfUfsDr/Vc1IfRf8sKnFVqeiVQfDu0tf/nWH9gAAAABJRU5ErkJggg==');
44 +
45 + .{prefix}-selectlist-wrap
46 + position: relative;
47 + select
48 + width: 100%;
49 + height: 28px;
50 + margin-top: 4px;
51 + border: 0;
52 + outline: 0;
53 + border-radius: 0;
54 + border: 1px solid #cbdbdb;
55 + background-color: #fff;
56 + -webkit-appearance: none;
57 + -moz-appearance: none;
58 + appearance: none;
59 + padding: 0 7px 0 10px;
60 + .{prefix}-selectlist
61 + display: none;
62 + position: relative;
63 + top: -1px;
64 + border: 1px solid #ccc;
65 + background-color: #fff;
66 + border-top: 0px;
67 + padding: 4px 0;
68 + li
69 + display: block;
70 + text-align: left;
71 + padding: 7px 10px;
72 + font-family: 'Noto Sans', sans-serif;
73 + li:hover
74 + background-color: rgba(81, 92, 230, 0.05);
75 + .{prefix}-selectlist-wrap:before
76 + content: '';
77 + position: absolute;
78 + display: inline-block;
79 + width: 14px;
80 + height: 14px;
81 + right: 5px;
82 + top: 10px;
83 + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAHlJREFUKBVjYBgFOEOAEVkmPDxc89+/f6eAYjzI4kD2FyYmJrOVK1deh4kzwRggGiQBVJCELAZig8SQNYHEmEEEMrh69eo1HR0dfqCYJUickZGxf9WqVf3IakBsFBthklpaWmVA9mEQhrJhUoTp0NBQCRAmrHL4qgAAuu4cWZOZIGsAAAAASUVORK5CYII=');
84 + background-size: cover;
85 + .{prefix}-selectlist-wrap select::-ms-expand
86 + display:none;
1 +/* COLOR PICKER */
2 +.{prefix}-container
3 + div.tui-colorpicker-clearfix
4 + width: 159px;
5 + height: 28px;
6 + border: 1px solid #d5d5d5;
7 + border-radius: 2px;
8 + background-color: #f5f5f5;
9 + margin-top: 6px;
10 + padding: 4px 7px 4px 7px;
11 + .tui-colorpicker-palette-hex
12 + width: 114px;
13 + background-color: #f5f5f5;
14 + border: 0;
15 + font-size: 11px;
16 + margin-top: 2px;
17 + font-family: 'Noto Sans', sans-serif;
18 + .tui-colorpicker-palette-hex[value='#ffffff'] + .tui-colorpicker-palette-preview,
19 + .tui-colorpicker-palette-hex[value=''] + .tui-colorpicker-palette-preview
20 + border: 1px solid #ccc;
21 + .tui-colorpicker-palette-hex[value=''] + .tui-colorpicker-palette-preview
22 + background-size: cover;
23 + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAdBJREFUWAnFl0FuwjAQRZ0ukiugHqFSOQNdseuKW3ALzkA4BateICvUGyCxrtRFd4WuunH/TzykaYJrnLEYaTJJsP2+x8GZZCbQrLU5mj7Bn+EP8HvnCObd+R7xBV5lWfaNON4AnsA38E94qLEt+0yiFaBzAV/Bv+Cxxr4co7hKCDpw1q9wLeNYYdlAwyn8TYt8Hme3+8D5ozcTaMCZ68PXa2tnM2sbEcOZAJhrrpl2DAcTOGNjZPSfCdzkw6JrfbiMv+osBe4y9WOedhm4jZfhbENWuxS44H9Wz/xw4WzqLOAqh1+zycgAwzEMzr5k5gaHOa9ULBwuuDkFlHI1Kl4PJ66kgIpnoywOTmRFAYcbwYk9UMApWkD8zAV5ihcwHk4Rx7gl0IFTQL0EFc+CTQ9OZHWH3YhlVJiVpTHbrTGLhTHLZVgff6s9lyBsI9KduSS83oj+34rTwJutmBmCnMsvozRwZqB5GTkBw6/jdPDu69iJ6BYk6eCcfbcgcQIK/MByaaiMqm8rHcjol2TnpWDhyAKSGdA3FrxtJUToX0ODqatetfGE+8tyEUOV8GY5dGRwLP/MBS4RHQr4bT7NRAQjlcOTfZxmv2G+c4hI8nn+Ax5PG/zhI393AAAAAElFTkSuQmCC');
24 + .tui-colorpicker-palette-preview
25 + border-radius: 100%;
26 + float: left;
27 + width: 17px;
28 + height: 17px;
29 + border: 0;
30 + .color-picker-control
31 + position: absolute;
32 + display: none;
33 + z-index: 99;
34 + width: 192px;
35 + background-color: #fff;
36 + box-shadow: 0 3px 22px 6px rgba(0, 0, 0, .15);
37 + padding: 16px;
38 + border-radius: 2px;
39 + .tui-colorpicker-palette-toggle-slider
40 + display: none;
41 + .tui-colorpicker-palette-button
42 + border: 0;
43 + border-radius: 100%;
44 + margin: 2px;
45 + background-size: cover;
46 + font-size: 1px;
47 + &[title='#ffffff']
48 + border: 1px solid #ccc;
49 + &[title='']
50 + border: 1px solid #ccc;
51 + .triangle
52 + width: 0;
53 + height: 0;
54 + border-right: 7px solid transparent;
55 + border-top: 8px solid #fff;
56 + border-left: 7px solid transparent;
57 + position: absolute;
58 + bottom: -8px;
59 + left: 84px;
60 + .tui-colorpicker-container,
61 + .tui-colorpicker-palette-container ul,
62 + .tui-colorpicker-palette-container
63 + width: 100%;
64 + height: auto;
65 +
66 +
67 + .filter-color-item
68 + .color-picker-control label
69 + font-color: #333;
70 + font-weight: normal;
71 + margin-right: 7pxleft
72 + .tui-image-editor-checkbox
73 + margin-top: 0;
74 + input + label:before,
75 + > label:before
76 + left: -16px;
77 + .color-picker
78 + width: 100%;
79 + height: auto;
80 + .color-picker-value
81 + width: 32px;
82 + height: 32px;
83 + border: 0px;
84 + border-radius: 100%;
85 + margin: auto;
86 + margin-bottom: 1px;
87 + &.transparent
88 + border: 1px solid #cbcbcb;
89 + background-size: cover;
90 + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAdBJREFUWAnFl0FuwjAQRZ0ukiugHqFSOQNdseuKW3ALzkA4BateICvUGyCxrtRFd4WuunH/TzykaYJrnLEYaTJJsP2+x8GZZCbQrLU5mj7Bn+EP8HvnCObd+R7xBV5lWfaNON4AnsA38E94qLEt+0yiFaBzAV/Bv+Cxxr4co7hKCDpw1q9wLeNYYdlAwyn8TYt8Hme3+8D5ozcTaMCZ68PXa2tnM2sbEcOZAJhrrpl2DAcTOGNjZPSfCdzkw6JrfbiMv+osBe4y9WOedhm4jZfhbENWuxS44H9Wz/xw4WzqLOAqh1+zycgAwzEMzr5k5gaHOa9ULBwuuDkFlHI1Kl4PJ66kgIpnoywOTmRFAYcbwYk9UMApWkD8zAV5ihcwHk4Rx7gl0IFTQL0EFc+CTQ9OZHWH3YhlVJiVpTHbrTGLhTHLZVgff6s9lyBsI9KduSS83oj+34rTwJutmBmCnMsvozRwZqB5GTkBw6/jdPDu69iJ6BYk6eCcfbcgcQIK/MByaaiMqm8rHcjol2TnpWDhyAKSGdA3FrxtJUToX0ODqatetfGE+8tyEUOV8GY5dGRwLP/MBS4RHQr4bT7NRAQjlcOTfZxmv2G+c4hI8nn+Ax5PG/zhI393AAAAAElFTkSuQmCC');
91 +
92 + .color-picker-value + label
93 + color: #fff;
94 +
95 + .{prefix}-submenu svg > use
96 + display: none;
97 + .{prefix}-submenu svg > use.normal
98 + display: block;
1 +/* GRID VISUAL OF FLIP AND ROTATE MENU */
2 +.{prefix}-container
3 + .{prefix}-grid-visual
4 + display: none;
5 + position: absolute;
6 + width: 100%;
7 + height: 100%;
8 + border: 1px solid rgba(255,255,255,0.7);
9 + .{prefix}-main.{prefix}-menu-flip,
10 + .{prefix}-main.{prefix}-menu-rotate
11 + .tui-image-editor
12 + transition: none;
13 + .{prefix}-main.{prefix}-menu-flip .{prefix}-grid-visual,
14 + .{prefix}-main.{prefix}-menu-rotate .{prefix}-grid-visual
15 + display: block;
16 + .{prefix}-grid-visual
17 + table
18 + width: 100%;
19 + height: 100%;
20 + border-collapse: collapse;
21 + td
22 + border: 1px solid rgba(255,255,255,0.3);
23 + td.dot:before
24 + content: '';
25 + position: absolute;
26 + box-sizing: border-box;
27 + width: 10px;
28 + height: 10px;
29 + border: 0;
30 + box-shadow: 0 0 1px 0 rgba(0,0,0,0.3);
31 + border-radius: 100%;
32 + background-color: #fff;
33 + td.dot.left-top:before
34 + top: -5px;
35 + left: -5px;
36 + td.dot.right-top:before
37 + top: -5px;
38 + right: -5px;
39 + td.dot.left-bottom:before
40 + bottom: -5px;
41 + left: -5px;
42 + td.dot.right-bottom:before
43 + bottom: -5px;
44 + right: -5px;
1 +/* ICON */
2 +.{prefix}-container
3 + .tie-icon-add-button .{prefix}-button
4 + min-width: 42px;
5 + .svg_ic-menu
6 + .svg_ic-helpmenu
7 + width: 24px;
8 + height: 24px;
9 + .svg_ic-submenu
10 + width: 32px;
11 + height: 32px;
12 + .svg_img-bi
13 + width: 257px;
14 + height: 26px;
15 +
16 + .{prefix}-controls
17 + svg > use
18 + display: none;
19 + .enabled svg:hover > use.hover
20 + .normal svg:hover > use.hover
21 + display: block;
22 + .active svg:hover > use.hover
23 + display: none;
24 + svg > use.normal
25 + display: block;
26 + .active svg > use.active
27 + display: block;
28 + .enabled svg > use.enabled
29 + display: block;
30 + .active svg > use.normal,
31 + .enabled svg > use.normal
32 + display: none;
33 + .help svg > use.disabled,
34 + .help.enabled svg > use.normal
35 + display: block;
36 + .help.enabled svg > use.disabled
37 + display: none;
38 +
39 + .{prefix}-controls:hover
40 + z-index: 3;
1 +prefix = 'tui-image-editor'
2 +
3 +@import 'main.styl'
4 +@import 'gridtable.styl'
5 +@import 'submenu.styl'
6 +@import 'checkbox.styl'
7 +@import 'range.styl'
8 +@import 'position.styl'
9 +@import 'icon.styl'
10 +@import 'colorpicker.styl'
11 +@import 'buttons.styl'
12 +.{prefix}-container.top
13 + &.{prefix}-top-optimization
14 + .{prefix}-controls ul
15 + text-align: right;
16 + .{prefix}-controls-logo
17 + display: none;
1 +body > textarea
2 + position: fixed !important;
3 +
4 ++prefix-classes(prefix)
5 + .-container
6 + margin: 0;
7 + padding: 0;
8 + box-sizing: border-box;
9 + min-height: 300px;
10 + height: 100%;
11 + position: relative;
12 + background-color: #282828;
13 + overflow: hidden;
14 + letter-spacing: 0.3px;
15 +
16 + div, ul, label, input, li
17 + box-sizing: border-box;
18 + margin: 0;
19 + padding: 0;
20 + -ms-user-select: none;
21 + -moz-user-select: -moz-none;
22 + -khtml-user-select: none;
23 + -webkit-user-select: none;
24 + user-select: none;
25 +
26 + .-header
27 + /* BUTTON AND LOGO */
28 + min-width: 533px;
29 + position: absolute;
30 + background-color: #151515;
31 + top: 0;
32 + width: 100%;
33 + .-header-buttons,
34 + .-controls-buttons
35 + float: right;
36 + margin: 8px;
37 +
38 + .-header-logo,
39 + .-controls-logo
40 + float: left;
41 + width: 30%;
42 + padding: 17px;
43 +
44 + .-controls-logo,
45 + .-controls-buttons
46 + width: 270px;
47 + height: 100%;
48 + display: none;
49 +
50 + .-header-buttons button,
51 + .-header-buttons div,
52 + .-controls-buttons button,
53 + .-controls-buttons div
54 + display: inline-block;
55 + position: relative;
56 + width: 120px;
57 + height: 40px;
58 + padding: 0;
59 + line-height: 40px;
60 + outline: none;
61 + border-radius: 20px;
62 + border: 1px solid #ddd;
63 + font-family: 'Noto Sans', sans-serif;
64 + font-size: 12px;
65 + font-weight: bold;
66 + cursor: pointer;
67 + vertical-align: middle;
68 + letter-spacing: 0.3px;
69 + text-align: center;
70 +
71 + .-download-btn
72 + background-color: #fdba3b;
73 + border-color: #fdba3b;
74 + color: #fff;
75 + .-load-btn
76 + position: absolute;
77 + left: 0;
78 + right: 0;
79 + display: inline-block;
80 + top: 0;
81 + bottom: 0;
82 + width: 100%;
83 + cursor: pointer;
84 + opacity: 0;
85 + .-main-container
86 + position: absolute;
87 + width: 100%;
88 + top: 0;
89 + bottom: 64px;
90 + .-main
91 + position: absolute;
92 + text-align: center;
93 + top: 64px;
94 + bottom: 0;
95 + right: 0;
96 + left: 0;
97 + .-wrap
98 + position: absolute;
99 + bottom: 0;
100 + width: 100%;
101 + overflow: auto;
102 + .-size-wrap
103 + display: table;
104 + width: 100%;
105 + height: 100%
106 + .-align-wrap
107 + display: table-cell;
108 + vertical-align: middle;
109 + .
110 + position: relative;
111 + display: inline-block;
112 +
113 +
114 +/* BIG MENU */
115 +.{prefix}-container
116 + .{prefix}-menu
117 + width: auto;
118 + list-style: none;
119 + padding: 0;
120 + margin: 0 auto;
121 + display: table-cell;
122 + text-align: center;
123 + vertical-align: middle;
124 + white-space: nowrap;
125 + > .{prefix}-item
126 + position: relative;
127 + display: inline-block;
128 + border-radius: 2px;
129 + padding: 7px 8px 3px 8px;
130 + cursor: pointer;
131 + margin: 0 4px;
132 + > .{prefix}-item[tooltip-content]:hover
133 + &:before
134 + content: '';
135 + position: absolute;
136 + display: inline-block;
137 + margin: 0 auto 0;
138 + width: 0;
139 + height: 0;
140 + border-right: 7px solid transparent;
141 + border-top: 7px solid #2f2f2f;
142 + border-left: 7px solid transparent;
143 + left: 13px;
144 + top: -2px;
145 + &:after
146 + content: attr(tooltip-content);
147 + position: absolute;
148 + display: inline-block;
149 + background-color: #2f2f2f;
150 + color: #fff;
151 + padding: 5px 8px;
152 + font-size: 11px;
153 + font-weight: lighter;
154 + border-radius: 3px;
155 + max-height: 23px;
156 + top: -25px;
157 + left: 0;
158 + min-width: 24px;
159 + > .{prefix}-item.active
160 + background-color: #fff;
161 + transition: all .3s ease;
162 + .{prefix}-wrap
163 + position: absolute;
1 +/* POSITION LEFT */
2 +.{prefix}-container
3 + &.left
4 + .{prefix}-menu
5 + > .{prefix}-item[tooltip-content]
6 + &:before
7 + left: 28px;
8 + top: 11px;
9 + border-right: 7px solid #2f2f2f;
10 + border-top: 7px solid transparent;
11 + border-bottom: 7px solid transparent;
12 + &:after
13 + top: 7px;
14 + left: 42px;
15 + white-space: nowrap;
16 + .{prefix}-submenu
17 + left: 0;
18 + height: 100%;
19 + width: 248px;
20 + .{prefix}-main-container
21 + left: 64px;
22 + width: calc(100% - 64px);
23 + height: 100%;
24 + .{prefix}-controls
25 + width: 64px;
26 + height: 100%;
27 + display: table;
28 +
29 +/* POSITION LEFT & RIGHT */
30 +.{prefix}-container
31 + &.left, &.right
32 + .{prefix}-menu
33 + white-space: inherit;
34 + .{prefix}-submenu
35 + white-space: normal;
36 + > div
37 + vertical-align: middle;
38 + .{prefix}-controls li
39 + display: inline-block;
40 + margin: 4px auto;
41 + .{prefix}-icpartition
42 + position: relative;
43 + top: -7px;
44 + width: 24px;
45 + height: 1px;
46 + .{prefix}-submenu
47 + .{prefix}-partition
48 + display: block;
49 + width: 75%;
50 + margin: auto;
51 + > div
52 + border-left: 0;
53 + height:10px;
54 + border-bottom: 1px solid #3c3c3c;
55 + width: 100%;
56 + margin: 0;
57 + .{prefix}-submenu-align
58 + margin-right: 0;
59 + .{prefix}-submenu-item
60 + li
61 + margin-top: 15px;
62 + .tui-colorpicker-clearfix li
63 + margin-top: 0;
64 +
65 + .{prefix}-checkbox-wrap.fixed-width
66 + width: 182px;
67 + white-space: normal;
68 + .{prefix}-range-wrap.{prefix}-newline label.range
69 + display: block;
70 + text-align: left;
71 + width: 75%;
72 + margin: auto;
73 + .{prefix}-range
74 + width: 136px;
75 +
76 +
77 +/* POSITION RIGIHT */
78 +.{prefix}-container
79 + &.right
80 + .{prefix}-menu
81 + > .{prefix}-item[tooltip-content]
82 + &:before
83 + left: -3px;
84 + top: 11px;
85 + border-left: 7px solid #2f2f2f;
86 + border-top: 7px solid transparent;
87 + border-bottom: 7px solid transparent;
88 + &:after
89 + top: 7px;
90 + left: unset;
91 + right: 43px;
92 + white-space: nowrap;
93 + .{prefix}-submenu
94 + right: 0;
95 + height: 100%;
96 + width: 248px;
97 + .{prefix}-main-container
98 + right: 64px;
99 + width: calc(100% - 64px);
100 + height: 100%;
101 + .{prefix}-controls
102 + right: 0;
103 + width: 64px;
104 + height: 100%;
105 + display: table;
106 +
107 +
108 +/* POSITION TOP & BOTTOM */
109 +.{prefix}-container
110 + &.top, &.bottom
111 + .{prefix}-submenu
112 + .{prefix}-partition.only-left-right
113 + display: none;
114 +
115 +
116 +/* POSITION BOTTOM */
117 +.{prefix}-container
118 + &.bottom .tui-image-editor-submenu > div
119 + padding-bottom: 24px;
120 +
121 +/* POSITION TOP */
122 +.{prefix}-container
123 + &.top
124 + .color-picker-control .triangle
125 + top: -8px;
126 + border-right: 7px solid transparent;
127 + border-top: 0px;
128 + border-left: 7px solid transparent;
129 + border-bottom: 8px solid #fff;
130 + .{prefix}-size-wrap
131 + height: 100%;
132 + .{prefix}-main-container
133 + bottom: 0;
134 + .{prefix}-menu
135 + > .{prefix}-item[tooltip-content]
136 + &:before
137 + left: 13px;
138 + border-top: 0;
139 + border-bottom: 7px solid #2f2f2f;
140 + top: 33px;
141 + &:after
142 + top: 38px;
143 + .{prefix}-submenu
144 + top: 0;
145 + bottom: auto;
146 + > div
147 + padding-top: 24px;
148 + vertical-align: top;
149 + .{prefix}-controls-logo
150 + display: table-cell;
151 + .{prefix}-controls-buttons
152 + display: table-cell;
153 + .{prefix}-main
154 + top: 64px;
155 + height: calc(100% - 64px);
156 + .{prefix}-controls
157 + top: 0;
158 + bottom: inherit;
159 +
1 +/* VIRTUAL RANGE */
2 +.{prefix}-container
3 +
4 + .{prefix}-virtual-range-bar
5 + .{prefix}-virtual-range-subbar
6 + .{prefix}-virtual-range-pointer
7 + .{prefix}-disabled
8 + backbround-color: red;
9 +
10 + .{prefix}-range
11 + position: relative;
12 + top: 5px;
13 + width: 166px;
14 + height: 17px;
15 + display: inline-block;
16 + .{prefix}-virtual-range-bar
17 + top: 7px;
18 + position: absolute;
19 + width: 100%;
20 + height: 2px;
21 + background-color: #666666;
22 + .{prefix}-virtual-range-subbar
23 + position: absolute;
24 + height: 100%;
25 + left: 0;
26 + right: 0;
27 + background-color: #d1d1d1;
28 + .{prefix}-virtual-range-pointer
29 + position: absolute;
30 + cursor: pointer;
31 + top: -5px;
32 + left: 0;
33 + width: 12px;
34 + height: 12px;
35 + background-color: #fff;
36 + border-radius: 100%;
37 + .{prefix}-range-wrap
38 + display: inline-block;
39 + margin-left: 4px;
40 + &.short .tui-image-editor-range
41 + width: 100px;
42 + .color-picker-control
43 + .{prefix}-range
44 + width: 108px;
45 + margin-left: 10px;
46 + .{prefix}-virtual-range-pointer
47 + background-color: #333;
48 + .{prefix}-virtual-range-bar
49 + background-color: #ccc;
50 + .{prefix}-virtual-range-subbar
51 + background-color: #606060;
52 + .{prefix}-range-wrap.{prefix}-newline.short
53 + margin-top: -2px;
54 + margin-left: 19px;
55 + label
56 + color: #8e8e8e;
57 + font-weight: normal;
58 + .{prefix}-range-wrap label
59 + vertical-align: baseline;
60 + font-size: 11px;
61 + margin-right: 7px;
62 + color: #fff;
63 + .{prefix}-range-value
64 + cursor: default;
65 + width: 40px;
66 + height: 24px;
67 + outline: none;
68 + border-radius: 2px;
69 + box-shadow: none;
70 + border: 1px solid #d5d5d5;
71 + text-align: center;
72 + background-color: #1c1c1c;
73 + color: #fff;
74 + font-weight: lighter;
75 + vertical-align: baseline;
76 + font-family: 'Noto Sans', sans-serif;
77 + margin-top: 21px;
78 + margin-left: 4px;
79 + .{prefix}-controls
80 + position: absolute;
81 + background-color: #151515;
82 + width: 100%;
83 + height: 64px;
84 + display: table;
85 + bottom: 0;
86 + z-index: 2;
87 + .{prefix}-icpartition
88 + display: inline-block;
89 + background-color: #282828;
90 + width: 1px;
91 + height: 24px;
...\ No newline at end of file ...\ No newline at end of file
1 +/* SUBMENU */
2 +.{prefix}-container
3 + .{prefix}-submenu
4 + display: none;
5 + position: absolute;
6 + bottom: 0;
7 + width:100%;
8 + height: 150px;
9 + white-space: nowrap;
10 + z-index: 2;
11 + .{prefix}-button:hover svg > use.active
12 + display: block;
13 + .{prefix}-submenu-item
14 + li
15 + display: inline-block;
16 + vertical-align: top;
17 + .{prefix}-newline
18 + display: block;
19 + margin-top: 0;
20 + .{prefix}-button
21 + position: relative;
22 + cursor: pointer;
23 + display: inline-block;
24 + font-weight: normal;
25 + font-size: 11px;
26 + margin: 0 9px 0 9px;
27 + .{prefix}-button.preset
28 + margin: 0 9px 20px 5px;
29 + label > span
30 + display: inline-block;
31 + cursor: pointer;
32 + padding-top: 5px;
33 + font-family: "Noto Sans", sans-serif;
34 + font-size: 11px;
35 + .{prefix}-button.apply label,
36 + .{prefix}-button.cancel label
37 + vertical-align: 7px;
38 + > div
39 + display: none;
40 + vertical-align: bottom;
41 + .{prefix}-submenu-style
42 + opacity: 0.95;
43 + z-index: -1;
44 + position: absolute;
45 + top: 0;
46 + bottom: 0;
47 + left: 0;
48 + right: 0;
49 + display: block;
50 +
51 + .{prefix}-partition > div
52 + width: 1px;
53 + height: 52px;
54 + border-left: 1px solid #3c3c3c;
55 + margin: 0 8px 0 8px;
56 + .{prefix}-main.{prefix}-menu-filter .{prefix}-partition > div
57 + height: 108px;
58 + margin: 0 29px 0 0px;
59 + .{prefix}-submenu-align
60 + text-align: left;
61 + margin-right: 30px;
62 + label > span
63 + width: 55px;
64 + white-space: nowrap;
65 + .{prefix}-submenu-align:first-child
66 + margin-right: 0;
67 + label > span
68 + width: 70px;
69 + .{prefix}-main.{prefix}-menu-crop .{prefix}-submenu > div.{prefix}-menu-crop,
70 + .{prefix}-main.{prefix}-menu-flip .{prefix}-submenu > div.{prefix}-menu-flip,
71 + .{prefix}-main.{prefix}-menu-rotate .{prefix}-submenu > div.{prefix}-menu-rotate,
72 + .{prefix}-main.{prefix}-menu-shape .{prefix}-submenu > div.{prefix}-menu-shape,
73 + .{prefix}-main.{prefix}-menu-text .{prefix}-submenu > div.{prefix}-menu-text,
74 + .{prefix}-main.{prefix}-menu-mask .{prefix}-submenu > div.{prefix}-menu-mask,
75 + .{prefix}-main.{prefix}-menu-icon .{prefix}-submenu > div.{prefix}-menu-icon,
76 + .{prefix}-main.{prefix}-menu-draw .{prefix}-submenu > div.{prefix}-menu-draw,
77 + .{prefix}-main.{prefix}-menu-filter .{prefix}-submenu > div.{prefix}-menu-filter
78 + display: table-cell;
79 + .{prefix}-main.{prefix}-menu-crop,
80 + .{prefix}-main.{prefix}-menu-flip,
81 + .{prefix}-main.{prefix}-menu-rotate,
82 + .{prefix}-main.{prefix}-menu-shape,
83 + .{prefix}-main.{prefix}-menu-text,
84 + .{prefix}-main.{prefix}-menu-mask,
85 + .{prefix}-main.{prefix}-menu-icon,
86 + .{prefix}-main.{prefix}-menu-draw,
87 + .{prefix}-main.{prefix}-menu-filter
88 + .{prefix}-submenu
89 + display: table;
90 +
1 +import './js/polyfill';
2 +import ImageEditor from './js/imageEditor';
3 +import './css/index.styl';
4 +
5 +// commands
6 +import './js/command/addIcon';
7 +import './js/command/addImageObject';
8 +import './js/command/addObject';
9 +import './js/command/addShape';
10 +import './js/command/addText';
11 +import './js/command/applyFilter';
12 +import './js/command/changeIconColor';
13 +import './js/command/changeShape';
14 +import './js/command/changeText';
15 +import './js/command/changeTextStyle';
16 +import './js/command/clearObjects';
17 +import './js/command/flip';
18 +import './js/command/loadImage';
19 +import './js/command/removeFilter';
20 +import './js/command/removeObject';
21 +import './js/command/resizeCanvasDimension';
22 +import './js/command/rotate';
23 +import './js/command/setObjectProperties';
24 +import './js/command/setObjectPosition';
25 +import './js/command/changeSelection';
26 +
27 +module.exports = ImageEditor;
This diff is collapsed. Click to expand it.
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Add an icon
4 + */
5 +import commandFactory from '../factory/command';
6 +import { Promise } from '../util';
7 +import { componentNames, commandNames } from '../consts';
8 +
9 +const { ICON } = componentNames;
10 +
11 +const command = {
12 + name: commandNames.ADD_ICON,
13 +
14 + /**
15 + * Add an icon
16 + * @param {Graphics} graphics - Graphics instance
17 + * @param {string} type - Icon type ('arrow', 'cancel', custom icon name)
18 + * @param {Object} options - Icon options
19 + * @param {string} [options.fill] - Icon foreground color
20 + * @param {string} [options.left] - Icon x position
21 + * @param {string} [options.top] - Icon y position
22 + * @returns {Promise}
23 + */
24 + execute(graphics, type, options) {
25 + const iconComp = graphics.getComponent(ICON);
26 +
27 + return iconComp.add(type, options).then((objectProps) => {
28 + this.undoData.object = graphics.getObject(objectProps.id);
29 +
30 + return objectProps;
31 + });
32 + },
33 + /**
34 + * @param {Graphics} graphics - Graphics instance
35 + * @returns {Promise}
36 + */
37 + undo(graphics) {
38 + graphics.remove(this.undoData.object);
39 +
40 + return Promise.resolve();
41 + },
42 +};
43 +
44 +commandFactory.register(command);
45 +
46 +export default command;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Add an image object
4 + */
5 +import commandFactory from '../factory/command';
6 +import { Promise } from '../util';
7 +import { commandNames } from '../consts';
8 +
9 +const command = {
10 + name: commandNames.ADD_IMAGE_OBJECT,
11 +
12 + /**
13 + * Add an image object
14 + * @param {Graphics} graphics - Graphics instance
15 + * @param {string} imgUrl - Image url to make object
16 + * @returns {Promise}
17 + */
18 + execute(graphics, imgUrl) {
19 + return graphics.addImageObject(imgUrl).then((objectProps) => {
20 + this.undoData.object = graphics.getObject(objectProps.id);
21 +
22 + return objectProps;
23 + });
24 + },
25 + /**
26 + * @param {Graphics} graphics - Graphics instance
27 + * @returns {Promise}
28 + */
29 + undo(graphics) {
30 + graphics.remove(this.undoData.object);
31 +
32 + return Promise.resolve();
33 + },
34 +};
35 +
36 +commandFactory.register(command);
37 +
38 +export default command;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Add an object
4 + */
5 +import commandFactory from '../factory/command';
6 +import { Promise } from '../util';
7 +import { commandNames, rejectMessages } from '../consts';
8 +
9 +const command = {
10 + name: commandNames.ADD_OBJECT,
11 +
12 + /**
13 + * Add an object
14 + * @param {Graphics} graphics - Graphics instance
15 + * @param {Object} object - Fabric object
16 + * @returns {Promise}
17 + */
18 + execute(graphics, object) {
19 + return new Promise((resolve, reject) => {
20 + if (!graphics.contains(object)) {
21 + graphics.add(object);
22 + resolve(object);
23 + } else {
24 + reject(rejectMessages.addedObject);
25 + }
26 + });
27 + },
28 + /**
29 + * @param {Graphics} graphics - Graphics instance
30 + * @param {Object} object - Fabric object
31 + * @returns {Promise}
32 + */
33 + undo(graphics, object) {
34 + return new Promise((resolve, reject) => {
35 + if (graphics.contains(object)) {
36 + graphics.remove(object);
37 + resolve(object);
38 + } else {
39 + reject(rejectMessages.noObject);
40 + }
41 + });
42 + },
43 +};
44 +
45 +commandFactory.register(command);
46 +
47 +export default command;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Add a shape
4 + */
5 +import commandFactory from '../factory/command';
6 +import { Promise } from '../util';
7 +import { componentNames, commandNames } from '../consts';
8 +
9 +const { SHAPE } = componentNames;
10 +
11 +const command = {
12 + name: commandNames.ADD_SHAPE,
13 +
14 + /**
15 + * Add a shape
16 + * @param {Graphics} graphics - Graphics instance
17 + * @param {string} type - Shape type (ex: 'rect', 'circle', 'triangle')
18 + * @param {Object} options - Shape options
19 + * @param {string} [options.fill] - Shape foreground color (ex: '#fff', 'transparent')
20 + * @param {string} [options.stroke] - Shape outline color
21 + * @param {number} [options.strokeWidth] - Shape outline width
22 + * @param {number} [options.width] - Width value (When type option is 'rect', this options can use)
23 + * @param {number} [options.height] - Height value (When type option is 'rect', this options can use)
24 + * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use)
25 + * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use)
26 + * @param {number} [options.left] - Shape x position
27 + * @param {number} [options.top] - Shape y position
28 + * @param {number} [options.isRegular] - Whether resizing shape has 1:1 ratio or not
29 + * @returns {Promise}
30 + */
31 + execute(graphics, type, options) {
32 + const shapeComp = graphics.getComponent(SHAPE);
33 +
34 + return shapeComp.add(type, options).then((objectProps) => {
35 + this.undoData.object = graphics.getObject(objectProps.id);
36 +
37 + return objectProps;
38 + });
39 + },
40 + /**
41 + * @param {Graphics} graphics - Graphics instance
42 + * @returns {Promise}
43 + */
44 + undo(graphics) {
45 + graphics.remove(this.undoData.object);
46 +
47 + return Promise.resolve();
48 + },
49 +};
50 +
51 +commandFactory.register(command);
52 +
53 +export default command;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Add a text object
4 + */
5 +import commandFactory from '../factory/command';
6 +import { Promise } from '../util';
7 +import { componentNames, commandNames, rejectMessages } from '../consts';
8 +const { TEXT } = componentNames;
9 +
10 +const command = {
11 + name: commandNames.ADD_TEXT,
12 +
13 + /**
14 + * Add a text object
15 + * @param {Graphics} graphics - Graphics instance
16 + * @param {string} text - Initial input text
17 + * @param {Object} [options] Options for text styles
18 + * @param {Object} [options.styles] Initial styles
19 + * @param {string} [options.styles.fill] Color
20 + * @param {string} [options.styles.fontFamily] Font type for text
21 + * @param {number} [options.styles.fontSize] Size
22 + * @param {string} [options.styles.fontStyle] Type of inclination (normal / italic)
23 + * @param {string} [options.styles.fontWeight] Type of thicker or thinner looking (normal / bold)
24 + * @param {string} [options.styles.textAlign] Type of text align (left / center / right)
25 + * @param {string} [options.styles.textDecoration] Type of line (underline / line-through / overline)
26 + * @param {{x: number, y: number}} [options.position] - Initial position
27 + * @returns {Promise}
28 + */
29 + execute(graphics, text, options) {
30 + const textComp = graphics.getComponent(TEXT);
31 +
32 + if (this.undoData.object) {
33 + const undoObject = this.undoData.object;
34 +
35 + return new Promise((resolve, reject) => {
36 + if (!graphics.contains(undoObject)) {
37 + graphics.add(undoObject);
38 + resolve(undoObject);
39 + } else {
40 + reject(rejectMessages.redo);
41 + }
42 + });
43 + }
44 +
45 + return textComp.add(text, options).then((objectProps) => {
46 + const { id } = objectProps;
47 + const textObject = graphics.getObject(id);
48 +
49 + this.undoData.object = textObject;
50 +
51 + return objectProps;
52 + });
53 + },
54 + /**
55 + * @param {Graphics} graphics - Graphics instance
56 + * @returns {Promise}
57 + */
58 + undo(graphics) {
59 + graphics.remove(this.undoData.object);
60 +
61 + return Promise.resolve();
62 + },
63 +};
64 +
65 +commandFactory.register(command);
66 +
67 +export default command;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Apply a filter into an image
4 + */
5 +import snippet from 'tui-code-snippet';
6 +import commandFactory from '../factory/command';
7 +import { componentNames, rejectMessages, commandNames } from '../consts';
8 +
9 +const { FILTER } = componentNames;
10 +
11 +/**
12 + * Chched data for undo
13 + * @type {Object}
14 + */
15 +let chchedUndoDataForSilent = null;
16 +
17 +/**
18 + * Make undoData
19 + * @param {string} type - Filter type
20 + * @param {Object} prevfilterOption - prev Filter options
21 + * @param {Object} options - Filter options
22 + * @returns {object} - undo data
23 + */
24 +function makeUndoData(type, prevfilterOption, options) {
25 + const undoData = {};
26 +
27 + if (type === 'mask') {
28 + undoData.object = options.mask;
29 + }
30 +
31 + undoData.options = prevfilterOption;
32 +
33 + return undoData;
34 +}
35 +
36 +const command = {
37 + name: commandNames.APPLY_FILTER,
38 +
39 + /**
40 + * Apply a filter into an image
41 + * @param {Graphics} graphics - Graphics instance
42 + * @param {string} type - Filter type
43 + * @param {Object} options - Filter options
44 + * @param {number} options.maskObjId - masking image object id
45 + * @param {boolean} isSilent - is silent execution or not
46 + * @returns {Promise}
47 + */
48 + execute(graphics, type, options, isSilent) {
49 + const filterComp = graphics.getComponent(FILTER);
50 +
51 + if (type === 'mask') {
52 + const maskObj = graphics.getObject(options.maskObjId);
53 +
54 + if (!(maskObj && maskObj.isType('image'))) {
55 + return Promise.reject(rejectMessages.invalidParameters);
56 + }
57 +
58 + snippet.extend(options, { mask: maskObj });
59 + graphics.remove(options.mask);
60 + }
61 + if (!this.isRedo) {
62 + const prevfilterOption = filterComp.getOptions(type);
63 + const undoData = makeUndoData(type, prevfilterOption, options);
64 +
65 + chchedUndoDataForSilent = this.setUndoData(undoData, chchedUndoDataForSilent, isSilent);
66 + }
67 +
68 + return filterComp.add(type, options);
69 + },
70 + /**
71 + * @param {Graphics} graphics - Graphics instance
72 + * @param {string} type - Filter type
73 + * @returns {Promise}
74 + */
75 + undo(graphics, type) {
76 + const filterComp = graphics.getComponent(FILTER);
77 +
78 + if (type === 'mask') {
79 + const mask = this.undoData.object;
80 + graphics.add(mask);
81 + graphics.setActiveObject(mask);
82 +
83 + return filterComp.remove(type);
84 + }
85 +
86 + // options changed case
87 + if (this.undoData.options) {
88 + return filterComp.add(type, this.undoData.options);
89 + }
90 +
91 + // filter added case
92 + return filterComp.remove(type);
93 + },
94 +};
95 +
96 +commandFactory.register(command);
97 +
98 +export default command;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Change icon color
4 + */
5 +import commandFactory from '../factory/command';
6 +import { Promise } from '../util';
7 +import { componentNames, rejectMessages, commandNames } from '../consts';
8 +
9 +const { ICON } = componentNames;
10 +
11 +const command = {
12 + name: commandNames.CHANGE_ICON_COLOR,
13 +
14 + /**
15 + * Change icon color
16 + * @param {Graphics} graphics - Graphics instance
17 + * @param {number} id - object id
18 + * @param {string} color - Color for icon
19 + * @returns {Promise}
20 + */
21 + execute(graphics, id, color) {
22 + return new Promise((resolve, reject) => {
23 + const iconComp = graphics.getComponent(ICON);
24 + const targetObj = graphics.getObject(id);
25 +
26 + if (!targetObj) {
27 + reject(rejectMessages.noObject);
28 + }
29 +
30 + this.undoData.object = targetObj;
31 + this.undoData.color = iconComp.getColor(targetObj);
32 + iconComp.setColor(color, targetObj);
33 + resolve();
34 + });
35 + },
36 + /**
37 + * @param {Graphics} graphics - Graphics instance
38 + * @returns {Promise}
39 + */
40 + undo(graphics) {
41 + const iconComp = graphics.getComponent(ICON);
42 + const { object: icon, color } = this.undoData;
43 +
44 + iconComp.setColor(color, icon);
45 +
46 + return Promise.resolve();
47 + },
48 +};
49 +
50 +commandFactory.register(command);
51 +
52 +export default command;
1 +/**
2 + * @author NHN. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview change selection
4 + */
5 +import commandFactory from '../factory/command';
6 +import { Promise } from '../util';
7 +import { commandNames } from '../consts';
8 +import { getCachedUndoDataForDimension } from '../helper/selectionModifyHelper';
9 +
10 +const command = {
11 + name: commandNames.CHANGE_SELECTION,
12 +
13 + execute(graphics, props) {
14 + if (this.isRedo) {
15 + props.forEach((prop) => {
16 + graphics.setObjectProperties(prop.id, prop);
17 + });
18 + } else {
19 + this.undoData = getCachedUndoDataForDimension();
20 + }
21 +
22 + return Promise.resolve();
23 + },
24 + undo(graphics) {
25 + this.undoData.forEach((datum) => {
26 + graphics.setObjectProperties(datum.id, datum);
27 + });
28 +
29 + return Promise.resolve();
30 + },
31 +};
32 +
33 +commandFactory.register(command);
34 +
35 +export default command;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview change a shape
4 + */
5 +import snippet from 'tui-code-snippet';
6 +import { Promise } from '../util';
7 +import commandFactory from '../factory/command';
8 +import { componentNames, rejectMessages, commandNames } from '../consts';
9 +
10 +const { SHAPE } = componentNames;
11 +
12 +/**
13 + * Chched data for undo
14 + * @type {Object}
15 + */
16 +let chchedUndoDataForSilent = null;
17 +
18 +/**
19 + * Make undoData
20 + * @param {object} options - shape options
21 + * @param {Component} targetObj - shape component
22 + * @returns {object} - undo data
23 + */
24 +function makeUndoData(options, targetObj) {
25 + const undoData = {
26 + object: targetObj,
27 + options: {},
28 + };
29 +
30 + snippet.forEachOwnProperties(options, (value, key) => {
31 + undoData.options[key] = targetObj[key];
32 + });
33 +
34 + return undoData;
35 +}
36 +
37 +const command = {
38 + name: commandNames.CHANGE_SHAPE,
39 +
40 + /**
41 + * Change a shape
42 + * @param {Graphics} graphics - Graphics instance
43 + * @param {number} id - object id
44 + * @param {Object} options - Shape options
45 + * @param {string} [options.fill] - Shape foreground color (ex: '#fff', 'transparent')
46 + * @param {string} [options.stroke] - Shape outline color
47 + * @param {number} [options.strokeWidth] - Shape outline width
48 + * @param {number} [options.width] - Width value (When type option is 'rect', this options can use)
49 + * @param {number} [options.height] - Height value (When type option is 'rect', this options can use)
50 + * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use)
51 + * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use)
52 + * @param {number} [options.left] - Shape x position
53 + * @param {number} [options.top] - Shape y position
54 + * @param {number} [options.isRegular] - Whether resizing shape has 1:1 ratio or not
55 + * @param {boolean} isSilent - is silent execution or not
56 + * @returns {Promise}
57 + */
58 + execute(graphics, id, options, isSilent) {
59 + const shapeComp = graphics.getComponent(SHAPE);
60 + const targetObj = graphics.getObject(id);
61 +
62 + if (!targetObj) {
63 + return Promise.reject(rejectMessages.noObject);
64 + }
65 +
66 + if (!this.isRedo) {
67 + const undoData = makeUndoData(options, targetObj);
68 +
69 + chchedUndoDataForSilent = this.setUndoData(undoData, chchedUndoDataForSilent, isSilent);
70 + }
71 +
72 + return shapeComp.change(targetObj, options);
73 + },
74 + /**
75 + * @param {Graphics} graphics - Graphics instance
76 + * @returns {Promise}
77 + */
78 + undo(graphics) {
79 + const shapeComp = graphics.getComponent(SHAPE);
80 + const { object: shape, options } = this.undoData;
81 +
82 + return shapeComp.change(shape, options);
83 + },
84 +};
85 +
86 +commandFactory.register(command);
87 +
88 +export default command;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Change a text
4 + */
5 +import commandFactory from '../factory/command';
6 +import { Promise } from '../util';
7 +import { componentNames, rejectMessages, commandNames } from '../consts';
8 +
9 +const { TEXT } = componentNames;
10 +
11 +const command = {
12 + name: commandNames.CHANGE_TEXT,
13 +
14 + /**
15 + * Change a text
16 + * @param {Graphics} graphics - Graphics instance
17 + * @param {number} id - object id
18 + * @param {string} text - Changing text
19 + * @returns {Promise}
20 + */
21 + execute(graphics, id, text) {
22 + const textComp = graphics.getComponent(TEXT);
23 + const targetObj = graphics.getObject(id);
24 +
25 + if (!targetObj) {
26 + return Promise.reject(rejectMessages.noObject);
27 + }
28 +
29 + this.undoData.object = targetObj;
30 + this.undoData.text = textComp.getText(targetObj);
31 +
32 + return textComp.change(targetObj, text);
33 + },
34 + /**
35 + * @param {Graphics} graphics - Graphics instance
36 + * @returns {Promise}
37 + */
38 + undo(graphics) {
39 + const textComp = graphics.getComponent(TEXT);
40 + const { object: textObj, text } = this.undoData;
41 +
42 + return textComp.change(textObj, text);
43 + },
44 +};
45 +
46 +commandFactory.register(command);
47 +
48 +export default command;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Change text styles
4 + */
5 +import snippet from 'tui-code-snippet';
6 +import commandFactory from '../factory/command';
7 +import { Promise } from '../util';
8 +import { componentNames, rejectMessages, commandNames } from '../consts';
9 +
10 +const { TEXT } = componentNames;
11 +
12 +/**
13 + * Chched data for undo
14 + * @type {Object}
15 + */
16 +let chchedUndoDataForSilent = null;
17 +
18 +/**
19 + * Make undoData
20 + * @param {object} styles - text styles
21 + * @param {Component} targetObj - text component
22 + * @returns {object} - undo data
23 + */
24 +function makeUndoData(styles, targetObj) {
25 + const undoData = {
26 + object: targetObj,
27 + styles: {},
28 + };
29 + snippet.forEachOwnProperties(styles, (value, key) => {
30 + const undoValue = targetObj[key];
31 + undoData.styles[key] = undoValue;
32 + });
33 +
34 + return undoData;
35 +}
36 +
37 +const command = {
38 + name: commandNames.CHANGE_TEXT_STYLE,
39 +
40 + /**
41 + * Change text styles
42 + * @param {Graphics} graphics - Graphics instance
43 + * @param {number} id - object id
44 + * @param {Object} styles - text styles
45 + * @param {string} [styles.fill] Color
46 + * @param {string} [styles.fontFamily] Font type for text
47 + * @param {number} [styles.fontSize] Size
48 + * @param {string} [styles.fontStyle] Type of inclination (normal / italic)
49 + * @param {string} [styles.fontWeight] Type of thicker or thinner looking (normal / bold)
50 + * @param {string} [styles.textAlign] Type of text align (left / center / right)
51 + * @param {string} [styles.textDecoration] Type of line (underline / line-through / overline)
52 + * @param {boolean} isSilent - is silent execution or not
53 + * @returns {Promise}
54 + */
55 + execute(graphics, id, styles, isSilent) {
56 + const textComp = graphics.getComponent(TEXT);
57 + const targetObj = graphics.getObject(id);
58 +
59 + if (!targetObj) {
60 + return Promise.reject(rejectMessages.noObject);
61 + }
62 + if (!this.isRedo) {
63 + const undoData = makeUndoData(styles, targetObj);
64 +
65 + chchedUndoDataForSilent = this.setUndoData(undoData, chchedUndoDataForSilent, isSilent);
66 + }
67 +
68 + return textComp.setStyle(targetObj, styles);
69 + },
70 + /**
71 + * @param {Graphics} graphics - Graphics instance
72 + * @returns {Promise}
73 + */
74 + undo(graphics) {
75 + const textComp = graphics.getComponent(TEXT);
76 + const { object: textObj, styles } = this.undoData;
77 +
78 + return textComp.setStyle(textObj, styles);
79 + },
80 +};
81 +
82 +commandFactory.register(command);
83 +
84 +export default command;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Clear all objects
4 + */
5 +import commandFactory from '../factory/command';
6 +import { Promise } from '../util';
7 +import { commandNames } from '../consts';
8 +
9 +const command = {
10 + name: commandNames.CLEAR_OBJECTS,
11 +
12 + /**
13 + * Clear all objects without background (main) image
14 + * @param {Graphics} graphics - Graphics instance
15 + * @returns {Promise}
16 + */
17 + execute(graphics) {
18 + return new Promise((resolve) => {
19 + this.undoData.objects = graphics.removeAll();
20 + resolve();
21 + });
22 + },
23 + /**
24 + * @param {Graphics} graphics - Graphics instance
25 + * @returns {Promise}
26 + * @ignore
27 + */
28 + undo(graphics) {
29 + graphics.add(this.undoData.objects);
30 +
31 + return Promise.resolve();
32 + },
33 +};
34 +
35 +commandFactory.register(command);
36 +
37 +export default command;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Flip an image
4 + */
5 +import commandFactory from '../factory/command';
6 +import { componentNames, commandNames } from '../consts';
7 +
8 +const { FLIP } = componentNames;
9 +
10 +const command = {
11 + name: commandNames.FLIP_IMAGE,
12 +
13 + /**
14 + * flip an image
15 + * @param {Graphics} graphics - Graphics instance
16 + * @param {string} type - 'flipX' or 'flipY' or 'reset'
17 + * @returns {Promise}
18 + */
19 + execute(graphics, type) {
20 + const flipComp = graphics.getComponent(FLIP);
21 +
22 + this.undoData.setting = flipComp.getCurrentSetting();
23 +
24 + return flipComp[type]();
25 + },
26 + /**
27 + * @param {Graphics} graphics - Graphics instance
28 + * @returns {Promise}
29 + */
30 + undo(graphics) {
31 + const flipComp = graphics.getComponent(FLIP);
32 +
33 + return flipComp.set(this.undoData.setting);
34 + },
35 +};
36 +
37 +commandFactory.register(command);
38 +
39 +export default command;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Load a background (main) image
4 + */
5 +import commandFactory from '../factory/command';
6 +import { componentNames, commandNames } from '../consts';
7 +
8 +const { IMAGE_LOADER } = componentNames;
9 +
10 +const command = {
11 + name: commandNames.LOAD_IMAGE,
12 +
13 + /**
14 + * Load a background (main) image
15 + * @param {Graphics} graphics - Graphics instance
16 + * @param {string} imageName - Image name
17 + * @param {string} imgUrl - Image Url
18 + * @returns {Promise}
19 + */
20 + execute(graphics, imageName, imgUrl) {
21 + const loader = graphics.getComponent(IMAGE_LOADER);
22 + const prevImage = loader.getCanvasImage();
23 + const prevImageWidth = prevImage ? prevImage.width : 0;
24 + const prevImageHeight = prevImage ? prevImage.height : 0;
25 + const objects = graphics.removeAll(true).filter((objectItem) => objectItem.type !== 'cropzone');
26 +
27 + objects.forEach((objectItem) => {
28 + objectItem.evented = true;
29 + });
30 +
31 + this.undoData = {
32 + name: loader.getImageName(),
33 + image: prevImage,
34 + objects,
35 + };
36 +
37 + return loader.load(imageName, imgUrl).then((newImage) => ({
38 + oldWidth: prevImageWidth,
39 + oldHeight: prevImageHeight,
40 + newWidth: newImage.width,
41 + newHeight: newImage.height,
42 + }));
43 + },
44 +
45 + /**
46 + * @param {Graphics} graphics - Graphics instance
47 + * @returns {Promise}
48 + */
49 + undo(graphics) {
50 + const loader = graphics.getComponent(IMAGE_LOADER);
51 + const { objects, name, image } = this.undoData;
52 +
53 + graphics.removeAll(true);
54 + graphics.add(objects);
55 +
56 + return loader.load(name, image);
57 + },
58 +};
59 +
60 +commandFactory.register(command);
61 +
62 +export default command;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Remove a filter from an image
4 + */
5 +import commandFactory from '../factory/command';
6 +import { componentNames, commandNames } from '../consts';
7 +
8 +const { FILTER } = componentNames;
9 +
10 +const command = {
11 + name: commandNames.REMOVE_FILTER,
12 +
13 + /**
14 + * Remove a filter from an image
15 + * @param {Graphics} graphics - Graphics instance
16 + * @param {string} type - Filter type
17 + * @returns {Promise}
18 + */
19 + execute(graphics, type) {
20 + const filterComp = graphics.getComponent(FILTER);
21 +
22 + this.undoData.options = filterComp.getOptions(type);
23 +
24 + return filterComp.remove(type);
25 + },
26 + /**
27 + * @param {Graphics} graphics - Graphics instance
28 + * @param {string} type - Filter type
29 + * @returns {Promise}
30 + */
31 + undo(graphics, type) {
32 + const filterComp = graphics.getComponent(FILTER);
33 + const { options } = this.undoData;
34 +
35 + return filterComp.add(type, options);
36 + },
37 +};
38 +
39 +commandFactory.register(command);
40 +
41 +export default command;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Remove an object
4 + */
5 +import commandFactory from '../factory/command';
6 +import { Promise } from '../util';
7 +import { commandNames, rejectMessages } from '../consts';
8 +
9 +const command = {
10 + name: commandNames.REMOVE_OBJECT,
11 +
12 + /**
13 + * Remove an object
14 + * @param {Graphics} graphics - Graphics instance
15 + * @param {number} id - object id
16 + * @returns {Promise}
17 + */
18 + execute(graphics, id) {
19 + return new Promise((resolve, reject) => {
20 + this.undoData.objects = graphics.removeObjectById(id);
21 + if (this.undoData.objects.length) {
22 + resolve();
23 + } else {
24 + reject(rejectMessages.noObject);
25 + }
26 + });
27 + },
28 + /**
29 + * @param {Graphics} graphics - Graphics instance
30 + * @returns {Promise}
31 + */
32 + undo(graphics) {
33 + graphics.add(this.undoData.objects);
34 +
35 + return Promise.resolve();
36 + },
37 +};
38 +
39 +commandFactory.register(command);
40 +
41 +export default command;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Resize a canvas
4 + */
5 +import commandFactory from '../factory/command';
6 +import { Promise } from '../util';
7 +import { commandNames } from '../consts';
8 +
9 +const command = {
10 + name: commandNames.RESIZE_CANVAS_DIMENSION,
11 +
12 + /**
13 + * resize the canvas with given dimension
14 + * @param {Graphics} graphics - Graphics instance
15 + * @param {{width: number, height: number}} dimension - Max width & height
16 + * @returns {Promise}
17 + */
18 + execute(graphics, dimension) {
19 + return new Promise((resolve) => {
20 + this.undoData.size = {
21 + width: graphics.cssMaxWidth,
22 + height: graphics.cssMaxHeight,
23 + };
24 +
25 + graphics.setCssMaxDimension(dimension);
26 + graphics.adjustCanvasDimension();
27 + resolve();
28 + });
29 + },
30 + /**
31 + * @param {Graphics} graphics - Graphics instance
32 + * @returns {Promise}
33 + */
34 + undo(graphics) {
35 + graphics.setCssMaxDimension(this.undoData.size);
36 + graphics.adjustCanvasDimension();
37 +
38 + return Promise.resolve();
39 + },
40 +};
41 +
42 +commandFactory.register(command);
43 +
44 +export default command;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Rotate an image
4 + */
5 +import commandFactory from '../factory/command';
6 +import { componentNames, commandNames } from '../consts';
7 +
8 +const { ROTATION } = componentNames;
9 +
10 +/**
11 + * Chched data for undo
12 + * @type {Object}
13 + */
14 +let chchedUndoDataForSilent = null;
15 +
16 +/**
17 + * Make undo data
18 + * @param {Component} rotationComp - rotation component
19 + * @returns {object} - undodata
20 + */
21 +function makeUndoData(rotationComp) {
22 + return {
23 + angle: rotationComp.getCurrentAngle(),
24 + };
25 +}
26 +
27 +const command = {
28 + name: commandNames.ROTATE_IMAGE,
29 +
30 + /**
31 + * Rotate an image
32 + * @param {Graphics} graphics - Graphics instance
33 + * @param {string} type - 'rotate' or 'setAngle'
34 + * @param {number} angle - angle value (degree)
35 + * @param {boolean} isSilent - is silent execution or not
36 + * @returns {Promise}
37 + */
38 + execute(graphics, type, angle, isSilent) {
39 + const rotationComp = graphics.getComponent(ROTATION);
40 +
41 + if (!this.isRedo) {
42 + const undoData = makeUndoData(rotationComp);
43 +
44 + chchedUndoDataForSilent = this.setUndoData(undoData, chchedUndoDataForSilent, isSilent);
45 + }
46 +
47 + return rotationComp[type](angle);
48 + },
49 + /**
50 + * @param {Graphics} graphics - Graphics instance
51 + * @returns {Promise}
52 + */
53 + undo(graphics) {
54 + const rotationComp = graphics.getComponent(ROTATION);
55 + const [, type, angle] = this.args;
56 +
57 + if (type === 'setAngle') {
58 + return rotationComp[type](this.undoData.angle);
59 + }
60 +
61 + return rotationComp.rotate(-angle);
62 + },
63 +};
64 +
65 +commandFactory.register(command);
66 +
67 +export default command;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Set object properties
4 + */
5 +import commandFactory from '../factory/command';
6 +import { Promise } from '../util';
7 +import { commandNames, rejectMessages } from '../consts';
8 +
9 +const command = {
10 + name: commandNames.SET_OBJECT_POSITION,
11 +
12 + /**
13 + * Set object properties
14 + * @param {Graphics} graphics - Graphics instance
15 + * @param {number} id - object id
16 + * @param {Object} posInfo - position object
17 + * @param {number} posInfo.x - x position
18 + * @param {number} posInfo.y - y position
19 + * @param {string} posInfo.originX - can be 'left', 'center', 'right'
20 + * @param {string} posInfo.originY - can be 'top', 'center', 'bottom'
21 + * @returns {Promise}
22 + */
23 + execute(graphics, id, posInfo) {
24 + const targetObj = graphics.getObject(id);
25 +
26 + if (!targetObj) {
27 + return Promise.reject(rejectMessages.noObject);
28 + }
29 +
30 + this.undoData.objectId = id;
31 + this.undoData.props = graphics.getObjectProperties(id, ['left', 'top']);
32 +
33 + graphics.setObjectPosition(id, posInfo);
34 + graphics.renderAll();
35 +
36 + return Promise.resolve();
37 + },
38 + /**
39 + * @param {Graphics} graphics - Graphics instance
40 + * @returns {Promise}
41 + */
42 + undo(graphics) {
43 + const { objectId, props } = this.undoData;
44 +
45 + graphics.setObjectProperties(objectId, props);
46 + graphics.renderAll();
47 +
48 + return Promise.resolve();
49 + },
50 +};
51 +
52 +commandFactory.register(command);
53 +
54 +export default command;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Set object properties
4 + */
5 +import snippet from 'tui-code-snippet';
6 +import commandFactory from '../factory/command';
7 +import { Promise } from '../util';
8 +import { commandNames, rejectMessages } from '../consts';
9 +
10 +const command = {
11 + name: commandNames.SET_OBJECT_PROPERTIES,
12 +
13 + /**
14 + * Set object properties
15 + * @param {Graphics} graphics - Graphics instance
16 + * @param {number} id - object id
17 + * @param {Object} props - properties
18 + * @param {string} [props.fill] Color
19 + * @param {string} [props.fontFamily] Font type for text
20 + * @param {number} [props.fontSize] Size
21 + * @param {string} [props.fontStyle] Type of inclination (normal / italic)
22 + * @param {string} [props.fontWeight] Type of thicker or thinner looking (normal / bold)
23 + * @param {string} [props.textAlign] Type of text align (left / center / right)
24 + * @param {string} [props.textDecoration] Type of line (underline / line-through / overline)
25 + * @returns {Promise}
26 + */
27 + execute(graphics, id, props) {
28 + const targetObj = graphics.getObject(id);
29 +
30 + if (!targetObj) {
31 + return Promise.reject(rejectMessages.noObject);
32 + }
33 +
34 + this.undoData.props = {};
35 + snippet.forEachOwnProperties(props, (value, key) => {
36 + this.undoData.props[key] = targetObj[key];
37 + });
38 +
39 + graphics.setObjectProperties(id, props);
40 +
41 + return Promise.resolve();
42 + },
43 + /**
44 + * @param {Graphics} graphics - Graphics instance
45 + * @param {number} id - object id
46 + * @returns {Promise}
47 + */
48 + undo(graphics, id) {
49 + const { props } = this.undoData;
50 +
51 + graphics.setObjectProperties(id, props);
52 +
53 + return Promise.resolve();
54 + },
55 +};
56 +
57 +commandFactory.register(command);
58 +
59 +export default command;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Image crop module (start cropping, end cropping)
4 + */
5 +import snippet from 'tui-code-snippet';
6 +import fabric from 'fabric';
7 +import Component from '../interface/component';
8 +import Cropzone from '../extension/cropzone';
9 +import { keyCodes, componentNames, CROPZONE_DEFAULT_OPTIONS } from '../consts';
10 +import { clamp, fixFloatingPoint } from '../util';
11 +
12 +const MOUSE_MOVE_THRESHOLD = 10;
13 +const DEFAULT_OPTION = {
14 + presetRatio: null,
15 + top: -10,
16 + left: -10,
17 + height: 1,
18 + width: 1,
19 +};
20 +
21 +/**
22 + * Cropper components
23 + * @param {Graphics} graphics - Graphics instance
24 + * @extends {Component}
25 + * @class Cropper
26 + * @ignore
27 + */
28 +class Cropper extends Component {
29 + constructor(graphics) {
30 + super(componentNames.CROPPER, graphics);
31 +
32 + /**
33 + * Cropzone
34 + * @type {Cropzone}
35 + * @private
36 + */
37 + this._cropzone = null;
38 +
39 + /**
40 + * StartX of Cropzone
41 + * @type {number}
42 + * @private
43 + */
44 + this._startX = null;
45 +
46 + /**
47 + * StartY of Cropzone
48 + * @type {number}
49 + * @private
50 + */
51 + this._startY = null;
52 +
53 + /**
54 + * State whether shortcut key is pressed or not
55 + * @type {boolean}
56 + * @private
57 + */
58 + this._withShiftKey = false;
59 +
60 + /**
61 + * Listeners
62 + * @type {object.<string, function>}
63 + * @private
64 + */
65 + this._listeners = {
66 + keydown: this._onKeyDown.bind(this),
67 + keyup: this._onKeyUp.bind(this),
68 + mousedown: this._onFabricMouseDown.bind(this),
69 + mousemove: this._onFabricMouseMove.bind(this),
70 + mouseup: this._onFabricMouseUp.bind(this),
71 + };
72 + }
73 +
74 + /**
75 + * Start cropping
76 + */
77 + start() {
78 + if (this._cropzone) {
79 + return;
80 + }
81 + const canvas = this.getCanvas();
82 +
83 + canvas.forEachObject((obj) => {
84 + // {@link http://fabricjs.com/docs/fabric.Object.html#evented}
85 + obj.evented = false;
86 + });
87 +
88 + this._cropzone = new Cropzone(
89 + canvas,
90 + snippet.extend(
91 + {
92 + left: 0,
93 + top: 0,
94 + width: 0.5,
95 + height: 0.5,
96 + strokeWidth: 0, // {@link https://github.com/kangax/fabric.js/issues/2860}
97 + cornerSize: 10,
98 + cornerColor: 'black',
99 + fill: 'transparent',
100 + },
101 + CROPZONE_DEFAULT_OPTIONS,
102 + this.graphics.cropSelectionStyle
103 + )
104 + );
105 +
106 + canvas.discardActiveObject();
107 + canvas.add(this._cropzone);
108 + canvas.on('mouse:down', this._listeners.mousedown);
109 + canvas.selection = false;
110 + canvas.defaultCursor = 'crosshair';
111 +
112 + fabric.util.addListener(document, 'keydown', this._listeners.keydown);
113 + fabric.util.addListener(document, 'keyup', this._listeners.keyup);
114 + }
115 +
116 + /**
117 + * End cropping
118 + */
119 + end() {
120 + const canvas = this.getCanvas();
121 + const cropzone = this._cropzone;
122 +
123 + if (!cropzone) {
124 + return;
125 + }
126 + canvas.remove(cropzone);
127 + canvas.selection = true;
128 + canvas.defaultCursor = 'default';
129 + canvas.off('mouse:down', this._listeners.mousedown);
130 + canvas.forEachObject((obj) => {
131 + obj.evented = true;
132 + });
133 +
134 + this._cropzone = null;
135 +
136 + fabric.util.removeListener(document, 'keydown', this._listeners.keydown);
137 + fabric.util.removeListener(document, 'keyup', this._listeners.keyup);
138 + }
139 +
140 + /**
141 + * Change cropzone visible
142 + * @param {boolean} visible - cropzone visible state
143 + */
144 + changeVisibility(visible) {
145 + if (this._cropzone) {
146 + this._cropzone.set({ visible });
147 + }
148 + }
149 +
150 + /**
151 + * onMousedown handler in fabric canvas
152 + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event
153 + * @private
154 + */
155 + _onFabricMouseDown(fEvent) {
156 + const canvas = this.getCanvas();
157 +
158 + if (fEvent.target) {
159 + return;
160 + }
161 +
162 + canvas.selection = false;
163 + const coord = canvas.getPointer(fEvent.e);
164 +
165 + this._startX = coord.x;
166 + this._startY = coord.y;
167 +
168 + canvas.on({
169 + 'mouse:move': this._listeners.mousemove,
170 + 'mouse:up': this._listeners.mouseup,
171 + });
172 + }
173 +
174 + /**
175 + * onMousemove handler in fabric canvas
176 + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event
177 + * @private
178 + */
179 + _onFabricMouseMove(fEvent) {
180 + const canvas = this.getCanvas();
181 + const pointer = canvas.getPointer(fEvent.e);
182 + const { x, y } = pointer;
183 + const cropzone = this._cropzone;
184 +
185 + if (Math.abs(x - this._startX) + Math.abs(y - this._startY) > MOUSE_MOVE_THRESHOLD) {
186 + canvas.remove(cropzone);
187 + cropzone.set(this._calcRectDimensionFromPoint(x, y));
188 +
189 + canvas.add(cropzone);
190 + canvas.setActiveObject(cropzone);
191 + }
192 + }
193 +
194 + /**
195 + * Get rect dimension setting from Canvas-Mouse-Position(x, y)
196 + * @param {number} x - Canvas-Mouse-Position x
197 + * @param {number} y - Canvas-Mouse-Position Y
198 + * @returns {{left: number, top: number, width: number, height: number}}
199 + * @private
200 + */
201 + _calcRectDimensionFromPoint(x, y) {
202 + const canvas = this.getCanvas();
203 + const canvasWidth = canvas.getWidth();
204 + const canvasHeight = canvas.getHeight();
205 + const startX = this._startX;
206 + const startY = this._startY;
207 + let left = clamp(x, 0, startX);
208 + let top = clamp(y, 0, startY);
209 + let width = clamp(x, startX, canvasWidth) - left; // (startX <= x(mouse) <= canvasWidth) - left
210 + let height = clamp(y, startY, canvasHeight) - top; // (startY <= y(mouse) <= canvasHeight) - top
211 +
212 + if (this._withShiftKey) {
213 + // make fixed ratio cropzone
214 + if (width > height) {
215 + height = width;
216 + } else if (height > width) {
217 + width = height;
218 + }
219 +
220 + if (startX >= x) {
221 + left = startX - width;
222 + }
223 +
224 + if (startY >= y) {
225 + top = startY - height;
226 + }
227 + }
228 +
229 + return {
230 + left,
231 + top,
232 + width,
233 + height,
234 + };
235 + }
236 +
237 + /**
238 + * onMouseup handler in fabric canvas
239 + * @private
240 + */
241 + _onFabricMouseUp() {
242 + const cropzone = this._cropzone;
243 + const listeners = this._listeners;
244 + const canvas = this.getCanvas();
245 +
246 + canvas.setActiveObject(cropzone);
247 + canvas.off({
248 + 'mouse:move': listeners.mousemove,
249 + 'mouse:up': listeners.mouseup,
250 + });
251 + }
252 +
253 + /**
254 + * Get cropped image data
255 + * @param {Object} cropRect cropzone rect
256 + * @param {Number} cropRect.left left position
257 + * @param {Number} cropRect.top top position
258 + * @param {Number} cropRect.width width
259 + * @param {Number} cropRect.height height
260 + * @returns {?{imageName: string, url: string}} cropped Image data
261 + */
262 + getCroppedImageData(cropRect) {
263 + const canvas = this.getCanvas();
264 + const containsCropzone = canvas.contains(this._cropzone);
265 + if (!cropRect) {
266 + return null;
267 + }
268 +
269 + if (containsCropzone) {
270 + canvas.remove(this._cropzone);
271 + }
272 +
273 + const imageData = {
274 + imageName: this.getImageName(),
275 + url: canvas.toDataURL(cropRect),
276 + };
277 +
278 + if (containsCropzone) {
279 + canvas.add(this._cropzone);
280 + }
281 +
282 + return imageData;
283 + }
284 +
285 + /**
286 + * Get cropped rect
287 + * @returns {Object} rect
288 + */
289 + getCropzoneRect() {
290 + const cropzone = this._cropzone;
291 +
292 + if (!cropzone.isValid()) {
293 + return null;
294 + }
295 +
296 + return {
297 + left: cropzone.left,
298 + top: cropzone.top,
299 + width: cropzone.width,
300 + height: cropzone.height,
301 + };
302 + }
303 +
304 + /**
305 + * Set a cropzone square
306 + * @param {number} [presetRatio] - preset ratio
307 + */
308 + setCropzoneRect(presetRatio) {
309 + const canvas = this.getCanvas();
310 + const cropzone = this._cropzone;
311 +
312 + canvas.discardActiveObject();
313 + canvas.selection = false;
314 + canvas.remove(cropzone);
315 +
316 + cropzone.set(presetRatio ? this._getPresetPropertiesForCropSize(presetRatio) : DEFAULT_OPTION);
317 +
318 + canvas.add(cropzone);
319 + canvas.selection = true;
320 +
321 + if (presetRatio) {
322 + canvas.setActiveObject(cropzone);
323 + }
324 + }
325 +
326 + /**
327 + * get a cropzone square info
328 + * @param {number} presetRatio - preset ratio
329 + * @returns {{presetRatio: number, left: number, top: number, width: number, height: number}}
330 + * @private
331 + */
332 + _getPresetPropertiesForCropSize(presetRatio) {
333 + const canvas = this.getCanvas();
334 + const originalWidth = canvas.getWidth();
335 + const originalHeight = canvas.getHeight();
336 +
337 + const standardSize = originalWidth >= originalHeight ? originalWidth : originalHeight;
338 + const getScale = (value, orignalValue) => (value > orignalValue ? orignalValue / value : 1);
339 +
340 + let width = standardSize * presetRatio;
341 + let height = standardSize;
342 +
343 + const scaleWidth = getScale(width, originalWidth);
344 + [width, height] = snippet.map([width, height], (sizeValue) => sizeValue * scaleWidth);
345 +
346 + const scaleHeight = getScale(height, originalHeight);
347 + [width, height] = snippet.map([width, height], (sizeValue) =>
348 + fixFloatingPoint(sizeValue * scaleHeight)
349 + );
350 +
351 + return {
352 + presetRatio,
353 + top: (originalHeight - height) / 2,
354 + left: (originalWidth - width) / 2,
355 + width,
356 + height,
357 + };
358 + }
359 +
360 + /**
361 + * Keydown event handler
362 + * @param {KeyboardEvent} e - Event object
363 + * @private
364 + */
365 + _onKeyDown(e) {
366 + if (e.keyCode === keyCodes.SHIFT) {
367 + this._withShiftKey = true;
368 + }
369 + }
370 +
371 + /**
372 + * Keyup event handler
373 + * @param {KeyboardEvent} e - Event object
374 + * @private
375 + */
376 + _onKeyUp(e) {
377 + if (e.keyCode === keyCodes.SHIFT) {
378 + this._withShiftKey = false;
379 + }
380 + }
381 +}
382 +
383 +export default Cropper;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Add filter module
4 + */
5 +import { isUndefined, extend, forEach, filter } from 'tui-code-snippet';
6 +import { Promise } from '../util';
7 +import fabric from 'fabric';
8 +import Component from '../interface/component';
9 +import Mask from '../extension/mask';
10 +import { rejectMessages, componentNames } from '../consts';
11 +import Sharpen from '../extension/sharpen';
12 +import Emboss from '../extension/emboss';
13 +import ColorFilter from '../extension/colorFilter';
14 +
15 +const { filters } = fabric.Image;
16 +filters.Mask = Mask;
17 +filters.Sharpen = Sharpen;
18 +filters.Emboss = Emboss;
19 +filters.ColorFilter = ColorFilter;
20 +
21 +/**
22 + * Filter
23 + * @class Filter
24 + * @param {Graphics} graphics - Graphics instance
25 + * @extends {Component}
26 + * @ignore
27 + */
28 +class Filter extends Component {
29 + constructor(graphics) {
30 + super(componentNames.FILTER, graphics);
31 + }
32 +
33 + /**
34 + * Add filter to source image (a specific filter is added on fabric.js)
35 + * @param {string} type - Filter type
36 + * @param {Object} [options] - Options of filter
37 + * @returns {Promise}
38 + */
39 + add(type, options) {
40 + return new Promise((resolve, reject) => {
41 + const sourceImg = this._getSourceImage();
42 + const canvas = this.getCanvas();
43 + let imgFilter = this._getFilter(sourceImg, type);
44 + if (!imgFilter) {
45 + imgFilter = this._createFilter(sourceImg, type, options);
46 + }
47 +
48 + if (!imgFilter) {
49 + reject(rejectMessages.invalidParameters);
50 + }
51 +
52 + this._changeFilterValues(imgFilter, options);
53 +
54 + this._apply(sourceImg, () => {
55 + canvas.renderAll();
56 + resolve({
57 + type,
58 + action: 'add',
59 + options,
60 + });
61 + });
62 + });
63 + }
64 +
65 + /**
66 + * Remove filter to source image
67 + * @param {string} type - Filter type
68 + * @returns {Promise}
69 + */
70 + remove(type) {
71 + return new Promise((resolve, reject) => {
72 + const sourceImg = this._getSourceImage();
73 + const canvas = this.getCanvas();
74 + const options = this.getOptions(type);
75 +
76 + if (!sourceImg.filters.length) {
77 + reject(rejectMessages.unsupportedOperation);
78 + }
79 +
80 + this._removeFilter(sourceImg, type);
81 +
82 + this._apply(sourceImg, () => {
83 + canvas.renderAll();
84 + resolve({
85 + type,
86 + action: 'remove',
87 + options,
88 + });
89 + });
90 + });
91 + }
92 +
93 + /**
94 + * Whether this has the filter or not
95 + * @param {string} type - Filter type
96 + * @returns {boolean} true if it has the filter
97 + */
98 + hasFilter(type) {
99 + return !!this._getFilter(this._getSourceImage(), type);
100 + }
101 +
102 + /**
103 + * Get a filter options
104 + * @param {string} type - Filter type
105 + * @returns {Object} filter options or null if there is no that filter
106 + */
107 + getOptions(type) {
108 + const sourceImg = this._getSourceImage();
109 + const imgFilter = this._getFilter(sourceImg, type);
110 + if (!imgFilter) {
111 + return null;
112 + }
113 +
114 + return extend({}, imgFilter.options);
115 + }
116 +
117 + /**
118 + * Change filter values
119 + * @param {Object} imgFilter object of filter
120 + * @param {Object} options object
121 + * @private
122 + */
123 + _changeFilterValues(imgFilter, options) {
124 + forEach(options, (value, key) => {
125 + if (!isUndefined(imgFilter[key])) {
126 + imgFilter[key] = value;
127 + }
128 + });
129 + forEach(imgFilter.options, (value, key) => {
130 + if (!isUndefined(options[key])) {
131 + imgFilter.options[key] = options[key];
132 + }
133 + });
134 + }
135 +
136 + /**
137 + * Apply filter
138 + * @param {fabric.Image} sourceImg - Source image to apply filter
139 + * @param {function} callback - Executed function after applying filter
140 + * @private
141 + */
142 + _apply(sourceImg, callback) {
143 + sourceImg.filters.push();
144 + const result = sourceImg.applyFilters();
145 + if (result) {
146 + callback();
147 + }
148 + }
149 +
150 + /**
151 + * Get source image on canvas
152 + * @returns {fabric.Image} Current source image on canvas
153 + * @private
154 + */
155 + _getSourceImage() {
156 + return this.getCanvasImage();
157 + }
158 +
159 + /**
160 + * Create filter instance
161 + * @param {fabric.Image} sourceImg - Source image to apply filter
162 + * @param {string} type - Filter type
163 + * @param {Object} [options] - Options of filter
164 + * @returns {Object} Fabric object of filter
165 + * @private
166 + */
167 + _createFilter(sourceImg, type, options) {
168 + let filterObj;
169 + // capitalize first letter for matching with fabric image filter name
170 + const fabricType = this._getFabricFilterType(type);
171 + const ImageFilter = fabric.Image.filters[fabricType];
172 + if (ImageFilter) {
173 + filterObj = new ImageFilter(options);
174 + filterObj.options = options;
175 + sourceImg.filters.push(filterObj);
176 + }
177 +
178 + return filterObj;
179 + }
180 +
181 + /**
182 + * Get applied filter instance
183 + * @param {fabric.Image} sourceImg - Source image to apply filter
184 + * @param {string} type - Filter type
185 + * @returns {Object} Fabric object of filter
186 + * @private
187 + */
188 + _getFilter(sourceImg, type) {
189 + let imgFilter = null;
190 +
191 + if (sourceImg) {
192 + const fabricType = this._getFabricFilterType(type);
193 + const { length } = sourceImg.filters;
194 + let item, i;
195 +
196 + for (i = 0; i < length; i += 1) {
197 + item = sourceImg.filters[i];
198 + if (item.type === fabricType) {
199 + imgFilter = item;
200 + break;
201 + }
202 + }
203 + }
204 +
205 + return imgFilter;
206 + }
207 +
208 + /**
209 + * Remove applied filter instance
210 + * @param {fabric.Image} sourceImg - Source image to apply filter
211 + * @param {string} type - Filter type
212 + * @private
213 + */
214 + _removeFilter(sourceImg, type) {
215 + const fabricType = this._getFabricFilterType(type);
216 + sourceImg.filters = filter(sourceImg.filters, (value) => value.type !== fabricType);
217 + }
218 +
219 + /**
220 + * Change filter class name to fabric's, especially capitalizing first letter
221 + * @param {string} type - Filter type
222 + * @example
223 + * 'grayscale' -> 'Grayscale'
224 + * @returns {string} Fabric filter class name
225 + */
226 + _getFabricFilterType(type) {
227 + return type.charAt(0).toUpperCase() + type.slice(1);
228 + }
229 +}
230 +
231 +export default Filter;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Image flip module
4 + */
5 +import snippet from 'tui-code-snippet';
6 +import { Promise } from '../util';
7 +import Component from '../interface/component';
8 +import { componentNames, rejectMessages } from '../consts';
9 +
10 +/**
11 + * Flip
12 + * @class Flip
13 + * @param {Graphics} graphics - Graphics instance
14 + * @extends {Component}
15 + * @ignore
16 + */
17 +class Flip extends Component {
18 + constructor(graphics) {
19 + super(componentNames.FLIP, graphics);
20 + }
21 +
22 + /**
23 + * Get current flip settings
24 + * @returns {{flipX: Boolean, flipY: Boolean}}
25 + */
26 + getCurrentSetting() {
27 + const canvasImage = this.getCanvasImage();
28 +
29 + return {
30 + flipX: canvasImage.flipX,
31 + flipY: canvasImage.flipY,
32 + };
33 + }
34 +
35 + /**
36 + * Set flipX, flipY
37 + * @param {{flipX: Boolean, flipY: Boolean}} newSetting - Flip setting
38 + * @returns {Promise}
39 + */
40 + set(newSetting) {
41 + const setting = this.getCurrentSetting();
42 + const isChangingFlipX = setting.flipX !== newSetting.flipX;
43 + const isChangingFlipY = setting.flipY !== newSetting.flipY;
44 +
45 + if (!isChangingFlipX && !isChangingFlipY) {
46 + return Promise.reject(rejectMessages.flip);
47 + }
48 +
49 + snippet.extend(setting, newSetting);
50 + this.setImageProperties(setting, true);
51 + this._invertAngle(isChangingFlipX, isChangingFlipY);
52 + this._flipObjects(isChangingFlipX, isChangingFlipY);
53 +
54 + return Promise.resolve({
55 + flipX: setting.flipX,
56 + flipY: setting.flipY,
57 + angle: this.getCanvasImage().angle,
58 + });
59 + }
60 +
61 + /**
62 + * Invert image angle for flip
63 + * @param {boolean} isChangingFlipX - Change flipX
64 + * @param {boolean} isChangingFlipY - Change flipY
65 + */
66 + _invertAngle(isChangingFlipX, isChangingFlipY) {
67 + const canvasImage = this.getCanvasImage();
68 + let { angle } = canvasImage;
69 +
70 + if (isChangingFlipX) {
71 + angle *= -1;
72 + }
73 + if (isChangingFlipY) {
74 + angle *= -1;
75 + }
76 + canvasImage.rotate(parseFloat(angle)).setCoords(); // parseFloat for -0 to 0
77 + }
78 +
79 + /**
80 + * Flip objects
81 + * @param {boolean} isChangingFlipX - Change flipX
82 + * @param {boolean} isChangingFlipY - Change flipY
83 + * @private
84 + */
85 + _flipObjects(isChangingFlipX, isChangingFlipY) {
86 + const canvas = this.getCanvas();
87 +
88 + if (isChangingFlipX) {
89 + canvas.forEachObject((obj) => {
90 + obj
91 + .set({
92 + angle: parseFloat(obj.angle * -1), // parseFloat for -0 to 0
93 + flipX: !obj.flipX,
94 + left: canvas.width - obj.left,
95 + })
96 + .setCoords();
97 + });
98 + }
99 + if (isChangingFlipY) {
100 + canvas.forEachObject((obj) => {
101 + obj
102 + .set({
103 + angle: parseFloat(obj.angle * -1), // parseFloat for -0 to 0
104 + flipY: !obj.flipY,
105 + top: canvas.height - obj.top,
106 + })
107 + .setCoords();
108 + });
109 + }
110 + canvas.renderAll();
111 + }
112 +
113 + /**
114 + * Reset flip settings
115 + * @returns {Promise}
116 + */
117 + reset() {
118 + return this.set({
119 + flipX: false,
120 + flipY: false,
121 + });
122 + }
123 +
124 + /**
125 + * Flip x
126 + * @returns {Promise}
127 + */
128 + flipX() {
129 + const current = this.getCurrentSetting();
130 +
131 + return this.set({
132 + flipX: !current.flipX,
133 + flipY: current.flipY,
134 + });
135 + }
136 +
137 + /**
138 + * Flip y
139 + * @returns {Promise}
140 + */
141 + flipY() {
142 + const current = this.getCurrentSetting();
143 +
144 + return this.set({
145 + flipX: current.flipX,
146 + flipY: !current.flipY,
147 + });
148 + }
149 +}
150 +
151 +export default Flip;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Free drawing module, Set brush
4 + */
5 +import fabric from 'fabric';
6 +import Component from '../interface/component';
7 +import { componentNames } from '../consts';
8 +
9 +/**
10 + * FreeDrawing
11 + * @class FreeDrawing
12 + * @param {Graphics} graphics - Graphics instance
13 + * @extends {Component}
14 + * @ignore
15 + */
16 +class FreeDrawing extends Component {
17 + constructor(graphics) {
18 + super(componentNames.FREE_DRAWING, graphics);
19 +
20 + /**
21 + * Brush width
22 + * @type {number}
23 + */
24 + this.width = 12;
25 +
26 + /**
27 + * fabric.Color instance for brush color
28 + * @type {fabric.Color}
29 + */
30 + this.oColor = new fabric.Color('rgba(0, 0, 0, 0.5)');
31 + }
32 +
33 + /**
34 + * Start free drawing mode
35 + * @param {{width: ?number, color: ?string}} [setting] - Brush width & color
36 + */
37 + start(setting) {
38 + const canvas = this.getCanvas();
39 +
40 + canvas.isDrawingMode = true;
41 + this.setBrush(setting);
42 + }
43 +
44 + /**
45 + * Set brush
46 + * @param {{width: ?number, color: ?string}} [setting] - Brush width & color
47 + */
48 + setBrush(setting) {
49 + const brush = this.getCanvas().freeDrawingBrush;
50 +
51 + setting = setting || {};
52 + this.width = setting.width || this.width;
53 + if (setting.color) {
54 + this.oColor = new fabric.Color(setting.color);
55 + }
56 + brush.width = this.width;
57 + brush.color = this.oColor.toRgba();
58 + }
59 +
60 + /**
61 + * End free drawing mode
62 + */
63 + end() {
64 + const canvas = this.getCanvas();
65 +
66 + canvas.isDrawingMode = false;
67 + }
68 +}
69 +
70 +export default FreeDrawing;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Add icon module
4 + */
5 +import fabric from 'fabric';
6 +import snippet from 'tui-code-snippet';
7 +import { Promise } from '../util';
8 +import Component from '../interface/component';
9 +import { eventNames as events, rejectMessages, componentNames, fObjectOptions } from '../consts';
10 +
11 +const pathMap = {
12 + arrow: 'M 0 90 H 105 V 120 L 160 60 L 105 0 V 30 H 0 Z',
13 + cancel:
14 + 'M 0 30 L 30 60 L 0 90 L 30 120 L 60 90 L 90 120 L 120 90 ' +
15 + 'L 90 60 L 120 30 L 90 0 L 60 30 L 30 0 Z',
16 +};
17 +
18 +/**
19 + * Icon
20 + * @class Icon
21 + * @param {Graphics} graphics - Graphics instance
22 + * @extends {Component}
23 + * @ignore
24 + */
25 +class Icon extends Component {
26 + constructor(graphics) {
27 + super(componentNames.ICON, graphics);
28 +
29 + /**
30 + * Default icon color
31 + * @type {string}
32 + */
33 + this._oColor = '#000000';
34 +
35 + /**
36 + * Path value of each icon type
37 + * @type {Object}
38 + */
39 + this._pathMap = pathMap;
40 +
41 + /**
42 + * Type of the drawing icon
43 + * @type {string}
44 + * @private
45 + */
46 + this._type = null;
47 +
48 + /**
49 + * Color of the drawing icon
50 + * @type {string}
51 + * @private
52 + */
53 + this._iconColor = null;
54 +
55 + /**
56 + * Event handler list
57 + * @type {Object}
58 + * @private
59 + */
60 + this._handlers = {
61 + mousedown: this._onFabricMouseDown.bind(this),
62 + mousemove: this._onFabricMouseMove.bind(this),
63 + mouseup: this._onFabricMouseUp.bind(this),
64 + };
65 + }
66 +
67 + /**
68 + * Set states of the current drawing shape
69 + * @ignore
70 + * @param {string} type - Icon type ('arrow', 'cancel', custom icon name)
71 + * @param {string} iconColor - Icon foreground color
72 + */
73 + setStates(type, iconColor) {
74 + this._type = type;
75 + this._iconColor = iconColor;
76 + }
77 +
78 + /**
79 + * Start to draw the icon on canvas
80 + * @ignore
81 + */
82 + start() {
83 + const canvas = this.getCanvas();
84 + canvas.selection = false;
85 + canvas.on('mouse:down', this._handlers.mousedown);
86 + }
87 +
88 + /**
89 + * End to draw the icon on canvas
90 + * @ignore
91 + */
92 + end() {
93 + const canvas = this.getCanvas();
94 +
95 + canvas.selection = true;
96 + canvas.off({
97 + 'mouse:down': this._handlers.mousedown,
98 + });
99 + }
100 +
101 + /**
102 + * Add icon
103 + * @param {string} type - Icon type
104 + * @param {Object} options - Icon options
105 + * @param {string} [options.fill] - Icon foreground color
106 + * @param {string} [options.left] - Icon x position
107 + * @param {string} [options.top] - Icon y position
108 + * @returns {Promise}
109 + */
110 + add(type, options) {
111 + return new Promise((resolve, reject) => {
112 + const canvas = this.getCanvas();
113 + const path = this._pathMap[type];
114 + const selectionStyle = fObjectOptions.SELECTION_STYLE;
115 + const icon = path ? this._createIcon(path) : null;
116 + this._icon = icon;
117 +
118 + if (!icon) {
119 + reject(rejectMessages.invalidParameters);
120 + }
121 +
122 + icon.set(
123 + snippet.extend(
124 + {
125 + type: 'icon',
126 + fill: this._oColor,
127 + },
128 + selectionStyle,
129 + options,
130 + this.graphics.controlStyle
131 + )
132 + );
133 +
134 + canvas.add(icon).setActiveObject(icon);
135 +
136 + resolve(this.graphics.createObjectProperties(icon));
137 + });
138 + }
139 +
140 + /**
141 + * Register icon paths
142 + * @param {{key: string, value: string}} pathInfos - Path infos
143 + */
144 + registerPaths(pathInfos) {
145 + snippet.forEach(
146 + pathInfos,
147 + (path, type) => {
148 + this._pathMap[type] = path;
149 + },
150 + this
151 + );
152 + }
153 +
154 + /**
155 + * Set icon object color
156 + * @param {string} color - Color to set
157 + * @param {fabric.Path}[obj] - Current activated path object
158 + */
159 + setColor(color, obj) {
160 + this._oColor = color;
161 +
162 + if (obj && obj.get('type') === 'icon') {
163 + obj.set({ fill: this._oColor });
164 + this.getCanvas().renderAll();
165 + }
166 + }
167 +
168 + /**
169 + * Get icon color
170 + * @param {fabric.Path}[obj] - Current activated path object
171 + * @returns {string} color
172 + */
173 + getColor(obj) {
174 + return obj.fill;
175 + }
176 +
177 + /**
178 + * Create icon object
179 + * @param {string} path - Path value to create icon
180 + * @returns {fabric.Path} Path object
181 + */
182 + _createIcon(path) {
183 + return new fabric.Path(path);
184 + }
185 +
186 + /**
187 + * MouseDown event handler on canvas
188 + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object
189 + * @private
190 + */
191 + _onFabricMouseDown(fEvent) {
192 + const canvas = this.getCanvas();
193 +
194 + this._startPoint = canvas.getPointer(fEvent.e);
195 + const { x: left, y: top } = this._startPoint;
196 +
197 + this.add(this._type, {
198 + left,
199 + top,
200 + fill: this._iconColor,
201 + }).then(() => {
202 + this.fire(events.ADD_OBJECT, this.graphics.createObjectProperties(this._icon));
203 + canvas.on('mouse:move', this._handlers.mousemove);
204 + canvas.on('mouse:up', this._handlers.mouseup);
205 + });
206 + }
207 +
208 + /**
209 + * MouseMove event handler on canvas
210 + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object
211 + * @private
212 + */
213 + _onFabricMouseMove(fEvent) {
214 + const canvas = this.getCanvas();
215 +
216 + if (!this._icon) {
217 + return;
218 + }
219 + const moveOriginPointer = canvas.getPointer(fEvent.e);
220 +
221 + const scaleX = (moveOriginPointer.x - this._startPoint.x) / this._icon.width;
222 + const scaleY = (moveOriginPointer.y - this._startPoint.y) / this._icon.height;
223 +
224 + this._icon.set({
225 + scaleX: Math.abs(scaleX * 2),
226 + scaleY: Math.abs(scaleY * 2),
227 + });
228 +
229 + this._icon.setCoords();
230 + canvas.renderAll();
231 + }
232 +
233 + /**
234 + * MouseUp event handler on canvas
235 + * @private
236 + */
237 + _onFabricMouseUp() {
238 + const canvas = this.getCanvas();
239 +
240 + this.fire(events.OBJECT_ADDED, this.graphics.createObjectProperties(this._icon));
241 +
242 + this._icon = null;
243 +
244 + canvas.off('mouse:down', this._handlers.mousedown);
245 + canvas.off('mouse:move', this._handlers.mousemove);
246 + canvas.off('mouse:up', this._handlers.mouseup);
247 + }
248 +}
249 +
250 +export default Icon;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Image loader
4 + */
5 +import Component from '../interface/component';
6 +import { componentNames, rejectMessages } from '../consts';
7 +import { Promise } from '../util';
8 +
9 +const imageOption = {
10 + padding: 0,
11 + crossOrigin: 'Anonymous',
12 +};
13 +
14 +/**
15 + * ImageLoader components
16 + * @extends {Component}
17 + * @class ImageLoader
18 + * @param {Graphics} graphics - Graphics instance
19 + * @ignore
20 + */
21 +class ImageLoader extends Component {
22 + constructor(graphics) {
23 + super(componentNames.IMAGE_LOADER, graphics);
24 + }
25 +
26 + /**
27 + * Load image from url
28 + * @param {?string} imageName - File name
29 + * @param {?(fabric.Image|string)} img - fabric.Image instance or URL of an image
30 + * @returns {Promise}
31 + */
32 + load(imageName, img) {
33 + let promise;
34 +
35 + if (!imageName && !img) {
36 + // Back to the initial state, not error.
37 + const canvas = this.getCanvas();
38 +
39 + canvas.backgroundImage = null;
40 + canvas.renderAll();
41 +
42 + promise = new Promise((resolve) => {
43 + this.setCanvasImage('', null);
44 + resolve();
45 + });
46 + } else {
47 + promise = this._setBackgroundImage(img).then((oImage) => {
48 + this.setCanvasImage(imageName, oImage);
49 + this.adjustCanvasDimension();
50 +
51 + return oImage;
52 + });
53 + }
54 +
55 + return promise;
56 + }
57 +
58 + /**
59 + * Set background image
60 + * @param {?(fabric.Image|String)} img fabric.Image instance or URL of an image to set background to
61 + * @returns {Promise}
62 + * @private
63 + */
64 + _setBackgroundImage(img) {
65 + if (!img) {
66 + return Promise.reject(rejectMessages.loadImage);
67 + }
68 +
69 + return new Promise((resolve, reject) => {
70 + const canvas = this.getCanvas();
71 +
72 + canvas.setBackgroundImage(
73 + img,
74 + () => {
75 + const oImage = canvas.backgroundImage;
76 +
77 + if (oImage && oImage.getElement()) {
78 + resolve(oImage);
79 + } else {
80 + reject(rejectMessages.loadingImageFailed);
81 + }
82 + },
83 + imageOption
84 + );
85 + });
86 + }
87 +}
88 +
89 +export default ImageLoader;
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff could not be displayed because it is too large.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.