├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .github
└── workflows
│ └── github-pages.yml
├── .gitignore
├── .npmrc
├── .nvmrc
├── .prettierignore
├── .prettierrc
├── LICENSE
├── _config.yml
├── babel.config.js
├── demo
├── index.js
├── main.js
└── scss
│ ├── iconfont.scss
│ ├── index.scss
│ ├── main.scss
│ ├── mixin
│ ├── clearfix.scss
│ ├── common.scss
│ └── iconfont.scss
│ ├── normalize.scss
│ ├── theme
│ └── _default.scss
│ └── variable.scss
├── html
├── 404.html
└── index.html
├── index.js
├── jsdoc.conf.json
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
└── demo.jpeg
├── readme.md
├── rollup.config.js
├── src
├── command.js
├── commands
│ ├── add-object.js
│ ├── base.js
│ ├── clear.js
│ ├── load-image.js
│ ├── remove.js
│ ├── rotation-image.js
│ └── zoom.js
├── consts.js
├── index.js
├── lib
│ ├── canvas-to-blob.js
│ ├── custom-event.js
│ ├── event.js
│ ├── shape-resize-helper.js
│ └── util.js
├── module.js
├── modules
│ ├── arrow.2.js
│ ├── arrow.js
│ ├── base.js
│ ├── cropper.js
│ ├── draw.js
│ ├── image-loader.js
│ ├── line.js
│ ├── main.js
│ ├── mosaic.1.js
│ ├── mosaic.2.js
│ ├── mosaic.js
│ ├── pan.js
│ ├── rotation.js
│ ├── shape.js
│ └── text.js
└── shape
│ ├── arrow.js
│ ├── cropzone.js
│ └── mosaic.js
├── webpack.config.js
└── website
├── .fatherrc.ts
├── .gitignore
├── .npmrc
├── .umirc.ts
├── README.md
├── docs
└── index.md
├── package.json
├── public
└── images
│ └── demo.jpeg
├── src
├── index.ts
└── scss
│ ├── iconfont.scss
│ ├── index.scss
│ ├── main.scss
│ ├── mixin
│ ├── clearfix.scss
│ ├── common.scss
│ └── iconfont.scss
│ ├── normalize.scss
│ ├── theme
│ └── _default.scss
│ └── variable.scss
├── tsconfig.json
└── typings.d.ts
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: https://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | # Unix-style newlines with a newline ending every file
7 | [*]
8 | end_of_line = lf
9 | insert_final_newline = true
10 | indent_style = space
11 | indent_size = 4
12 |
13 | # Matches multiple files with brace expansion notation
14 | # Set default charset
15 | [*.{js,py}]
16 | charset = utf-8
17 |
18 | # 4 space indentation
19 | [*.py]
20 | indent_style = space
21 | indent_size = 4
22 |
23 | # Tab indentation (no size specified)
24 | [Makefile]
25 | indent_style = tab
26 |
27 | # Matches the exact files either package.json or .travis.yml
28 | [{package.json,.travis.yml}]
29 | indent_style = space
30 | indent_size = 4
31 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules/**/*.js
2 | *.json
3 | dist/**/*.js
4 | babel/*.js
5 | postcss.config.js
6 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['airbnb', 'prettier'],
3 | parser: 'babel-eslint',
4 | plugins: ['jest'],
5 | env: {
6 | 'jest/globals': true
7 | },
8 | globals: {
9 | window: true,
10 | Blob: true,
11 | atob: true,
12 | document: true
13 | },
14 | settings: {},
15 | rules: {
16 | 'max-lines': ['error', { max: 600, skipComments: true, skipBlankLines: true }],
17 | 'import/no-extraneous-dependencies': 'off',
18 | 'react/prop-types': 'off',
19 | 'jest/no-disabled-tests': 'warn',
20 | 'jest/no-focused-tests': 'error',
21 | 'jest/no-identical-title': 'error',
22 | 'jest/prefer-to-have-length': 'warn',
23 | 'jest/valid-expect': 'error',
24 | 'react/react-in-jsx-scope': 'off',
25 | 'react/jsx-filename-extension': 'off',
26 | 'react/jsx-no-undef': 'off',
27 | 'react/jsx-indent': 'off',
28 | 'react/no-access-state-in-setstate': 'off',
29 | 'no-shadow': 'off',
30 | calemcase: 'off',
31 | 'class-methods-use-this': 'off',
32 | 'react/destructuring-assignment': 'off',
33 | 'consistent-return': 'off',
34 | 'array-callback-return': 'off',
35 | 'import/named': 'off',
36 | 'import/prefer-default-export': 1,
37 | 'one-var': 'off',
38 | 'no-underscore-dangle': 'off',
39 | 'no-plusplus': 'off',
40 | camelcase: 1,
41 | 'no-console': 'off',
42 | 'no-empty': 1,
43 | 'no-unused-expressions': 1,
44 | 'no-multi-assign': 'off',
45 | 'import/first': 1,
46 | 'prefer-promise-reject-errors': 1,
47 | 'import/extensions': 'off',
48 | 'prefer-const': 1,
49 | 'no-bitwise': 'off',
50 | 'no-restricted-syntax': 1,
51 | 'no-param-reassign': 1,
52 | 'no-nested-ternary': 1,
53 | 'no-control-regex': 'off',
54 | 'react/no-unknown-property': 'off',
55 | 'react/jsx-one-expression-per-line': 'off',
56 | 'react/button-has-type': 'off',
57 | 'spaced-comment': 'off',
58 | 'react/sort-comp': 'off'
59 | }
60 | };
61 |
--------------------------------------------------------------------------------
/.github/workflows/github-pages.yml:
--------------------------------------------------------------------------------
1 | name: Example Build & Deploy to GitHub Pages
2 | on:
3 | push:
4 | branches:
5 | - master
6 | tags:
7 | - v1
8 | paths:
9 | - 'src/**'
10 | - 'website/**'
11 | - '.github/**'
12 | repository_dispatch:
13 | jobs:
14 | build:
15 | name: Build
16 | runs-on: ubuntu-latest
17 | steps:
18 | - name: Checkout 🛎️
19 | uses: actions/checkout@master
20 | with:
21 | persist-credentials: false
22 | - name: Cache node modules
23 | uses: actions/cache@v1
24 | with:
25 | path: ~/.npm
26 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
27 | restore-keys: |
28 | ${{ runner.os }}-build-${{ env.cache-name }}-
29 | ${{ runner.os }}-build-
30 | ${{ runner.os }}-
31 | - name: use Node.js 10
32 | uses: actions/setup-node@v1
33 | with:
34 | node-version: '10.x'
35 | - name: npm script 🔧
36 | run: |
37 | npm install --registry=https://registry.npmjs.com
38 | npm run install:website
39 | npm run build:website
40 | env:
41 | CI: true
42 | - name: Deploy 🚀
43 | uses: JamesIves/github-pages-deploy-action@releases/v3
44 | with:
45 | ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
46 | BRANCH: gh-pages
47 | FOLDER: website/dist
48 | GIT_CONFIG_NAME: ${{ secrets.GIT_CONFIG_NAME}}
49 | GIT_CONFIG_EMAIL: ${{ secrets.GIT_CONFIG_EMAIL}}
50 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .idea
3 | out
4 | dist
5 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | registry=https://registry.npm.taobao.org/
2 | disturl=https://npm.taobao.org/dist
3 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 10
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | src/login
2 | src/lib/npm/**/*.js
3 | src/lib/wxapp-mobx/**/*.js
4 | src/lib/wxParse/**/*.js
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 100,
3 | "tabWidth": 4,
4 | "singleQuote": true,
5 | "semi": true,
6 | "arrowParens": "always",
7 | "bracketSpacing": true
8 | }
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2017-present, Yuxi (Evan) You
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-architect
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function(api) {
2 | api.cache(false);
3 | return {
4 | presets: [
5 | [
6 | '@babel/preset-env',
7 | {
8 | targets: {
9 | browsers: ['safari >= 9', 'android >= 4.0']
10 | }
11 | }
12 | ],
13 | '@babel/preset-react'
14 | ],
15 | ignore: [],
16 | comments: false,
17 | plugins: [
18 | [
19 | '@babel/plugin-proposal-decorators',
20 | {
21 | legacy: true
22 | }
23 | ],
24 | '@babel/plugin-syntax-dynamic-import',
25 | '@babel/plugin-syntax-import-meta',
26 | '@babel/plugin-proposal-class-properties',
27 | '@babel/plugin-proposal-json-strings',
28 | '@babel/plugin-proposal-function-sent',
29 | '@babel/plugin-proposal-export-namespace-from',
30 | '@babel/plugin-proposal-numeric-separator',
31 | '@babel/plugin-proposal-throw-expressions',
32 | '@babel/plugin-proposal-export-default-from',
33 | '@babel/plugin-proposal-logical-assignment-operators',
34 | '@babel/plugin-proposal-optional-chaining',
35 | [
36 | '@babel/plugin-proposal-pipeline-operator',
37 | {
38 | proposal: 'minimal'
39 | }
40 | ],
41 | '@babel/plugin-proposal-nullish-coalescing-operator',
42 | '@babel/plugin-proposal-do-expressions',
43 | '@babel/plugin-proposal-function-bind'
44 | ]
45 | };
46 | };
47 |
--------------------------------------------------------------------------------
/demo/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by yeanzhi on 16/12/1.
3 | */
4 | 'use strict';
5 | import 'babel-polyfill';
6 | import './scss/index.scss';
7 | import React from 'react';
8 | import ReactDOM from 'react-dom';
9 | import Main from './main';
10 | ReactDOM.render(
11 |
12 |
13 |
,
14 | document.getElementById('demo_container')
15 | );
--------------------------------------------------------------------------------
/demo/scss/iconfont.scss:
--------------------------------------------------------------------------------
1 | // font-face
2 | // @icon-url: 字体源文件的地址
3 | @font-face {
4 |
5 |
6 | font-family: 'dxicon';
7 |
8 | src: url('#{$icon-url}.eot'); /* IE9*/
9 | src: url('#{$icon-url}.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
10 | url('#{$icon-url}.woff') format('woff'), /* chrome、firefox */
11 | url('#{$icon-url}.ttf') format('truetype'), /* chrome、firefox、opera、Safari, Android, iOS 4.2+*/
12 | url('#{$icon-url}.svg#dxicon') format('svg'); /* iOS 4.1- */
13 |
14 | }
15 |
16 | .#{$iconfont-css-prefix} {
17 | @include iconfont-mixin;
18 | -webkit-font-smoothing: antialiased;
19 | -moz-osx-font-smoothing: grayscale;
20 | }
21 |
22 | .#{$iconfont-css-prefix}-setting:before {
23 | content: "\e654";
24 | }
25 |
26 | .#{$iconfont-css-prefix}-group-setting:before {
27 | content: "\e676";
28 | }
29 |
30 | .#{$iconfont-css-prefix}-todo:before {
31 | content: "\e62d";
32 | }
33 |
34 | .#{$iconfont-css-prefix}-right:before {
35 | content: "\e678";
36 | }
37 |
38 | .#{$iconfont-css-prefix}-news:before {
39 | content: "\e657";
40 | }
41 |
42 | .#{$iconfont-css-prefix}-contact:before {
43 | content: "\e651";
44 | }
45 |
46 | .#{$iconfont-css-prefix}-group:before {
47 | content: "\e655";
48 | }
49 |
50 | .#{$iconfont-css-prefix}-group1:before {
51 | content: "\e675";
52 | }
53 |
54 | .#{$iconfont-css-prefix}-group2:before {
55 | content: "\e68c";
56 | }
57 |
58 | .#{$iconfont-css-prefix}-addchat:before {
59 | content: "\e686";
60 | }
61 |
62 | .#{$iconfont-css-prefix}-addapp:before {
63 | content: "\e602";
64 | }
65 |
66 | .#{$iconfont-css-prefix}-xiangshang:before {
67 | content: "\e62c";
68 | }
69 |
70 | .#{$iconfont-css-prefix}-arrow-down:before {
71 | content: "\e66b";
72 | }
73 |
74 | //实心向下尖头
75 |
76 | .#{$iconfont-css-prefix}-arrow-right:before {
77 | content: "\e66a";
78 | }
79 |
80 | .#{$iconfont-css-prefix}-mute:before {
81 | content: "\e670";
82 | }
83 |
84 | .#{$iconfont-css-prefix}-round_close:before {
85 | content: "\e673";
86 | }
87 |
88 | //缺失
89 | .#{$iconfont-css-prefix}-search:before {
90 | content: "\e66e";
91 | }
92 |
93 | .#{$iconfont-css-prefix}-unrecognized:before {
94 | content: "\e66f";
95 | }
96 |
97 | .#{$iconfont-css-prefix}-zhankai:before {
98 | content: "\e656";
99 | }
100 |
101 | .#{$iconfont-css-prefix}-search-close:before {
102 | content: "\e679";
103 | }
104 |
105 | .#{$iconfont-css-prefix}-pull_right:before {
106 | content: "\e674";
107 | }
108 |
109 | .#{$iconfont-css-prefix}-message:before {
110 | content: "\e690";
111 | }
112 |
113 | .#{$iconfont-css-prefix}-checkbox:before {
114 | content: "\e668";
115 | }
116 |
117 | .#{$iconfont-css-prefix}-checkbox-checked:before {
118 | content: "\e669";
119 | }
120 |
121 | .#{$iconfont-css-prefix}-radio_box:before {
122 | content: "\e604";
123 | }
124 |
125 | .#{$iconfont-css-prefix}-radio_box_fill:before {
126 | content: "\e605";
127 | }
128 |
129 | .#{$iconfont-css-prefix}-gender-woman:before {
130 | content: "\e66d";
131 | }
132 |
133 | .#{$iconfont-css-prefix}-gender-man:before {
134 | content: "\e66c";
135 | }
136 |
137 | .#{$iconfont-css-prefix}-star-full:before {
138 | content: "\e680";
139 | }
140 |
141 | .#{$iconfont-css-prefix}-star:before {
142 | content: "\e67f";
143 | }
144 |
145 | .#{$iconfont-css-prefix}-folder:before {
146 | content: "\e67e";
147 | }
148 |
149 | .#{$iconfont-css-prefix}-sendfile:before {
150 | content: "\e61b";
151 | }
152 |
153 | .#{$iconfont-css-prefix}-screenshot:before {
154 | content: "\e62b";
155 | }
156 |
157 | .#{$iconfont-css-prefix}-unfold:before {
158 | content: "\e67b";
159 | }
160 |
161 | .#{$iconfont-css-prefix}-emoji:before {
162 | content: "\e610";
163 | }
164 |
165 | .#{$iconfont-css-prefix}-plus:before {
166 | content: "\e691";
167 | }
168 |
169 | .#{$iconfont-css-prefix}-post:before {
170 | content: "\e600";
171 | }
172 |
173 | //
174 | .#{$iconfont-css-prefix}-more:before {
175 | content: "\e652";
176 | }
177 |
178 | .#{$iconfont-css-prefix}-file:before {
179 | content: "\e650";
180 | }
181 |
182 | .#{$iconfont-css-prefix}-kefu:before {
183 | content: "\e653";
184 | }
185 |
186 |
187 | //toastr 使用的
188 | .#{$iconfont-css-prefix}-error:before {
189 | content: "\e693";
190 | }
191 |
192 | .#{$iconfont-css-prefix}-confirm:before {
193 | content: "\e684";
194 | }
195 |
196 | //app 自定义配置
197 | .#{$iconfont-css-prefix}-confirm-fill:before {
198 | content: "\e603";
199 | }
200 |
201 | .#{$iconfont-css-prefix}-remove-fill:before {
202 | content: "\e601";
203 | }
204 |
205 | .#{$iconfont-css-prefix}-left_voice_0:before {
206 | content: "\e681";
207 | }
208 |
209 | .#{$iconfont-css-prefix}-left_voice_1:before {
210 | content: "\e682";
211 | }
212 |
213 | .#{$iconfont-css-prefix}-left_voice_2:before {
214 | content: "\e683";
215 | }
216 |
217 | .#{$iconfont-css-prefix}-right_voice_0:before {
218 | content: "\e60b";
219 | }
220 |
221 | .#{$iconfont-css-prefix}-right_voice_1:before {
222 | content: "\e60c";
223 | }
224 |
225 | .#{$iconfont-css-prefix}-right_voice_2:before {
226 | content: "\e60d";
227 | }
228 |
229 | .#{$iconfont-css-prefix}-remove:before {
230 | content: "\e673";
231 | }
232 |
233 | .#{$iconfont-css-prefix}-warning:before {
234 | content: "\e672";
235 | }
236 |
237 | .#{$iconfont-css-prefix}-remove-group:before {
238 | content: "\e694";
239 | }
240 |
241 | .#{$iconfont-css-prefix}-round_down:before {
242 | content: "\e687";
243 | }
244 |
245 | .#{$iconfont-css-prefix}-quit:before {
246 | content: "\e67a";
247 | }
248 |
249 | .#{$iconfont-css-prefix}-crown:before {
250 | content: "\e667";
251 | }
252 |
253 | .#{$iconfont-css-prefix}-info:before {
254 | content: "\e677";
255 | }
256 |
257 | .#{$iconfont-css-prefix}-bubble-leader:before {
258 | content: "\e607";
259 | }
260 |
261 | .#{$iconfont-css-prefix}-bubble-star:before {
262 | content: "\e608";
263 | }
264 |
265 | .#{$iconfont-css-prefix}-yunpan:before {
266 | content: "\e613";
267 | }
268 |
269 | .#{$iconfont-css-prefix}-yunpan-close:before {
270 | content: "\e60a";
271 | }
272 |
273 | .#{$iconfont-css-prefix}-gongzongpingtai:before {
274 | content: "\e68b";
275 | }
276 |
277 | .#{$iconfont-css-prefix}-dalaba:before {
278 | content: "\e689";
279 | }
280 |
281 | .#{$iconfont-css-prefix}-message-fail:before {
282 | content: "\e606";
283 | }
284 |
285 | .#{$iconfont-css-prefix}-quote:before {
286 | content: "\e627";
287 | }
288 |
289 | .#{$iconfont-css-prefix}-chehui:before {
290 | content: "\e626";
291 | }
292 |
293 | .#{$iconfont-css-prefix}-close_notice:before {
294 | content: "\e670";
295 | }
296 |
297 | .#{$iconfont-css-prefix}-feedback:before {
298 | content: "\e614";
299 | }
300 |
301 | .#{$iconfont-css-prefix}-open-qr:before {
302 | content: "\e61a";
303 | }
304 |
305 | .#{$iconfont-css-prefix}-close-qr:before {
306 | content: "\e619";
307 | }
308 |
309 | .#{$iconfont-css-prefix}-view-qr:before {
310 | content: "\e618";
311 | }
312 |
313 | .#{$iconfont-css-prefix}-lfc:before { //left full corner
314 | content: "\e615";
315 | }
316 |
317 | .#{$iconfont-css-prefix}-llc:before {
318 | content: "\e616";
319 | }
320 |
321 | .#{$iconfont-css-prefix}-rfc:before { //left full corner
322 | content: "\e611";
323 | }
324 |
325 | .#{$iconfont-css-prefix}-rlc:before {
326 | content: "\e617";
327 | }
328 | .#{$iconfont-css-prefix}-i1000:before {
329 | content: "\e60e";
330 | }
331 |
332 | .#{$iconfont-css-prefix}-survey:before {
333 | content: "\e60f";
334 | }
335 |
336 | .#{$iconfont-css-prefix}-moremessage:before {
337 | content: "\e622";
338 | }
339 | .#{$iconfont-css-prefix}-forward:before {
340 | content: "\e628";
341 | }
342 |
343 | .#{$iconfont-css-prefix}-checkboxChecked:before {
344 | content: "\e620";
345 | }
346 | .#{$iconfont-css-prefix}-checkboxUncheck:before {
347 | content: "\e61f";
348 | }
349 | .#{$iconfont-css-prefix}-cancelChecked:before {
350 | content: "\e61e";
351 | }
352 |
353 | .#{$iconfont-css-prefix}-tag-receipt:before {
354 | content: "\e61d";
355 | }
356 |
357 | .#{$iconfont-css-prefix}-send-receipt:before {
358 | content: "\e61c";
359 | }
360 |
361 | .#{$iconfont-css-prefix}-daiban:before {
362 | content: "\e625";
363 | }
364 | .#{$iconfont-css-prefix}-backArrow:before {
365 | content: "\e623";
366 | }
367 | .#{$iconfont-css-prefix}-update:before {
368 | content: "\e624";
369 | }
370 | .#{$iconfont-css-prefix}-checked:before {
371 | content: "\e629";
372 | }
373 | .#{$iconfont-css-prefix}-toastr-close:before {
374 | content: "\e62a";
375 | }
376 | .#{$iconfont-css-prefix}-question-mark:before {
377 | content: "\e621";
378 | }
379 | .#{$iconfont-css-prefix}-daily-qun:before {
380 | content: "\e62e";
381 | }
382 | /*图片编辑器*/
383 | .#{$iconfont-css-prefix}-image-fangda:before {
384 | content: "\e638";
385 | }
386 | .#{$iconfont-css-prefix}-image-gou:before {
387 | content: "\e637";
388 | }
389 | .#{$iconfont-css-prefix}-image-xuanzhuan:before {
390 | content: "\e636";
391 | }
392 | .#{$iconfont-css-prefix}-image-masaike:before {
393 | content: "\e635";
394 | }
395 | .#{$iconfont-css-prefix}-image-suoxiao:before {
396 | content: "\e634";
397 | }
398 | .#{$iconfont-css-prefix}-image-text:before {
399 | content: "\e633";
400 | }
401 | .#{$iconfont-css-prefix}-image-huabi:before {
402 | content: "\e632";
403 | }
404 | .#{$iconfont-css-prefix}-image-jiantou:before {
405 | content: "\e631";
406 | }
407 | .#{$iconfont-css-prefix}-image-guanbi:before {
408 | content: "\e630";
409 | }
410 | .#{$iconfont-css-prefix}-image-jiancai:before {
411 | content: "\e62f";
412 | }
413 | /* end */
414 |
--------------------------------------------------------------------------------
/demo/scss/index.scss:
--------------------------------------------------------------------------------
1 | @import "./variable";
2 | @import "./mixin/iconfont";
3 | @import "./mixin/clearfix";
4 | @import "./theme/_default";
5 | @import "./normalize";
6 | @import "./iconfont";
7 | @import "./main";
--------------------------------------------------------------------------------
/demo/scss/main.scss:
--------------------------------------------------------------------------------
1 | .wrap_inner {
2 | width: 700px;
3 | height: 500px;
4 | margin: 0 auto;
5 | display: block;
6 | position: relative;
7 | .main {
8 | height: 100%;
9 | width: 100%;
10 | margin-top:50px;
11 | .upload-file-image-preview {
12 | height: 400px;
13 | width: 700px;
14 | text-align: center;
15 | .xm-fabric-photo-editor-canvas-container {
16 | display: inline-block;
17 | }
18 | }
19 | .file-button {
20 | height: 100px;
21 | width: 100%
22 | }
23 | .file-info-progress {
24 | height: 4px;
25 | background-color: $chat-bg-color;
26 | border-radius: 2px;
27 | position: absolute;
28 | bottom: 0px;
29 | left: 0;
30 | right: 0;
31 | .file-info-progress-bar {
32 | width: 0;
33 | height: 4px;
34 | background-color: $success;
35 | border-radius: 2px;
36 | }
37 | }
38 | .file-button.upload-success {
39 | width: 100%;
40 | height: 64px;
41 | }
42 | .file-button {
43 | display: flex;
44 | flex-direction: row;
45 | justify-content: space-between;
46 | padding: 14px 20px;
47 | box-sizing: border-box;
48 | .ctn-tips {
49 | flex: 1;
50 | position: relative;
51 | padding: 18px 16px;
52 | -webkit-user-select: none;
53 | -moz-user-select: none;
54 | .text {
55 | color: $primary;
56 | cursor: pointer;
57 | }
58 | .moretips {
59 | position: absolute;
60 | top: -108px;
61 | background: rgba(0, 0, 0, 0.87);
62 | border-radius: 4px;
63 | color: white;
64 | width: 520px;
65 | padding: 13px 20px 10px 20px;
66 | ol {
67 | padding: 0 20px;
68 | li {
69 | list-style: initial;
70 | }
71 | }
72 | .close {
73 | color: white;
74 | top: 0;
75 | right: 0;
76 | .dxicon {
77 | font-style: 10px;
78 | color: rgba(255, 255, 255, 0.69);
79 | }
80 | }
81 | .arrow {
82 | position: absolute;
83 | bottom: -13px;
84 | left: 40px;
85 | .dxicon {
86 | color: rgba(0, 0, 0, 0.87);
87 | font-size: 17px;
88 | }
89 | }
90 | }
91 | }
92 | .image-thumb-btns {
93 | vertical-align: middle;
94 | font-size: 0;
95 | &:before {
96 | content: '';
97 | display: inline-block;
98 | vertical-align: middle;
99 | font-size: 0;
100 | width: 0;
101 | height: 100%;
102 | }
103 | .thumb-divider {
104 | display: inline-block;
105 | height: 2px;
106 | border-bottom: 1px solid #eee;
107 | width: 60px;
108 | margin: 0 9px;
109 | }
110 | i {
111 | font-size: 18px;
112 | cursor: pointer;
113 | &:hover {
114 | color: $primary;
115 | }
116 | }
117 | }
118 | .image-tools-btns {
119 | vertical-align: middle;
120 | position: relative;
121 | .tools-divider {
122 | width: 1px;
123 | height: 24px;
124 | background: rgba(0,0,0,0.10);
125 | display: inline-block;
126 | vertical-align: middle;
127 | }
128 | i {
129 | font-size: 24px;
130 | margin-left: 14px;
131 | cursor: pointer;
132 | &:hover {
133 | color: $primary;
134 | }
135 | }
136 | .file-button-cancel {
137 | background: #FFFFFF;
138 | border: 1px solid rgba(0, 0, 0, 0.38);
139 | border-radius: 2px;
140 | height: 20px;
141 | width: 34px;
142 | font-size: 12px;
143 | color: rgba(0, 0, 0, 0.54);
144 | letter-spacing: 0;
145 | line-height: 18px;
146 | padding: 2px;
147 | cursor: pointer;
148 | }
149 | .tools-panel {
150 | position: absolute;
151 | top: 40px;
152 | width: 350px;
153 | height: 46px;
154 | box-sizing: border-box;
155 | background: #FFFFFF;
156 | box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.24);
157 | border-radius: 4px;
158 | padding: 8px 0;
159 | .tools-panel-brush {
160 | display: inline-block;
161 | div {
162 | height: 24px;
163 | width: 24px;
164 | display: inline-block;
165 | margin-left: 14px;
166 | text-align: center;
167 | vertical-align: middle;
168 | cursor: pointer;
169 | &:hover {
170 | span {
171 | background: rgba(0, 0, 0, 0.54);
172 | }
173 | }
174 | span {
175 | background: rgba(0, 0, 0, 0.24);
176 | &.active {
177 | background: rgba(0, 0, 0, 0.54);
178 | }
179 | }
180 | .small-brush {
181 | border-radius: 50%;
182 | width: 4px;
183 | height: 4px;
184 | display: inline-block;
185 | }
186 | .normal-brush {
187 | border-radius: 50%;
188 | width: 8px;
189 | height: 8px;
190 | display: inline-block;
191 | }
192 | .big-brush {
193 | border-radius: 50%;
194 | width: 12px;
195 | height: 12px;
196 | display: inline-block;
197 | }
198 | }
199 | }
200 | .tools-panel-color {
201 | display: inline-block;
202 | .color {
203 | border: 1px solid rgba(0, 0, 0, 0.10);
204 | border-radius: 2px;
205 | height: 16px;
206 | width: 16px;
207 | display: inline-block;
208 | margin-right: 8px;
209 | cursor: pointer;
210 | &.active {
211 | height: 22px;
212 | width: 22px;
213 | }
214 | &:hover {
215 | height: 22px;
216 | width: 22px;
217 | }
218 | &.red {
219 | background: #FF3440;
220 | }
221 | &.yellow {
222 | background: #FFCF50;
223 | }
224 | &.green {
225 | background: #00A344;
226 | }
227 | &.blue {
228 | background: #0DA9D6;
229 | }
230 | &.grey {
231 | background: #999999;
232 | }
233 | &.black {
234 | background: #000000;
235 | }
236 | &.white {
237 | background: #FFFFFF;
238 | }
239 | }
240 | }
241 | }
242 | }
243 | .ctn-btns {
244 | text-align: center;
245 | button {
246 | width: 88px;
247 | height: 36px;
248 | padding: 8px 0;
249 | }
250 | }
251 | &.upload-success {
252 | width: 100%;
253 | height: 56px;
254 | margin: 10px auto 0 auto;
255 | }
256 | }
257 | .file-button--pc {
258 | justify-content: flex-end;
259 | }
260 | }
261 | }
262 |
--------------------------------------------------------------------------------
/demo/scss/mixin/clearfix.scss:
--------------------------------------------------------------------------------
1 | // mixins for clearfix
2 | // ------------------------
3 | @mixin clearfix() {
4 | zoom: 1;
5 | &:before,
6 | &:after {
7 | content: " ";
8 | display: table;
9 | }
10 | &:after {
11 | clear: both;
12 | visibility: hidden;
13 | font-size: 0;
14 | height: 0;
15 | }
16 | }
--------------------------------------------------------------------------------
/demo/scss/mixin/common.scss:
--------------------------------------------------------------------------------
1 | @mixin cyclize-avatar($radius) {
2 | width: $radius * 2;
3 | border-radius: $radius;
4 | }
5 |
--------------------------------------------------------------------------------
/demo/scss/mixin/iconfont.scss:
--------------------------------------------------------------------------------
1 | @mixin iconfont-mixin {
2 | display: inline-block;
3 | font-style: normal;
4 | vertical-align: middle;
5 | text-align: center;
6 | text-transform: none;
7 | text-rendering: auto;
8 | line-height: 1;
9 | font-size: 14px;
10 |
11 | &:before {
12 | display: block;
13 | font-family: "dxicon" !important;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/demo/scss/normalize.scss:
--------------------------------------------------------------------------------
1 | /*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */
2 |
3 | //
4 | // 1. Set default font family to sans-serif.
5 | // 2. Prevent iOS and IE text size adjust after device orientation change,
6 | // without disabling user zoom.
7 | //
8 |
9 | html {
10 | font-family: sans-serif; // 1
11 | -ms-text-size-adjust: 100%; // 2
12 | -webkit-text-size-adjust: 100%; // 2
13 | }
14 |
15 | //
16 | // Remove default margin.
17 | //
18 |
19 | body {
20 | margin: 0;
21 | }
22 |
23 | // HTML5 display definitions
24 | // ==========================================================================
25 |
26 | //
27 | // Correct `block` display not defined for any HTML5 element in IE 8/9.
28 | // Correct `block` display not defined for `details` or `summary` in IE 10/11
29 | // and Firefox.
30 | // Correct `block` display not defined for `main` in IE 11.
31 | //
32 |
33 | article,
34 | aside,
35 | details,
36 | figcaption,
37 | figure,
38 | footer,
39 | header,
40 | hgroup,
41 | main,
42 | menu,
43 | nav,
44 | section,
45 | summary {
46 | display: block;
47 | }
48 |
49 | //
50 | // 1. Correct `inline-block` display not defined in IE 8/9.
51 | // 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
52 | //
53 |
54 | audio,
55 | canvas,
56 | progress,
57 | video {
58 | display: inline-block; // 1
59 | vertical-align: baseline; // 2
60 | }
61 |
62 | //
63 | // Prevent modern browsers from displaying `audio` without controls.
64 | // Remove excess height in iOS 5 devices.
65 | //
66 |
67 | audio:not([controls]) {
68 | display: none;
69 | height: 0;
70 | }
71 |
72 | //
73 | // Address `[hidden]` styling not present in IE 8/9/10.
74 | // Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22.
75 | //
76 |
77 | [hidden],
78 | template {
79 | display: none;
80 | }
81 |
82 | // Links
83 | // ==========================================================================
84 |
85 | //
86 | // Remove the gray background color from active links in IE 10.
87 | //
88 |
89 | a {
90 | background-color: transparent;
91 | }
92 |
93 | //
94 | // Improve readability of focused elements when they are also in an
95 | // active/hover state.
96 | //
97 |
98 | a:active,
99 | a:hover {
100 | outline: 0;
101 | }
102 |
103 | // Text-level semantics
104 | // ==========================================================================
105 |
106 | //
107 | // Address styling not present in IE 8/9/10/11, Safari, and Chrome.
108 | //
109 |
110 | abbr[title] {
111 | border-bottom: 1px dotted;
112 | }
113 |
114 | //
115 | // Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
116 | //
117 |
118 | b,
119 | strong {
120 | font-weight: bold;
121 | }
122 |
123 | //
124 | // Address styling not present in Safari and Chrome.
125 | //
126 |
127 | dfn {
128 | font-style: italic;
129 | }
130 |
131 | //
132 | // Address variable `h1` font-size and margin within `section` and `article`
133 | // contexts in Firefox 4+, Safari, and Chrome.
134 | //
135 |
136 | h1 {
137 | font-size: 2em;
138 | margin: 0.67em 0;
139 | }
140 |
141 | //
142 | // Address styling not present in IE 8/9.
143 | //
144 |
145 | mark {
146 | background: #ff0;
147 | color: #000;
148 | }
149 |
150 | //
151 | // Address inconsistent and variable font size in all browsers.
152 | //
153 |
154 | small {
155 | font-size: 80%;
156 | }
157 |
158 | //
159 | // Prevent `sub` and `sup` affecting `line-height` in all browsers.
160 | //
161 |
162 | sub,
163 | sup {
164 | font-size: 75%;
165 | line-height: 0;
166 | position: relative;
167 | vertical-align: baseline;
168 | }
169 |
170 | sup {
171 | top: -0.5em;
172 | }
173 |
174 | sub {
175 | bottom: -0.25em;
176 | }
177 |
178 | // Embedded content
179 | // ==========================================================================
180 |
181 | //
182 | // Remove border when inside `a` element in IE 8/9/10.
183 | //
184 |
185 | img {
186 | border: 0;
187 | }
188 |
189 | //
190 | // Correct overflow not hidden in IE 9/10/11.
191 | //
192 |
193 | svg:not(:root) {
194 | overflow: hidden;
195 | }
196 |
197 | // Grouping content
198 | // ==========================================================================
199 |
200 | //
201 | // Address margin not present in IE 8/9 and Safari.
202 | //
203 |
204 | figure {
205 | margin: 1em 40px;
206 | }
207 |
208 | //
209 | // Address differences between Firefox and other browsers.
210 | //
211 |
212 | hr {
213 | box-sizing: content-box;
214 | height: 0;
215 | }
216 |
217 | //
218 | // Contain overflow in all browsers.
219 | //
220 |
221 | //pre {
222 | // overflow: auto;
223 | //}
224 |
225 | //
226 | // Address odd `em`-unit font size rendering in all browsers.
227 | //
228 |
229 | code,
230 | kbd,
231 | pre,
232 | samp {
233 | font-family: monospace, monospace;
234 | font-size: 1em;
235 | }
236 |
237 | // Forms
238 | // ==========================================================================
239 |
240 | //
241 | // Known limitation: by default, Chrome and Safari on OS X allow very limited
242 | // styling of `select`, unless a `border` property is set.
243 | //
244 |
245 | //
246 | // 1. Correct color not being inherited.
247 | // Known issue: affects color of disabled elements.
248 | // 2. Correct font properties not being inherited.
249 | // 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
250 | //
251 |
252 | button,
253 | input,
254 | optgroup,
255 | select,
256 | textarea {
257 | color: inherit; // 1
258 | font: inherit; // 2
259 | margin: 0; // 3
260 | }
261 |
262 | //
263 | // Address `overflow` set to `hidden` in IE 8/9/10/11.
264 | //
265 |
266 | button {
267 | overflow: visible;
268 | }
269 |
270 | //
271 | // Address inconsistent `text-transform` inheritance for `button` and `select`.
272 | // All other form control elements do not inherit `text-transform` values.
273 | // Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
274 | // Correct `select` style inheritance in Firefox.
275 | //
276 |
277 | button,
278 | select {
279 | text-transform: none;
280 | }
281 |
282 | //
283 | // 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
284 | // and `video` controls.
285 | // 2. Correct inability to style clickable `input` types in iOS.
286 | // 3. Improve usability and consistency of cursor style between image-type
287 | // `input` and others.
288 | //
289 |
290 | button,
291 | html input[type="button"], // 1
292 | input[type="reset"],
293 | input[type="submit"] {
294 | -webkit-appearance: button; // 2
295 | cursor: pointer; // 3
296 | }
297 |
298 | //
299 | // Re-set default cursor for disabled elements.
300 | //
301 |
302 | button[disabled],
303 | html input[disabled] {
304 | cursor: default;
305 | }
306 |
307 | //
308 | // Remove inner padding and border in Firefox 4+.
309 | //
310 |
311 | button::-moz-focus-inner,
312 | input::-moz-focus-inner {
313 | border: 0;
314 | padding: 0;
315 | }
316 |
317 | //
318 | // Address Firefox 4+ groupSetting `line-height` on `input` using `!important` in
319 | // the UA stylesheet.
320 | //
321 |
322 | input {
323 | line-height: normal;
324 | }
325 |
326 | //
327 | // It's recommended that you don't attempt to style these elements.
328 | // Firefox's implementation doesn't respect box-sizing, padding, or width.
329 | //
330 | // 1. Address box sizing set to `content-box` in IE 8/9/10.
331 | // 2. Remove excess padding in IE 8/9/10.
332 | //
333 |
334 | input[type="checkbox"],
335 | input[type="radio"] {
336 | box-sizing: border-box; // 1
337 | padding: 0; // 2
338 | }
339 |
340 | //
341 | // Fix the cursor style for Chrome's increment/decrement buttons. For certain
342 | // `font-size` values of the `input`, it causes the cursor style of the
343 | // decrement button to change from `default` to `text`.
344 | //
345 |
346 | input[type="number"]::-webkit-inner-spin-button,
347 | input[type="number"]::-webkit-outer-spin-button {
348 | height: auto;
349 | }
350 |
351 | //
352 | // 1. Address `appearance` set to `searchfield` in Safari and Chrome.
353 | // 2. Address `box-sizing` set to `border-box` in Safari and Chrome.
354 | //
355 |
356 | input[type="search"] {
357 | -webkit-appearance: textfield; // 1
358 | box-sizing: content-box; //2
359 | }
360 |
361 | //
362 | // Remove inner padding and search cancel button in Safari and Chrome on OS X.
363 | // Safari (but not Chrome) clips the cancel button when the search input has
364 | // padding (and `textfield` appearance).
365 | //
366 |
367 | input[type="search"]::-webkit-search-cancel-button,
368 | input[type="search"]::-webkit-search-decoration {
369 | -webkit-appearance: none;
370 | }
371 |
372 | //
373 | // Define consistent border, margin, and padding.
374 | //
375 |
376 | fieldset {
377 | border: 1px solid #c0c0c0;
378 | margin: 0 2px;
379 | padding: 0.35em 0.625em 0.75em;
380 | }
381 |
382 | //
383 | // 1. Correct `color` not being inherited in IE 8/9/10/11.
384 | // 2. Remove padding so people aren't caught out if they zero out fieldsets.
385 | //
386 |
387 | legend {
388 | border: 0; // 1
389 | padding: 0; // 2
390 | }
391 |
392 | //
393 | // Remove default vertical scrollbar in IE 8/9/10/11.
394 | //
395 |
396 | textarea {
397 | overflow: auto;
398 | }
399 |
400 | //
401 | // Don't inherit the `font-weight` (applied by a rule above).
402 | // NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
403 | //
404 |
405 | optgroup {
406 | font-weight: bold;
407 | }
408 |
409 | // Tables
410 | // ==========================================================================
411 |
412 | //
413 | // Remove most spacing between table cells.
414 | //
415 |
416 | table {
417 | border-collapse: collapse;
418 | border-spacing: 0;
419 | }
420 |
421 | td,
422 | th {
423 | padding: 0;
424 | }
425 |
426 | ul, li{
427 | padding:0;
428 | margin:0;
429 | }
430 | li{
431 | list-style:none;
432 | }
433 |
--------------------------------------------------------------------------------
/demo/scss/theme/_default.scss:
--------------------------------------------------------------------------------
1 | //上面的六种颜色是要删掉的,不要再项目中引用了
2 | //灰白色,这里只是取一些更普通的名称
3 | //$color-darker : rgba(0,0,0,.87);/*#333333*/
4 | //$color-dark : rgba(0,0,0,.54);/*#666666*/
5 | //$color-base : rgba(0,0,0,.38);/*#666666*/
6 | //$color-medium : #CCCCCC;
7 | //$color-light : #E5E5E5;
8 | //$color-lighter : #F8F8F8;
9 | //$color-lightest : #FFFFFF;
10 |
11 | $border-color : rgba(0,0,0,0.1);
12 | $chat-bg-color : #F3F5F7;//右侧聊天窗口的背景色 rgba(255,255,255, 0.75)
13 | $session-bg-color : #E8EBEF;//右侧聊天窗口的背景色 rgba(255,255,255, 0.65)
14 | $right-bg-color : #F3F5F7;
15 | $right-chat-bg-color : transparent;
16 | $search-ipt-bg-color : rgba(255,255,255, 0.5);
17 |
18 | //主色
19 | $primary : #118BFB;
20 | $navigation-bg : #2E3E51;
21 | $navigation-hover-bg : rgba($primary, 0.1);
22 | $navigation-active-bg : rgba(16,139,251,0.15);
23 | $navigation-active-border : $primary;
24 | $navigation-color : rgba($color-lightest, 0.5);//导航栏icon颜色
25 |
26 | $divider-color : rgba($color-lightest,0.1);
27 |
28 |
29 | $hover-color: rgba($primary, 0.05);//hover时候的颜色
30 | $active-color: rgba($primary, 0.15);//激活时候的颜色
31 |
32 |
33 | //辅助颜色
34 | $toast-common : #F3F9FF;
35 | $toast-abnormal : #FFFAF2;
36 | $toast-error : #FFF7F6;
37 | $toast-success : #F7FBF5;
38 |
39 |
40 |
41 | $color-grey : #F4F4F4;//搜索框
42 | $color-you : #CAE5F9;
43 | $color-me : #E5E5E5;
44 |
45 | //输入框背景色
46 | $input-background-color : #FBFBFB;
47 |
48 |
49 |
50 | $bg-image : $chat-bg-color;//背景图片
51 |
52 | $main-bg-style : url($chat-bg-color) left bottom/cover no-repeat at-2x;
53 | $group-file-bg-image : '';
54 | $group-file-bg-group-path : rgba(0,0,0,0.02);
55 | $group-file-bg-group-path-border: 1px solid rgba(130, 130, 130, 0.1);
56 | $group-file-search-border: 1px solid rgba(0, 0, 0, 0.1);
57 | $group-file-header-tab-background-color: transparent;
58 |
59 |
60 | $btn-bg-base : $primary;
61 | $border-color-base : darken($primary, 5%);
62 | $btn-primary-color : #fff;
63 | $btn-primary-bg : $primary;
64 |
65 | $btn-ghost-color : #FFFFFF;
66 | $btn-ghost-bg : $supplement;
67 | $btn-ghost-border : darken($supplement, 5%);
68 |
69 | $btn-disable-color : #ccc;
70 | $btn-disable-bg : #f3f5f7;
71 | $btn-disable-border : $border-color-base;
72 |
73 | $btn-danger-color : #FFFFFF;
74 | $btn-danger-bg : $danger;
75 | $btn-danger-border : $danger;
76 |
77 |
78 |
79 | //new css
80 | $yunpan-bg : #FFFFFF
81 |
--------------------------------------------------------------------------------
/demo/scss/variable.scss:
--------------------------------------------------------------------------------
1 | //这里定义了一些基础的变量
2 |
3 | //通用基础颜色
4 | $color-darker : rgba(0,0,0,.87);/*#333333*/
5 | $color-dark : rgba(0,0,0,.54);/*#666666*/
6 | $color-base : rgba(0,0,0,.38);/*#999999*/
7 | $color-medium : rgba(0,0,0,.24);/*#CCCCCC*/
8 | $color-light : rgba(0,0,0,.1);/*#E5E5E5*/
9 | $color-light-1 : rgba(0,0,0,.05);
10 | $color-lighter : #F8F8F8;
11 | $color-lightest : #FFFFFF;
12 |
13 | //通用辅助颜色
14 | $supplement : #FF9801;
15 | $danger : #FF5D4A;
16 | $success : #5ABB3C;
17 | $name-other : #596E8F;
18 | $name-own : #CC841B;
19 | $color-pink : #F37D9A;//女性
20 | $color-blue : #46BEEF;//男性
21 |
22 |
23 | //存放一些宽度
24 |
25 | $main-title-height : 70px;//顶部70px
26 | $nav-width :60px;
27 | $left-width :280px;//内容区域左边列表宽度
28 |
29 |
30 | //z-index.scss
31 | $min-zindex : 1;
32 | $medium-zindex : 10;
33 | $large-zindex : 100;
34 | $max-zindex : 1000;
35 |
36 | //普通元素的padding
37 | $ctn-padding-xmin : 1px;
38 | $ctn-padding-min : 2px;
39 | $ctn-padding-normal : 5px;
40 | $ctn-padding-large : 10px;
41 | $ctn-padding-xlarge : 15px;
42 | $ctn-padding-xxlarge : 20px;
43 |
44 | //profile 宽高度
45 |
46 | //thumb 头像大小(头像的尺寸:80,60,40,36,28.)
47 | $thumb-base : 28px;
48 | $thumb-large : 36px;
49 | $thumb-xlarge : 40px;
50 | $thumb-xxlarge : 60px;
51 | $thumb-xxxlarge : 80px;
52 |
53 |
54 | //原有的头像尺寸(需要废弃)
55 | //$thumb
56 | //$thumb-min : 12px;
57 | //$thumb-xxxxlarge : $thumb-min * 6;
58 | //$thumb-big : 80px;
59 |
60 |
61 |
62 | //这里存字体相关的
63 | $font-family : "Helvetica Neue",Helvetica,"Apple Color Emoji",'Segoe UI Emoji', 'Segoe UI Symbol',Arial,"PingFang SC","Heiti SC", "Hiragino Sans GB","Microsoft YaHei","微软雅黑",sans-serif;
64 | $code-family : Consolas,Menlo,Courier,monospace;
65 | $font-size-small : 12px; //针对h6,说明文字
66 | $font-size-lSmall : 13px; //针对h6,说明文字
67 | $font-size-base : 14px; //正文,链接,h5
68 | $font-size-large : 16px; //h4
69 | $font-size-xlarge : 18px; //h3
70 | $font-size-xxlarge : 20px; //h2
71 | $font-size-xxxlarge : 24px; //h1
72 | $line-height-base : 1.5;
73 | $line-height-computed : floor(($font-size-base * $line-height-base));
74 |
75 | $border-radius-base : 2px;
76 | $border-radius-normal : 4px;
77 | $border-radius-large : 5px;
78 | $border-radius-xlarge : 10px;
79 | $border-radius-xxlarge : 15px;
80 |
81 |
82 |
83 | // ICONFONT
84 | $iconfont-css-prefix : dxicon;
85 |
86 | $icon-url : "//at.alicdn.com/t/font_ovsogmhgit4ndn29"; //经常会改变,以后会换成本地的地址
87 |
88 |
89 |
90 | // Animation
91 | $ease-out : cubic-bezier(0.215, 0.61, 0.355, 1);
92 | $ease-in : cubic-bezier(0.55, 0.055, 0.675, 0.19);
93 | $ease-in-out : cubic-bezier(0.645, 0.045, 0.355, 1);
94 | $ease-out-back : cubic-bezier(0.12, 0.4, 0.29, 1.46);
95 | $ease-in-back : cubic-bezier(0.71, -0.46, 0.88, 0.6);
96 | $ease-in-out-back : cubic-bezier(0.71, -0.46, 0.29, 1.46);
97 | $ease-out-circ : cubic-bezier(0.08, 0.82, 0.17, 1);
98 | $ease-in-circ : cubic-bezier(0.6, 0.04, 0.98, 0.34);
99 | $ease-in-out-circ : cubic-bezier(0.78, 0.14, 0.15, 0.86);
100 | $ease-out-quint : cubic-bezier(0.23, 1, 0.32, 1);
101 | $ease-in-quint : cubic-bezier(0.755, 0.05, 0.855, 0.06);
102 | $ease-in-out-quint : cubic-bezier(0.86, 0, 0.07, 1);
103 |
104 |
105 | // 按钮边框颜色,理论上应该是背景色加深一点就可以了
106 |
107 | //$border-color-base : #d9d9d9; // base border outline a component
108 | //$box-shadow-base : 0 0 4px rgba(0, 0, 0, 0.17);
109 | //$border-color-split : #e9e9e9; // split border inside a component
110 | $cursor-disabled : not-allowed;
111 | $btn-font-weight : normal;
112 |
113 |
114 |
115 |
116 |
117 | $btn-default-color : $color-darker;
118 | $btn-default-bg : $color-lighter;
119 | $btn-default-border : $color-light;
120 |
121 |
122 |
123 |
124 |
125 |
126 | $btn-padding-base : 8px 31px;
127 | $btn-border-radius-base : 4px;
128 |
129 | $btn-font-size-lg : 14px;
130 | $btn-padding-lg : 4px 11px 5px 11px;
131 | $btn-border-radius-lg : $btn-border-radius-base;
132 |
133 | $btn-padding-sm : 1px 7px;
134 | $btn-border-radius-sm : $btn-border-radius-base;
135 |
136 | $btn-circle-size : 28px;
137 | $btn-circle-size-lg : 32px;
138 | $btn-circle-size-sm : 22px;
139 |
140 | $msg-title-height: 70px;
141 | $msg-border-color: $color-light;
142 |
143 |
144 |
145 |
146 |
147 | //气泡页部分宽度定义
148 | $msg-medium-min-width: 630px;
149 | $msg-medium-max-width: 1000px;
150 | $msg-medium-width : 860px;
151 | $msg-medium-left : 0px;
152 | $msg-medium-right : 0px;
153 | $msg-medium-top: 0px;
154 | $msg-medium-bottom: 0px;
155 | $slidepanel-width: 470px;
156 |
157 |
--------------------------------------------------------------------------------
/html/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Title
6 |
7 |
8 |
9 | 404
10 |
11 |
--------------------------------------------------------------------------------
/html/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | kityphoto
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by yeanzhi on 17/2/7.
3 | */
4 | 'use strict';
5 | import FabricPhoto from './src/index.js';
6 | export default FabricPhoto;
7 |
--------------------------------------------------------------------------------
/jsdoc.conf.json:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ximing/fabric-photo/5e59eff9a37166f7ce14eed250c00329b444a091/jsdoc.conf.json
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fabric-photo",
3 | "version": "0.2.0",
4 | "description": "web 图片编辑器",
5 | "repository": "",
6 | "author": "ximing",
7 | "module": "dist/index.esm.js",
8 | "main": "dist/index.js",
9 | "scripts": {
10 | "i": "PUPPETEER_DOWNLOAD_HOST=https://npm.taobao.org/mirrors SASS_BINARY_SITE=https://npm.taobao.org/mirrors/node-sass npm install",
11 | "dev": "./node_modules/.bin/webpack-dev-server ",
12 | "install:website": "cd website && npm i --registry=https://registry.npmjs.com",
13 | "build:website": "cd website && rm -rf .umi dist && npm run docs:build",
14 | "build": "rollup -c"
15 | },
16 | "publishConfig": {
17 | "access": "public",
18 | "registry": "https://registry.npmjs.org/"
19 | },
20 | "dependencies": {
21 | "classnames": "^2.2.5",
22 | "fabric": "1.7.3"
23 | },
24 | "devDependencies": {
25 | "@babel/cli": "^7.8.3",
26 | "@babel/core": "^7.8.3",
27 | "@babel/generator": "^7.8.3",
28 | "@babel/parser": "^7.8.3",
29 | "@babel/plugin-external-helpers": "^7.8.3",
30 | "@babel/plugin-proposal-class-properties": "^7.8.3",
31 | "@babel/plugin-proposal-decorators": "^7.8.3",
32 | "@babel/plugin-proposal-do-expressions": "^7.8.3",
33 | "@babel/plugin-proposal-export-default-from": "^7.8.3",
34 | "@babel/plugin-proposal-export-namespace-from": "^7.8.3",
35 | "@babel/plugin-proposal-function-bind": "^7.8.3",
36 | "@babel/plugin-proposal-function-sent": "^7.8.3",
37 | "@babel/plugin-proposal-json-strings": "^7.8.3",
38 | "@babel/plugin-proposal-logical-assignment-operators": "^7.8.3",
39 | "@babel/plugin-proposal-nullish-coalescing-operator": "^7.4.4",
40 | "@babel/plugin-proposal-numeric-separator": "^7.8.3",
41 | "@babel/plugin-proposal-object-rest-spread": "^7.8.3",
42 | "@babel/plugin-proposal-optional-chaining": "^7.8.3",
43 | "@babel/plugin-proposal-pipeline-operator": "^7.3.2",
44 | "@babel/plugin-proposal-throw-expressions": "^7.8.3",
45 | "@babel/plugin-syntax-decorators": "^7.8.3",
46 | "@babel/plugin-syntax-do-expressions": "^7.8.3",
47 | "@babel/plugin-syntax-dynamic-import": "^7.8.3",
48 | "@babel/plugin-syntax-import-meta": "^7.8.3",
49 | "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
50 | "@babel/plugin-syntax-optional-chaining": "^7.8.3",
51 | "@babel/plugin-transform-react-jsx": "^7.8.3",
52 | "@babel/plugin-transform-runtime": "^7.8.3",
53 | "@babel/preset-env": "^7.8.3",
54 | "@babel/preset-react": "^7.8.3",
55 | "@commitlint/cli": "^8.3.5",
56 | "@commitlint/config-conventional": "^8.3.4",
57 | "@rollup/plugin-replace": "^2.2.0",
58 | "autoprefixer": "^9.7.4",
59 | "babel-eslint": "^10.0.1",
60 | "babel-loader": "^8.0.0-beta.0",
61 | "babel-plugin-import": "^1.8.0",
62 | "clean-webpack-plugin": "^3.0.0",
63 | "css-loader": "^3.4.2",
64 | "eslint": "^6.5.1",
65 | "eslint-config-airbnb": "^18.0.1",
66 | "eslint-config-prettier": "^6.4.0",
67 | "eslint-config-standard": "^14.1.0",
68 | "eslint-config-standard-jsx": "^8.1.0",
69 | "eslint-html-reporter": "^0.7.4",
70 | "eslint-loader": "^3.0.3",
71 | "eslint-plugin-flowtype": "^4.6.0",
72 | "eslint-plugin-import": "^2.18.2",
73 | "eslint-plugin-jest": "^23.6.0",
74 | "eslint-plugin-jsx-a11y": "^6.1.1",
75 | "eslint-plugin-node": "^11.0.0",
76 | "eslint-plugin-prettier": "^3.1.1",
77 | "eslint-plugin-promise": "^4.2.1",
78 | "eslint-plugin-react": "^7.18.0",
79 | "eslint-plugin-standard": "^4.0.1",
80 | "file-loader": "^5.0.2",
81 | "fs-extra": "^8.1.0",
82 | "html-loader": "^0.5.5",
83 | "html-webpack-plugin": "^3.0.6",
84 | "husky": "^4.2.2",
85 | "lerna": "^3.20.2",
86 | "less": "^3.11.1",
87 | "less-loader": "^5.0.0",
88 | "lint-staged": "^10.0.7",
89 | "memory-fs": "^0.5.0",
90 | "mini-css-extract-plugin": "^0.9.0",
91 | "mocha": "^7.0.0",
92 | "node-sass": "^4.13.1",
93 | "postcss": "^7.0.2",
94 | "postcss-clearfix": "^2.0.1",
95 | "postcss-flexbugs-fixes": "^4.2.0",
96 | "postcss-loader": "^3.0.0",
97 | "postcss-position": "^1.1.0",
98 | "postcss-preset-env": "^6.7.0",
99 | "postcss-size": "^3.0.0",
100 | "prettier": "^1.18.2",
101 | "react": "^16.10.2",
102 | "react-dom": "^16.10.2",
103 | "rollup": "^1.29.1",
104 | "rollup-plugin-alias": "^2.0.1",
105 | "rollup-plugin-babel": "^4.3.3",
106 | "rollup-plugin-commonjs": "^10.1.0",
107 | "rollup-plugin-json": "^4.0.0",
108 | "rollup-plugin-node-resolve": "^5.2.0",
109 | "rollup-plugin-url": "^3.0.1",
110 | "sass-loader": "^8.0.2",
111 | "style-loader": "^1.1.3",
112 | "stylelint": "^13.1.0",
113 | "url-loader": "^3.0.0",
114 | "webpack": "^4.41.0",
115 | "webpack-cli": "^3.3.9",
116 | "webpack-dev-server": "^3.8.2",
117 | "webpack-merge": "^4.1.4",
118 | "webpack-stream": "^5.1.1"
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by yeanzhi on 17/1/13.
3 | */
4 | module.exports = {
5 | plugins: [
6 | require('postcss-flexbugs-fixes'),
7 | require('postcss-preset-env')({
8 | autoprefixer: {
9 | flexbox: 'no-2009'
10 | },
11 | stage: 3
12 | }),
13 | require('postcss-clearfix')(),
14 | require('postcss-position')(),
15 | require('postcss-size')()
16 | ]
17 | };
18 |
--------------------------------------------------------------------------------
/public/demo.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ximing/fabric-photo/5e59eff9a37166f7ce14eed250c00329b444a091/public/demo.jpeg
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # fabric photo
2 |
3 | 基于 canvas 的纯前端的图片编辑器,支持方形,圆形,箭头,缩放,拖拽,鹰眼,马赛克,涂鸦,线条,导出 png,剪切等
4 | 
5 |
6 | ## online Demo
7 |
8 | 访问 [https://ximing.github.io/fabric-photo/](https://ximing.github.io/fabric-photo/) 体验
9 |
10 | ## 启动 demo
11 |
12 | ```bash
13 | # 安装依赖
14 | npm run i
15 | # 运行项目
16 | npm run dev
17 | ```
18 |
19 | [结合 react 使用方法](https://github.com/ximing/fabric-photo/blob/master/demo/main.js)
20 |
21 | ## License
22 |
23 | [MIT](https://github.com/ximing/fabric-photo/blob/master/LICENSE)
24 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | const { join } = require('path');
2 | const babel = require('rollup-plugin-babel');
3 | const alias = require('rollup-plugin-alias');
4 |
5 | const cwd = __dirname;
6 |
7 | const baseConfig = {
8 | input: join(cwd, 'src/index.js'),
9 | external: ['react', 'react-dom', 'jquery'],
10 | output: [
11 | {
12 | file: join(cwd, 'dist/index.js'),
13 | format: 'cjs',
14 | sourcemap: true,
15 | exports: 'named'
16 | }
17 | ],
18 | plugins: [
19 | alias({
20 | entries: [
21 | // {
22 | // find: 'fabric',
23 | // replacement: join(cwd, 'node_modules/fabric/dist/fabric.js')
24 | // }
25 | ]
26 | }),
27 | babel()
28 | ]
29 | };
30 | const esmConfig = {
31 | ...baseConfig,
32 | output: {
33 | ...baseConfig.output,
34 | sourcemap: true,
35 | format: 'es',
36 | file: join(cwd, 'dist/index.esm.js')
37 | }
38 | };
39 |
40 | function rollup() {
41 | const target = process.env.TARGET;
42 | if (target === 'umd') {
43 | return baseConfig;
44 | }
45 | if (target === 'esm') {
46 | return esmConfig;
47 | }
48 | return [baseConfig, esmConfig];
49 | }
50 | module.exports = rollup();
51 |
--------------------------------------------------------------------------------
/src/command.js:
--------------------------------------------------------------------------------
1 | import consts from './consts';
2 |
3 | import addObject from './commands/add-object';
4 | import remove from './commands/remove';
5 | import clear from './commands/clear';
6 | import loadImage from './commands/load-image.js';
7 | import zoom from './commands/zoom.js';
8 | import rotationImage from './commands/rotation-image.js';
9 |
10 | const { commandNames } = consts;
11 | const creators = {};
12 |
13 | creators[commandNames.CLEAR_OBJECTS] = clear;
14 | creators[commandNames.ADD_OBJECT] = addObject;
15 | creators[commandNames.REMOVE_OBJECT] = remove;
16 | creators[commandNames.LOAD_IMAGE] = loadImage;
17 | creators[commandNames.ZOOM] = zoom;
18 | creators[commandNames.ROTATE_IMAGE] = rotationImage;
19 |
20 | function create(name, ...args) {
21 | return creators[name].apply(null, args);
22 | }
23 |
24 | export default {
25 | create
26 | };
27 |
--------------------------------------------------------------------------------
/src/commands/add-object.js:
--------------------------------------------------------------------------------
1 | import util from '../lib/util';
2 | import Command from './base';
3 | import consts from '../consts';
4 |
5 | const { moduleNames } = consts;
6 | const { MAIN } = moduleNames;
7 | export default function(object) {
8 | util.stamp(object);
9 |
10 | return new Command({
11 | /**
12 | * @param {object.} moduleMap - Modules injection
13 | * @returns {Promise}
14 | * @ignore
15 | */
16 | execute(moduleMap) {
17 | return new Promise((resolve, reject) => {
18 | const canvas = moduleMap[MAIN].getCanvas();
19 |
20 | if (!canvas.contains(object)) {
21 | canvas.add(object);
22 | resolve(object);
23 | } else {
24 | reject();
25 | }
26 | });
27 | },
28 | /**
29 | * @param {object.} moduleMap - Modules injection
30 | * @returns {Promise}
31 | * @ignore
32 | */
33 | undo(moduleMap) {
34 | return new Promise((resolve, reject) => {
35 | const canvas = moduleMap[MAIN].getCanvas();
36 |
37 | if (canvas.contains(object)) {
38 | canvas.remove(object);
39 | resolve(object);
40 | } else {
41 | reject();
42 | }
43 | });
44 | }
45 | });
46 | }
47 |
--------------------------------------------------------------------------------
/src/commands/base.js:
--------------------------------------------------------------------------------
1 | export default class {
2 | constructor(actions) {
3 | this.execute = actions.execute;
4 |
5 | this.undo = actions.undo;
6 |
7 | this.executeCallback = null;
8 |
9 | this.undoCallback = null;
10 | }
11 |
12 | execute() {
13 | throw new Error('没有实现execute方法');
14 | }
15 |
16 | undo() {
17 | throw new Error('没有实现undo方法');
18 | }
19 |
20 | setExecuteCallback(callback) {
21 | this.executeCallback = callback;
22 | return this;
23 | }
24 |
25 | setUndoCallback(callback) {
26 | this.undoCallback = callback;
27 | return this;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/commands/clear.js:
--------------------------------------------------------------------------------
1 | import Command from './base';
2 | import consts from '../consts';
3 |
4 | const { moduleNames } = consts;
5 | const { MAIN } = moduleNames;
6 | export default function() {
7 | return new Command({
8 | /**
9 | * @param {object.} moduleMap - Components injection
10 | * @returns {Promise}
11 | * @ignore
12 | */
13 | execute(moduleMap) {
14 | return new Promise((resolve, reject) => {
15 | const canvas = moduleMap[MAIN].getCanvas();
16 | const objs = canvas.getObjects();
17 |
18 | // Slice: "canvas.clear()" clears the objects array, So shallow copy the array
19 | this.store = objs.slice();
20 | objs.slice().forEach((obj) => {
21 | if (obj.get('type') === 'group') {
22 | canvas.remove(obj);
23 | } else {
24 | obj.remove();
25 | }
26 | });
27 | resolve();
28 | });
29 | },
30 | /**
31 | * @param {object.} moduleMap - Components injection
32 | * @returns {Promise}
33 | * @ignore
34 | */
35 | undo(moduleMap) {
36 | const canvas = moduleMap[MAIN].getCanvas();
37 | const canvasContext = canvas;
38 |
39 | canvas.add.apply(canvasContext, this.store);
40 |
41 | return Promise.resolve();
42 | }
43 | });
44 | }
45 |
--------------------------------------------------------------------------------
/src/commands/load-image.js:
--------------------------------------------------------------------------------
1 | import Command from './base';
2 | import consts from '../consts';
3 |
4 | const { moduleNames } = consts;
5 | const { IMAGE_LOADER } = moduleNames;
6 | export default function(imageName, img) {
7 | return new Command({
8 | execute(moduleMap) {
9 | const loader = moduleMap[IMAGE_LOADER];
10 | const canvas = loader.getCanvas();
11 |
12 | this.store = {
13 | prevName: loader.getImageName(),
14 | prevImage: loader.getCanvasImage(),
15 | //"canvas.clear()" 会清除数据,所以用 slice进行一下 深拷贝
16 | objects: canvas.getObjects().slice()
17 | };
18 |
19 | canvas.clear();
20 |
21 | return loader.load(imageName, img);
22 | },
23 | undo(moduleMap) {
24 | const loader = moduleMap[IMAGE_LOADER];
25 | const canvas = loader.getCanvas();
26 | const canvasContext = canvas;
27 |
28 | canvas.clear();
29 | canvas.add.apply(canvasContext, this.store.objects);
30 |
31 | return loader.load(this.store.prevName, this.store.prevImage);
32 | }
33 | });
34 | }
35 |
--------------------------------------------------------------------------------
/src/commands/remove.js:
--------------------------------------------------------------------------------
1 | import Command from './base';
2 | import consts from '../consts';
3 |
4 | const { moduleNames } = consts;
5 | const { MAIN } = moduleNames;
6 | export default function(target) {
7 | return new Command({
8 | /**
9 | * @param {object.} moduleMap - Modules injection
10 | * @returns {Promise}
11 | * @ignore
12 | */
13 | execute(moduleMap) {
14 | return new Promise((resolve, reject) => {
15 | const canvas = moduleMap[MAIN].getCanvas();
16 | const isValidGroup = target && target.isType('group') && !target.isEmpty();
17 |
18 | if (isValidGroup) {
19 | canvas.discardActiveGroup(); // restore states for each objects
20 | this.store = target.getObjects();
21 | target.forEachObject((obj) => {
22 | obj.remove();
23 | });
24 | resolve();
25 | } else if (canvas.contains(target)) {
26 | this.store = [target];
27 | target.remove();
28 | resolve();
29 | } else {
30 | reject();
31 | }
32 | });
33 | },
34 | /**
35 | * @param {object.} moduleMap - Modules injection
36 | * @returns {Promise}
37 | * @ignore
38 | */
39 | undo(moduleMap) {
40 | const canvas = moduleMap[MAIN].getCanvas();
41 | const canvasContext = canvas;
42 |
43 | canvas.add.apply(canvasContext, this.store);
44 |
45 | return Promise.resolve();
46 | }
47 | });
48 | }
49 |
--------------------------------------------------------------------------------
/src/commands/rotation-image.js:
--------------------------------------------------------------------------------
1 | import Command from './base';
2 | import consts from '../consts';
3 |
4 | const { moduleNames } = consts;
5 | export default function(type, angle) {
6 | return new Command({
7 | execute(moduleMap) {
8 | const rotationComp = moduleMap[moduleNames.ROTATION];
9 | this.store = rotationComp.getCurrentAngle();
10 | return rotationComp[type](angle);
11 | },
12 | undo(moduleMap) {
13 | const rotationComp = moduleMap[moduleNames.ROTATION];
14 | return rotationComp.setAngle(this.store);
15 | }
16 | });
17 | }
18 |
--------------------------------------------------------------------------------
/src/commands/zoom.js:
--------------------------------------------------------------------------------
1 | import Command from './base';
2 | import consts from '../consts';
3 |
4 | const { moduleNames } = consts;
5 | const { MAIN } = moduleNames;
6 | export default function(zoom) {
7 | return new Command({
8 | /**
9 | * @param {object.} moduleMap - Modules injection
10 | * @returns {Promise}
11 | * @ignore
12 | */
13 | execute(moduleMap) {
14 | const mainModule = moduleMap[MAIN];
15 | // const canvas = mainModule.getCanvas();
16 | // this.zoom = (canvas.viewportTransform[0] || 1);
17 | // let zoom = rate * (canvas.viewportTransform[0] || 1);
18 | //直接这么设置是不行的,因为 这个本质上是在设置 transform 会导致坐标系乱套
19 | // this.zoom = canvas.getZoom();
20 | // canvas.setZoom(zoom);
21 | //使用新的方法通过放大canvas本身的方式进行设置
22 | this.zoom = mainModule.getZoom(); //mainModule.getZoom();
23 | mainModule.setZoom(zoom);
24 | return Promise.resolve(zoom);
25 | },
26 | /**
27 | * @param {object.} moduleMap - Modules injection
28 | * @returns {Promise}
29 | * @ignore
30 | */
31 | undo(moduleMap) {
32 | // const canvas = moduleMap[MAIN].getCanvas();
33 | // const canvasContext = canvas;
34 | // canvas.setZoom.call(canvasContext, this.zoom);
35 | const mainModule = moduleMap[MAIN];
36 | mainModule.setZoom(this.zoom);
37 | return Promise.resolve(this.zoom);
38 | }
39 | });
40 | }
41 |
--------------------------------------------------------------------------------
/src/consts.js:
--------------------------------------------------------------------------------
1 | import util from './lib/util';
2 |
3 | export default {
4 | /**
5 | * Component names
6 | * @type {Object.}
7 | */
8 | moduleNames: util.keyMirror(
9 | 'MAIN',
10 | 'IMAGE_LOADER',
11 | 'CROPPER',
12 | 'FLIP',
13 | 'ROTATION',
14 | 'FREE_DRAWING',
15 | 'LINE',
16 | 'ARROW',
17 | 'TEXT',
18 | 'ICON',
19 | 'FILTER',
20 | 'SHAPE',
21 | 'MOSAIC',
22 | 'PAN'
23 | ),
24 |
25 | /**
26 | * Command names
27 | * @type {Object.}
28 | */
29 | commandNames: util.keyMirror(
30 | 'CLEAR',
31 | 'LOAD_IMAGE',
32 | 'FLIP_IMAGE',
33 | 'ROTATE_IMAGE',
34 | 'ADD_OBJECT',
35 | 'REMOVE_OBJECT',
36 | 'APPLY_FILTER',
37 | 'ZOOM'
38 | ),
39 |
40 | /**
41 | * Event names
42 | * @type {Object.}
43 | */
44 | eventNames: {
45 | LOAD_IMAGE: 'loadImage',
46 | CLEAR_OBJECTS: 'clearObjects',
47 | CLEAR_IMAGE: 'clearImage',
48 | START_CROPPING: 'startCropping',
49 | END_CROPPING: 'endCropping',
50 | FLIP_IMAGE: 'flipImage',
51 | ROTATE_IMAGE: 'rotateImage',
52 | ADD_OBJECT: 'addObject',
53 | SELECT_OBJECT: 'selectObject',
54 | REMOVE_OBJECT: 'removeObject',
55 | ADJUST_OBJECT: 'adjustObject',
56 | START_FREE_DRAWING: 'startFreeDrawing',
57 | END_FREE_DRAWING: 'endFreeDrawing',
58 | START_LINE_DRAWING: 'startLineDrawing',
59 | END_LINE_DRAWING: 'endLineDrawing',
60 | START_PAN: 'startPan',
61 | END_PAN: 'endPan',
62 | START_ARROW_DRAWING: 'startArrowDrawing',
63 | END_ARROW_DRAWING: 'endArrowDrawing',
64 | START_MOSAIC_DRAWING: 'startMosaicDrawing',
65 | END_MOSAIC_DRAWING: 'endMosaicDrawing',
66 | EMPTY_REDO_STACK: 'emptyRedoStack',
67 | EMPTY_UNDO_STACK: 'emptyUndoStack',
68 | PUSH_UNDO_STACK: 'pushUndoStack',
69 | PUSH_REDO_STACK: 'pushRedoStack',
70 | ACTIVATE_TEXT: 'activateText',
71 | APPLY_FILTER: 'applyFilter',
72 | EDIT_TEXT: 'editText',
73 | MOUSE_DOWN: 'mousedown',
74 | CHANGE_ZOOM: 'changeZoom'
75 | },
76 |
77 | /**
78 | * Editor states
79 | * @type {Object.}
80 | */
81 | states: util.keyMirror(
82 | 'NORMAL',
83 | 'CROP',
84 | 'FREE_DRAWING',
85 | 'LINE',
86 | 'ARROW',
87 | 'MOSAIC',
88 | 'TEXT',
89 | 'SHAPE',
90 | 'PAN'
91 | ),
92 |
93 | /**
94 | * Shortcut key values
95 | * @type {Object.}
96 | */
97 | keyCodes: {
98 | Z: 90,
99 | Y: 89,
100 | SHIFT: 16,
101 | BACKSPACE: 8,
102 | DEL: 46
103 | },
104 |
105 | /**
106 | * Fabric object options
107 | * @type {Object.}
108 | */
109 | fObjectOptions: {
110 | SELECTION_STYLE: {
111 | borderColor: '#118BFB',
112 | cornerColor: '#FFFFFF',
113 | cornerStrokeColor: '#118BFB',
114 | cornerSize: 12,
115 | padding: 1,
116 | originX: 'center',
117 | originY: 'center',
118 | transparentCorners: false,
119 | cornerStyle: 'circle'
120 | }
121 | },
122 |
123 | rejectMessages: {
124 | flip: 'The flipX and flipY setting values are not changed.',
125 | rotation: 'The current angle is same the old angle.',
126 | loadImage: 'The background image is empty.',
127 | isLock: 'The executing command state is locked.',
128 | undo: 'The promise of undo command is reject.',
129 | redo: 'The promise of redo command is reject.'
130 | },
131 |
132 | MOUSE_MOVE_THRESHOLD: 10
133 | };
134 |
--------------------------------------------------------------------------------
/src/lib/canvas-to-blob.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by yeanzhi on 16/12/12.
3 | */
4 | let CanvasPrototype = window.HTMLCanvasElement && window.HTMLCanvasElement.prototype;
5 | let hasBlobConstructor =
6 | window.Blob &&
7 | (function() {
8 | try {
9 | return Boolean(new Blob());
10 | } catch (e) {
11 | return false;
12 | }
13 | })();
14 | let hasArrayBufferViewSupport =
15 | hasBlobConstructor &&
16 | window.Uint8Array &&
17 | (function() {
18 | try {
19 | return new Blob([new Uint8Array(100)]).size === 100;
20 | } catch (e) {
21 | return false;
22 | }
23 | })();
24 | let BlobBuilder =
25 | window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder;
26 | let dataURIPattern = /^data:((.*?)(;charset=.*?)?)(;base64)?,/;
27 | // eslint-disable-next-line import/no-mutable-exports
28 | let dataURLtoBlob =
29 | (hasBlobConstructor || BlobBuilder) &&
30 | window.atob &&
31 | window.ArrayBuffer &&
32 | window.Uint8Array &&
33 | function(dataURI) {
34 | let matches, mediaType, isBase64, dataString, byteString, arrayBuffer, intArray, i, bb;
35 | // Parse the dataURI components as per RFC 2397
36 | matches = dataURI.match(dataURIPattern);
37 | if (!matches) {
38 | throw new Error('invalid data URI');
39 | }
40 | // Default to text/plain;charset=US-ASCII
41 | mediaType = matches[2] ? matches[1] : `text/plain${matches[3] || ';charset=US-ASCII'}`;
42 | isBase64 = !!matches[4];
43 | dataString = dataURI.slice(matches[0].length);
44 | if (isBase64) {
45 | // Convert base64 to raw binary data held in a string:
46 | byteString = atob(dataString);
47 | } else {
48 | // Convert base64/URLEncoded data component to raw binary:
49 | byteString = decodeURIComponent(dataString);
50 | }
51 | // Write the bytes of the string to an ArrayBuffer:
52 | arrayBuffer = new ArrayBuffer(byteString.length);
53 | intArray = new Uint8Array(arrayBuffer);
54 | for (i = 0; i < byteString.length; i += 1) {
55 | intArray[i] = byteString.charCodeAt(i);
56 | }
57 | // Write the ArrayBuffer (or ArrayBufferView) to a blob:
58 | if (hasBlobConstructor) {
59 | return new Blob([hasArrayBufferViewSupport ? intArray : arrayBuffer], {
60 | type: mediaType
61 | });
62 | }
63 | bb = new BlobBuilder();
64 | bb.append(arrayBuffer);
65 | return bb.getBlob(mediaType);
66 | };
67 | if (window.HTMLCanvasElement && !CanvasPrototype.toBlob) {
68 | if (CanvasPrototype.mozGetAsFile) {
69 | CanvasPrototype.toBlob = function(callback, type, quality) {
70 | if (quality && CanvasPrototype.toDataURL && dataURLtoBlob) {
71 | callback(dataURLtoBlob(this.toDataURL(type, quality)));
72 | } else {
73 | callback(this.mozGetAsFile('blob', type));
74 | }
75 | };
76 | } else if (CanvasPrototype.toDataURL && dataURLtoBlob) {
77 | CanvasPrototype.toBlob = function(callback, type, quality) {
78 | callback(dataURLtoBlob(this.toDataURL(type, quality)));
79 | };
80 | }
81 | }
82 | export default dataURLtoBlob;
83 | window.dataURLtoBlob = dataURLtoBlob;
84 |
--------------------------------------------------------------------------------
/src/lib/event.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by yeanzhi on 16/12/4.
3 | */
4 | const events = require('events');
5 | // 创建 eventEmitter 对象
6 | export default new events.EventEmitter();
7 |
--------------------------------------------------------------------------------
/src/lib/shape-resize-helper.js:
--------------------------------------------------------------------------------
1 | import { fabric } from 'fabric';
2 |
3 | const DIVISOR = {
4 | rect: 1,
5 | circle: 2,
6 | triangle: 1
7 | };
8 | const DIMENSION_KEYS = {
9 | rect: {
10 | w: 'width',
11 | h: 'height'
12 | },
13 | circle: {
14 | w: 'rx',
15 | h: 'ry'
16 | },
17 | triangle: {
18 | w: 'width',
19 | h: 'height'
20 | }
21 | };
22 |
23 | /**
24 | * Set the start point value to the shape object
25 | * @param {fabric.Object} shape - Shape object
26 | * @ignore
27 | */
28 | function setStartPoint(shape) {
29 | const originX = shape.getOriginX();
30 | const originY = shape.getOriginY();
31 | const originKey = originX.substring(0, 1) + originY.substring(0, 1);
32 |
33 | shape.startPoint = shape.origins[originKey];
34 | }
35 |
36 | /**
37 | * Get the positions of ratated origin by the pointer value
38 | * @param {{x: number, y: number}} origin - Origin value
39 | * @param {{x: number, y: number}} pointer - Pointer value
40 | * @param {number} angle - Rotating angle
41 | * @returns {object} Postions of origin
42 | * @ignore
43 | */
44 | function getPositionsOfRotatedOrigin(origin, pointer, angle) {
45 | const sx = origin.x;
46 | const sy = origin.y;
47 | const px = pointer.x;
48 | const py = pointer.y;
49 | const r = (angle * Math.PI) / 180;
50 | const rx = (px - sx) * Math.cos(r) - (py - sy) * Math.sin(r) + sx;
51 | const ry = (px - sx) * Math.sin(r) + (py - sy) * Math.cos(r) + sy;
52 |
53 | return {
54 | originX: sx > rx ? 'right' : 'left',
55 | originY: sy > ry ? 'bottom' : 'top'
56 | };
57 | }
58 |
59 | /**
60 | * Whether the shape has the center origin or not
61 | * @param {fabric.Object} shape - Shape object
62 | * @returns {boolean} State
63 | * @ignore
64 | */
65 | function hasCenterOrigin(shape) {
66 | return shape.getOriginX() === 'center' && shape.getOriginY() === 'center';
67 | }
68 |
69 | /**
70 | * Adjust the origin of shape by the start point
71 | * @param {{x: number, y: number}} pointer - Pointer value
72 | * @param {fabric.Object} shape - Shape object
73 | * @ignore
74 | */
75 | function adjustOriginByStartPoint(pointer, shape) {
76 | const centerPoint = shape.getPointByOrigin('center', 'center');
77 | const angle = -shape.getAngle();
78 | const originPositions = getPositionsOfRotatedOrigin(centerPoint, pointer, angle);
79 | const originX = originPositions.originX;
80 | const originY = originPositions.originY;
81 | const origin = shape.getPointByOrigin(originX, originY);
82 | const left = shape.getLeft() - (centerPoint.x - origin.x);
83 | const top = shape.getTop() - (centerPoint.x - origin.y);
84 |
85 | shape.set({
86 | originX,
87 | originY,
88 | left,
89 | top
90 | });
91 |
92 | shape.setCoords();
93 | }
94 |
95 | /**
96 | * Adjust the origin of shape by the moving pointer value
97 | * @param {{x: number, y: number}} pointer - Pointer value
98 | * @param {fabric.Object} shape - Shape object
99 | * @ignore
100 | */
101 | function adjustOriginByMovingPointer(pointer, shape) {
102 | const origin = shape.startPoint;
103 | const angle = -shape.getAngle();
104 | const originPositions = getPositionsOfRotatedOrigin(origin, pointer, angle);
105 | const originX = originPositions.originX;
106 | const originY = originPositions.originY;
107 |
108 | shape.setPositionByOrigin(origin, originX, originY);
109 | }
110 |
111 | /**
112 | * Adjust the dimension of shape on firing scaling event
113 | * @param {fabric.Object} shape - Shape object
114 | * @ignore
115 | */
116 | function adjustDimensionOnScaling(shape) {
117 | const type = shape.type;
118 | const dimensionKeys = DIMENSION_KEYS[type];
119 | const scaleX = shape.scaleX;
120 | const scaleY = shape.scaleY;
121 | let width = shape[dimensionKeys.w] * scaleX;
122 | let height = shape[dimensionKeys.h] * scaleY;
123 |
124 | if (shape.isRegular) {
125 | const maxScale = Math.max(scaleX, scaleY);
126 |
127 | width = shape[dimensionKeys.w] * maxScale;
128 | height = shape[dimensionKeys.h] * maxScale;
129 | }
130 |
131 | const options = {
132 | hasControls: false,
133 | hasBorders: false,
134 | scaleX: 1,
135 | scaleY: 1
136 | };
137 |
138 | options[dimensionKeys.w] = width;
139 | options[dimensionKeys.h] = height;
140 |
141 | shape.set(options);
142 | }
143 |
144 | /**
145 | * Adjust the dimension of shape on firing mouse move event
146 | * @param {{x: number, y: number}} pointer - Pointer value
147 | * @param {fabric.Object} shape - Shape object
148 | * @ignore
149 | */
150 | function adjustDimensionOnMouseMove(pointer, shape) {
151 | const origin = shape.startPoint;
152 | const type = shape.type;
153 | const divisor = DIVISOR[type];
154 | const dimensionKeys = DIMENSION_KEYS[type];
155 | const strokeWidth = shape.strokeWidth;
156 | const isTriangle = !!(shape.type === 'triangle');
157 | const options = {};
158 | let width = Math.abs(origin.x - pointer.x) / divisor;
159 | let height = Math.abs(origin.y - pointer.y) / divisor;
160 |
161 | if (width > strokeWidth) {
162 | width -= strokeWidth / divisor;
163 | }
164 |
165 | if (height > strokeWidth) {
166 | height -= strokeWidth / divisor;
167 | }
168 |
169 | if (shape.isRegular) {
170 | width = height = Math.max(width, height);
171 |
172 | if (isTriangle) {
173 | height = (Math.sqrt(3) / 2) * width;
174 | }
175 | }
176 |
177 | options[dimensionKeys.w] = width;
178 | options[dimensionKeys.h] = height;
179 |
180 | shape.set(options);
181 | }
182 |
183 | export default {
184 | /**
185 | * Set each origin value to shape
186 | * @param {fabric.Object} shape - Shape object
187 | */
188 | setOrigins(shape) {
189 | const leftTopPoint = shape.getPointByOrigin('left', 'top');
190 | const rightTopPoint = shape.getPointByOrigin('right', 'top');
191 | const rightBottomPoint = shape.getPointByOrigin('right', 'bottom');
192 | const leftBottomPoint = shape.getPointByOrigin('left', 'bottom');
193 |
194 | shape.origins = {
195 | lt: leftTopPoint,
196 | rt: rightTopPoint,
197 | rb: rightBottomPoint,
198 | lb: leftBottomPoint
199 | };
200 | },
201 |
202 | /**
203 | * Resize the shape
204 | * @param {fabric.Object} shape - Shape object
205 | * @param {{x: number, y: number}} pointer - Mouse pointer values on canvas
206 | * @param {boolean} isScaling - Whether the resizing action is scaling or not
207 | */
208 | resize(shape, pointer, isScaling) {
209 | if (hasCenterOrigin(shape)) {
210 | adjustOriginByStartPoint(pointer, shape);
211 | setStartPoint(shape);
212 | }
213 |
214 | if (isScaling) {
215 | adjustDimensionOnScaling(shape, pointer);
216 | } else {
217 | adjustDimensionOnMouseMove(pointer, shape);
218 | }
219 |
220 | adjustOriginByMovingPointer(pointer, shape);
221 | },
222 |
223 | /**
224 | * Adjust the origin position of shape to center
225 | * @param {fabric.Object} shape - Shape object
226 | */
227 | adjustOriginToCenter(shape) {
228 | const centerPoint = shape.getPointByOrigin('center', 'center');
229 | const originX = shape.getOriginX();
230 | const originY = shape.getOriginY();
231 | const origin = shape.getPointByOrigin(originX, originY);
232 | const left = shape.getLeft() + (centerPoint.x - origin.x);
233 | const top = shape.getTop() + (centerPoint.y - origin.y);
234 |
235 | shape.set({
236 | hasControls: true,
237 | hasBorders: true,
238 | originX: 'center',
239 | originY: 'center',
240 | left,
241 | top
242 | });
243 |
244 | shape.setCoords(); // For left, top properties
245 | }
246 | };
247 |
--------------------------------------------------------------------------------
/src/lib/util.js:
--------------------------------------------------------------------------------
1 | const { min, max } = Math;
2 |
3 | function isExisty(param) {
4 | return param != null;
5 | }
6 |
7 | function isUndefined(obj) {
8 | return obj === void 0;
9 | }
10 |
11 | function isNull(obj) {
12 | return obj === null;
13 | }
14 |
15 | function isTruthy(obj) {
16 | return isExisty(obj) && obj !== false;
17 | }
18 |
19 | function isFalsy(obj) {
20 | return !isTruthy(obj);
21 | }
22 |
23 | function isArguments(obj) {
24 | var result = isExisty(obj) && (toString.call(obj) === '[object Arguments]' || !!obj.callee);
25 |
26 | return result;
27 | }
28 |
29 | function isArray(obj) {
30 | return Array.isArray(obj);
31 | }
32 |
33 | function isFunction(obj) {
34 | return obj instanceof Function;
35 | }
36 |
37 | function createObject() {
38 | function F() {}
39 |
40 | return function(obj) {
41 | F.prototype = obj;
42 | return new F();
43 | };
44 | }
45 |
46 | function inherit(subType, superType) {
47 | var prototype = createObject(superType.prototype);
48 | prototype.constructor = subType;
49 | subType.prototype = prototype;
50 | }
51 |
52 | var lastId = 0;
53 |
54 | function stamp(obj) {
55 | obj.__xm_id = obj.__xm_id || ++lastId;
56 | return obj.__xm_id;
57 | }
58 |
59 | function pick(obj, paths) {
60 | var args = arguments,
61 | target = args[0],
62 | length = args.length,
63 | i;
64 | try {
65 | for (i = 1; i < length; i++) {
66 | target = target[args[i]];
67 | }
68 | return target;
69 | } catch (e) {
70 | return;
71 | }
72 | }
73 |
74 | function hasStamp(obj) {
75 | return isExisty(pick(obj, '__xm_id'));
76 | }
77 |
78 | function resetLastId() {
79 | lastId = 0;
80 | }
81 | function inArray(val, arr, startIndex = 0) {
82 | arr = arr || [];
83 | let len = arr.length;
84 | for (let i = startIndex; i < len; i++) {
85 | if (arr[i] === val) {
86 | return true;
87 | }
88 | }
89 | return false;
90 | }
91 | function compareJSON(object) {
92 | var leftChain,
93 | rightChain,
94 | argsLen = arguments.length,
95 | i;
96 |
97 | function isSameObject(x, y) {
98 | var p;
99 |
100 | if (isNaN(x) && isNaN(y) && isNumber(x) && isNumber(y)) {
101 | return true;
102 | }
103 |
104 | if (x === y) {
105 | return true;
106 | }
107 |
108 | if (
109 | (isFunction(x) && isFunction(y)) ||
110 | (x instanceof Date && y instanceof Date) ||
111 | (x instanceof RegExp && y instanceof RegExp) ||
112 | (x instanceof String && y instanceof String) ||
113 | (x instanceof Number && y instanceof Number)
114 | ) {
115 | return x.toString() === y.toString();
116 | }
117 |
118 | if (!(x instanceof Object && y instanceof Object)) {
119 | return false;
120 | }
121 |
122 | if (
123 | x.isPrototypeOf(y) ||
124 | y.isPrototypeOf(x) ||
125 | x.constructor !== y.constructor ||
126 | x.prototype !== y.prototype
127 | ) {
128 | return false;
129 | }
130 |
131 | if (inArray(x, leftChain) > -1 || inArray(y, rightChain) > -1) {
132 | return false;
133 | }
134 |
135 | for (p in y) {
136 | if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
137 | return false;
138 | } else if (typeof y[p] !== typeof x[p]) {
139 | return false;
140 | }
141 | }
142 |
143 | for (p in x) {
144 | if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
145 | return false;
146 | } else if (typeof y[p] !== typeof x[p]) {
147 | return false;
148 | }
149 |
150 | if (typeof x[p] === 'object' || typeof x[p] === 'function') {
151 | leftChain.push(x);
152 | rightChain.push(y);
153 |
154 | if (!isSameObject(x[p], y[p])) {
155 | return false;
156 | }
157 |
158 | leftChain.pop();
159 | rightChain.pop();
160 | } else if (x[p] !== y[p]) {
161 | return false;
162 | }
163 | }
164 |
165 | return true;
166 | }
167 |
168 | if (argsLen < 1) {
169 | return true;
170 | }
171 |
172 | for (i = 1; i < argsLen; i++) {
173 | leftChain = [];
174 | rightChain = [];
175 |
176 | if (!isSameObject(arguments[0], arguments[i])) {
177 | return false;
178 | }
179 | }
180 |
181 | return true;
182 | }
183 |
184 | function clamp(value, minValue, maxValue) {
185 | let temp;
186 | if (minValue > maxValue) {
187 | temp = minValue;
188 | minValue = maxValue;
189 | maxValue = temp;
190 | }
191 |
192 | return max(minValue, min(value, maxValue));
193 | }
194 |
195 | function keyMirror(...args) {
196 | const obj = {};
197 |
198 | args.forEach((key) => {
199 | obj[key] = key;
200 | });
201 |
202 | return obj;
203 | }
204 |
205 | function makeStyleText(styleObj) {
206 | let styleStr = '';
207 | Object.keys(styleObj).forEach((key) => {
208 | styleStr += `${key}: ${styleObj[key]};`;
209 | });
210 |
211 | return styleStr;
212 | }
213 | var browser = {
214 | chrome: false,
215 | firefox: false,
216 | safari: false,
217 | msie: false,
218 | edge: false,
219 | others: false,
220 | version: 0
221 | };
222 | var nav = window.navigator,
223 | appName = nav.appName.replace(/\s/g, '_'),
224 | userAgent = nav.userAgent;
225 |
226 | var rIE = /MSIE\s([0-9]+[.0-9]*)/,
227 | rIE11 = /Trident.*rv:11\./,
228 | rEdge = /Edge\/(\d+)\./,
229 | versionRegex = {
230 | firefox: /Firefox\/(\d+)\./,
231 | chrome: /Chrome\/(\d+)\./,
232 | safari: /Version\/([\d\.]+)\sSafari\/(\d+)/
233 | };
234 |
235 | var key, tmp;
236 |
237 | var detector = {
238 | Microsoft_Internet_Explorer: function() {
239 | // ie8 ~ ie10
240 | browser.msie = true;
241 | browser.version = parseFloat(userAgent.match(rIE)[1]);
242 | },
243 | Netscape: function() {
244 | var detected = false;
245 |
246 | if (rIE11.exec(userAgent)) {
247 | browser.msie = true;
248 | browser.version = 11;
249 | detected = true;
250 | } else if (rEdge.exec(userAgent)) {
251 | browser.edge = true;
252 | browser.version = userAgent.match(rEdge)[1];
253 | detected = true;
254 | } else {
255 | for (key in versionRegex) {
256 | if (versionRegex.hasOwnProperty(key)) {
257 | tmp = userAgent.match(versionRegex[key]);
258 | if (tmp && tmp.length > 1) {
259 | browser[key] = detected = true;
260 | browser.version = parseFloat(tmp[1] || 0);
261 | break;
262 | }
263 | }
264 | }
265 | }
266 | if (!detected) {
267 | browser.others = true;
268 | }
269 | }
270 | };
271 |
272 | var fn = detector[appName];
273 |
274 | if (fn) {
275 | detector[appName]();
276 | }
277 |
278 | function forEachArray(arr, iteratee, context) {
279 | var index = 0,
280 | len = arr.length;
281 |
282 | context = context || null;
283 |
284 | for (; index < len; index++) {
285 | if (iteratee.call(context, arr[index], index, arr) === false) {
286 | break;
287 | }
288 | }
289 | }
290 |
291 | function forEachOwnProperties(obj, iteratee, context) {
292 | var key;
293 |
294 | context = context || null;
295 |
296 | for (key in obj) {
297 | if (obj.hasOwnProperty(key)) {
298 | if (iteratee.call(context, obj[key], key, obj) === false) {
299 | break;
300 | }
301 | }
302 | }
303 | }
304 |
305 | function forEach(obj, iteratee, context) {
306 | if (Array.isArray(obj)) {
307 | forEachArray(obj, iteratee, context);
308 | } else {
309 | forEachOwnProperties(obj, iteratee, context);
310 | }
311 | }
312 | function map(obj, iteratee, context) {
313 | var resultArray = [];
314 |
315 | context = context || null;
316 |
317 | forEach(obj, function() {
318 | resultArray.push(iteratee.apply(context, arguments));
319 | });
320 |
321 | return resultArray;
322 | }
323 |
324 | function isObject(obj) {
325 | return obj === Object(obj);
326 | }
327 |
328 | function isString(obj) {
329 | return typeof obj === 'string' || obj instanceof String;
330 | }
331 |
332 | function isNumber(obj) {
333 | return typeof obj === 'number' || obj instanceof Number;
334 | }
335 | function bind(fn, obj) {
336 | var slice = Array.prototype.slice;
337 |
338 | if (fn.bind) {
339 | return fn.bind.apply(fn, slice.call(arguments, 1));
340 | }
341 |
342 | /* istanbul ignore next */
343 | var args = slice.call(arguments, 2);
344 |
345 | /* istanbul ignore next */
346 | return function() {
347 | /* istanbul ignore next */
348 | return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
349 | };
350 | }
351 | function extend(target, objects) {
352 | var source,
353 | prop,
354 | hasOwnProp = Object.prototype.hasOwnProperty,
355 | i,
356 | len;
357 |
358 | for (i = 1, len = arguments.length; i < len; i++) {
359 | source = arguments[i];
360 | for (prop in source) {
361 | if (hasOwnProp.call(source, prop)) {
362 | target[prop] = source[prop];
363 | }
364 | }
365 | }
366 | return target;
367 | }
368 | function setStyle(obj, css) {
369 | for (let atr in css) {
370 | obj.style.setProperty(atr, css[atr]);
371 | }
372 | }
373 | export default {
374 | createObject: createObject(),
375 | inherit: inherit,
376 | isFunction: isFunction,
377 | isArray: isArray,
378 | isArguments: isArguments,
379 | isString: isString,
380 | isNumber: isNumber,
381 | isFalsy: isFalsy,
382 | isObject: isObject,
383 | isTruthy: isTruthy,
384 | isNull: isNull,
385 | isUndefined: isUndefined,
386 | compareJSON: compareJSON,
387 | hasStamp: hasStamp,
388 | resetLastId: resetLastId,
389 | stamp: stamp,
390 | pick: pick,
391 | clamp: clamp,
392 | keyMirror: keyMirror,
393 | browser: browser,
394 | makeStyleText: makeStyleText,
395 | forEach: forEach,
396 | map: map,
397 | isExisty: isExisty,
398 | bind: bind,
399 | extend: extend,
400 | setStyle: setStyle,
401 | inArray: inArray
402 | };
403 |
--------------------------------------------------------------------------------
/src/module.js:
--------------------------------------------------------------------------------
1 | import CustomEvents from './lib/custom-event';
2 |
3 | import Main from './modules/main';
4 | import Draw from './modules/draw';
5 | import Text from './modules/text';
6 | import ImageLoader from './modules/image-loader';
7 | import Rotation from './modules/rotation';
8 | import Shape from './modules/shape';
9 | import Line from './modules/line';
10 | import Arrow from './modules/arrow';
11 | import Cropper from './modules/cropper';
12 | import Mosaic from './modules/mosaic';
13 | import Pan from './modules/pan';
14 |
15 | import consts from './consts';
16 |
17 | import util from './lib/util.js';
18 |
19 | const { eventNames, rejectMessages } = consts;
20 |
21 | export default class {
22 | constructor() {
23 | this._customEvents = new CustomEvents();
24 |
25 | this._undoStack = [];
26 | this._redoStack = [];
27 |
28 | this._moduleMap = {};
29 |
30 | /* Lock-flag for executing command*/
31 | this._isLocked = false;
32 |
33 | this._createModules();
34 | }
35 |
36 | _createModules() {
37 | const main = new Main();
38 |
39 | this._register(main);
40 | this._register(new Draw(main));
41 | this._register(new Text(main));
42 | this._register(new ImageLoader(main));
43 | this._register(new Mosaic(main));
44 | this._register(new Rotation(main));
45 | this._register(new Shape(main));
46 | this._register(new Line(main));
47 | this._register(new Arrow(main));
48 | this._register(new Cropper(main));
49 | this._register(new Pan(main));
50 | }
51 |
52 | _register(component) {
53 | this._moduleMap[component.getName()] = component;
54 | }
55 |
56 | _invokeExecution(command) {
57 | this.lock();
58 |
59 | return command
60 | .execute(this._moduleMap)
61 | .then((value) => {
62 | this.pushUndoStack(command);
63 | this.unlock();
64 | if (util.isFunction(command.executeCallback)) {
65 | command.executeCallback(value);
66 | }
67 |
68 | return value;
69 | })
70 | .catch((err) => {
71 | this.unlock();
72 | console.error(err);
73 | }) // do nothing with exception
74 | .then((value) => {
75 | this.unlock();
76 |
77 | return value;
78 | });
79 | }
80 |
81 | _invokeUndo(command) {
82 | this.lock();
83 |
84 | return command
85 | .undo(this._moduleMap)
86 | .then((value) => {
87 | this.pushRedoStack(command);
88 | this.unlock();
89 | if (util.isFunction(command.undoCallback)) {
90 | command.undoCallback(value);
91 | }
92 |
93 | return value;
94 | })
95 | .catch(() => {
96 | this.unlock();
97 | console.error(err);
98 | }) //TODO do nothing with exception
99 | .then((value) => {
100 | this.unlock();
101 |
102 | return value;
103 | });
104 | }
105 |
106 | _fire(...args) {
107 | const event = this._customEvents;
108 | const eventContext = event;
109 | event.emit.apply(eventContext, args);
110 | }
111 |
112 | on(...args) {
113 | const event = this._customEvents;
114 | const eventContext = event;
115 | event.on.apply(eventContext, args);
116 | }
117 |
118 | getModule(name) {
119 | return this._moduleMap[name];
120 | }
121 |
122 | lock() {
123 | this._isLocked = true;
124 | }
125 |
126 | unlock() {
127 | this._isLocked = false;
128 | }
129 |
130 | /**
131 | * 执行命令
132 | * 存储命令到undo然后清除 redoStack
133 | * @param {Command} command - Command
134 | * @returns {Promise}
135 | */
136 | invoke(command) {
137 | if (this._isLocked) {
138 | return Promise.reject(rejectMessages.isLock);
139 | }
140 |
141 | return this._invokeExecution(command).then((value) => {
142 | this.clearRedoStack();
143 |
144 | return value;
145 | });
146 | }
147 |
148 | //undo命令
149 | undo() {
150 | let command = this._undoStack.pop();
151 | let promise;
152 |
153 | if (command && this._isLocked) {
154 | this.pushUndoStack(command, true);
155 | command = null;
156 | }
157 | if (command) {
158 | if (this.isEmptyUndoStack()) {
159 | this._fire(eventNames.EMPTY_UNDO_STACK);
160 | }
161 | promise = this._invokeUndo(command);
162 | } else {
163 | promise = Promise.reject(rejectMessages.undo);
164 | }
165 |
166 | return promise;
167 | }
168 |
169 | //redo命令
170 | redo() {
171 | let command = this._redoStack.pop();
172 |
173 | let promise;
174 |
175 | if (command && this._isLocked) {
176 | this.pushRedoStack(command, true);
177 | command = null;
178 | }
179 | if (command) {
180 | if (this.isEmptyRedoStack()) {
181 | this._fire(eventNames.EMPTY_REDO_STACK);
182 | }
183 | promise = this._invokeExecution(command);
184 | } else {
185 | promise = Promise.reject(rejectMessages.redo);
186 | }
187 |
188 | return promise;
189 | }
190 |
191 | /**
192 | * Push undo stack
193 | * @param {Command} command - command
194 | * @param {boolean} [isSilent] - Fire event or not
195 | */
196 | pushUndoStack(command, isSilent) {
197 | this._undoStack.push(command);
198 | if (!isSilent) {
199 | this._fire(eventNames.PUSH_UNDO_STACK);
200 | }
201 | }
202 |
203 | pushRedoStack(command, isSilent) {
204 | this._redoStack.push(command);
205 | if (!isSilent) {
206 | this._fire(eventNames.PUSH_REDO_STACK);
207 | }
208 | }
209 |
210 | isEmptyRedoStack() {
211 | return this._redoStack.length === 0;
212 | }
213 |
214 | isEmptyUndoStack() {
215 | return this._undoStack.length === 0;
216 | }
217 |
218 | clearUndoStack() {
219 | if (!this.isEmptyUndoStack()) {
220 | this._undoStack = [];
221 | this._fire(eventNames.EMPTY_UNDO_STACK);
222 | }
223 | }
224 |
225 | clearRedoStack() {
226 | if (!this.isEmptyRedoStack()) {
227 | this._redoStack = [];
228 | this._fire(eventNames.EMPTY_REDO_STACK);
229 | }
230 | }
231 | }
232 |
--------------------------------------------------------------------------------
/src/modules/arrow.2.js:
--------------------------------------------------------------------------------
1 | import { fabric } from 'fabric';
2 | import Base from './base';
3 | import consts from '../consts';
4 |
5 | const abs = Math.abs;
6 | const arrowPath =
7 | 'M3.9603906,29.711582 C3.94156309,29.8708042 3.79272845,29.9999998 3.63155855,29.9999998 C3.482237,30.0001621 3.33535003,29.8737257 3.31603561,29.7117443 L2.24238114,5.11020599 C2.2384858,5.02109998 2.16642191,4.9706228 2.08072432,4.99789021 L0.0900407177,5.63039686 C0.00466773962,5.65750197 -0.0253588782,5.61871082 0.0233329345,5.5427516 L3.54894478,0.0568073759 C3.59747429,-0.0186649336 3.6757058,-0.0191518517 3.72455992,0.0566450699 L7.24725025,5.54047931 C7.29577976,5.61595162 7.26624006,5.65539199 7.18070478,5.62812457 L5.19034578,4.99691638 C5.10513511,4.96981127 5.03290892,5.01899 5.02901358,5.10923216 L3.9603906,29.711582 Z';
8 | export default class Arrow extends Base {
9 | constructor(parent) {
10 | super();
11 | this.setParent(parent);
12 | this.name = consts.moduleNames.ARROW;
13 | this._width = 5;
14 | this._radius = 3;
15 | this._dimension = {
16 | height: 20,
17 | width: 20
18 | };
19 | this._oColor = new fabric.Color('rgba(0, 0, 0, 0.5)');
20 | this._listeners = {
21 | mousedown: this._onFabricMouseDown.bind(this),
22 | mousemove: this._onFabricMouseMove.bind(this),
23 | mouseup: this._onFabricMouseUp.bind(this)
24 | };
25 | }
26 |
27 | /**
28 | * Start drawing arrow mode
29 | * @param {{width: ?number, color: ?string,radius:?number,dimension:?object} [setting] - Brush width & color
30 | */
31 | start(setting) {
32 | const canvas = this.getCanvas();
33 |
34 | canvas.defaultCursor = 'crosshair';
35 | canvas.selection = false;
36 |
37 | canvas.forEachObject((obj) => {
38 | obj.set({
39 | evented: false
40 | });
41 | });
42 | this.setBrush(setting);
43 | canvas.on({
44 | 'mouse:down': this._listeners.mousedown
45 | });
46 | }
47 |
48 | /**
49 | * Set brush
50 | * @param {{width: ?number, color: ?string,radius:?number,dimension:?object} [setting] - Brush width & color
51 | */
52 | setBrush(setting) {
53 | const brush = this.getCanvas().freeDrawingBrush;
54 |
55 | setting = setting || {};
56 | this._width = setting.width || this._width;
57 | this._radius = setting.radius || this._radius;
58 | this._dimension = Object.assign(this._dimension, setting.dimension);
59 |
60 | if (setting.color) {
61 | this._oColor = new fabric.Color(setting.color);
62 | }
63 | brush.width = this._width;
64 | brush.color = this._oColor.toRgba();
65 | }
66 |
67 | /**
68 | * Set obj style
69 | * @param {object} activeObj - Current selected text object
70 | * @param {object} styleObj - Initial styles
71 | */
72 | setStyle(activeObj, styleObj) {
73 | activeObj.set(styleObj);
74 | this.getCanvas().renderAll();
75 | }
76 |
77 | /**
78 | * End drawing line mode
79 | */
80 | end() {
81 | const canvas = this.getCanvas();
82 |
83 | canvas.defaultCursor = 'default';
84 | canvas.selection = false;
85 |
86 | canvas.forEachObject((obj) => {
87 | obj.set({
88 | evented: true
89 | });
90 | });
91 |
92 | canvas.off('mouse:down', this._listeners.mousedown);
93 | }
94 |
95 | /**
96 | * Mousedown event handler in fabric canvas
97 | * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object
98 | * @private
99 | */
100 | _onFabricMouseDown(fEvent) {
101 | const canvas = this.getCanvas();
102 | if (fEvent.target && fEvent.target.customType === 'arrow') {
103 | canvas.trigger('object:selected', { target: fEvent.target });
104 | return;
105 | }
106 | this.startPointer = canvas.getPointer(fEvent.e);
107 | let arrow = (this.arrow = new fabric.Path(arrowPath));
108 | this.arrow.set(consts.fObjectOptions.SELECTION_STYLE);
109 | this.arrow.set({
110 | left: this.startPointer.x,
111 | top: this.startPointer.y
112 | });
113 | this.arrow.setOriginX('center');
114 | this.arrow.setOriginY('bottom');
115 | arrow.customType = 'arrow';
116 | canvas.add(arrow);
117 | canvas.renderAll();
118 | canvas.on({
119 | 'mouse:move': this._listeners.mousemove,
120 | 'mouse:up': this._listeners.mouseup
121 | });
122 | }
123 |
124 | getAngle(x1, y1, x2, y2) {
125 | let x = Math.abs(x1 - x2),
126 | y = Math.abs(y1 - y2),
127 | z = Math.sqrt(x * x + y * y),
128 | rotat = Math.round((Math.asin(y / z) / Math.PI) * 180);
129 | // 第一象限
130 | if (x2 >= x1 && y2 <= y1) {
131 | rotat = 90 - rotat;
132 | }
133 | // 第二象限
134 | else if (x2 <= x1 && y2 <= y1) {
135 | rotat = rotat - 90;
136 | }
137 | // 第三象限
138 | else if (x2 <= x1 && y2 >= y1) {
139 | rotat = 270 - rotat;
140 | }
141 | // 第四象限
142 | else if (x2 >= x1 && y2 >= y1) {
143 | rotat = 90 + rotat;
144 | }
145 | return rotat;
146 | }
147 |
148 | /**
149 | * Mousemove event handler in fabric canvas
150 | * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object
151 | * @private
152 | */
153 | _onFabricMouseMove(fEvent) {
154 | const canvas = this.getCanvas();
155 | const pointer = canvas.getPointer(fEvent.e);
156 | const { x: sx, y: sy } = this.startPointer;
157 | const x = pointer.x;
158 | const y = pointer.y;
159 | if (abs(x - sx) + abs(y - sy) > 5) {
160 | if (x === sx && y > sy) {
161 | this.arrow.setOriginX('center');
162 | this.arrow.setOriginY('bottom');
163 | } else if (x < sx && y > sy) {
164 | this.arrow.setOriginX('right');
165 | this.arrow.setOriginY('bottom');
166 | } else if (x < sx && y === sy) {
167 | this.arrow.setOriginX('right');
168 | this.arrow.setOriginY('bottom');
169 | } else if (x < sx && y < sy) {
170 | this.arrow.setOriginX('right');
171 | this.arrow.setOriginY('top');
172 | } else if (x === sx && y === sy) {
173 | this.arrow.setOriginX('center');
174 | this.arrow.setOriginY('center');
175 | } else if (x > sx && y < sy) {
176 | this.arrow.setOriginX('left');
177 | this.arrow.setOriginY('top');
178 | } else if (x > sx && y === sy) {
179 | this.arrow.setOriginX('center');
180 | this.arrow.setOriginY('left');
181 | } else if (x > sx && y > sy) {
182 | this.arrow.setOriginX('left');
183 | this.arrow.setOriginY('bottom');
184 | } else if (x === sx && y < sy) {
185 | this.arrow.setOriginX('center');
186 | this.arrow.setOriginY('top');
187 | }
188 | let scale = Math.max(
189 | (abs(x - this.startPointer.x) / 8) * this.getRoot().getZoom(),
190 | (abs(y - this.startPointer.y) / 30) * this.getRoot().getZoom()
191 | );
192 | this.arrow.scale(scale);
193 | let angle = this.getAngle(this.startPointer.x, this.startPointer.y, x, y);
194 | this.arrow.setAngle(angle);
195 | canvas.renderAll();
196 | }
197 | }
198 |
199 | /**
200 | * Mouseup event handler in fabric canvas
201 | * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object
202 | * @private
203 | */
204 | _onFabricMouseUp() {
205 | const canvas = this.getCanvas();
206 |
207 | this.arrow = null;
208 |
209 | canvas.off({
210 | 'mouse:move': this._listeners.mousemove,
211 | 'mouse:up': this._listeners.mouseup
212 | });
213 | }
214 | }
215 |
--------------------------------------------------------------------------------
/src/modules/arrow.js:
--------------------------------------------------------------------------------
1 | import { fabric } from 'fabric';
2 | import Base from './base';
3 | import consts from '../consts';
4 | // import util from '../lib/util';
5 |
6 | // const resetStyles = {
7 | // fill: '#000000',
8 | // width: 5
9 | // };
10 | export default class Arrow extends Base {
11 | constructor(parent) {
12 | super();
13 | this.setParent(parent);
14 | this.name = consts.moduleNames.ARROW;
15 | this._width = 5;
16 | this._radius = 3;
17 | this._dimension = {
18 | height: 20,
19 | width: 20
20 | };
21 | this._oColor = new fabric.Color('rgba(0, 0, 0, 0.5)');
22 | this._listeners = {
23 | mousedown: this._onFabricMouseDown.bind(this),
24 | mousemove: this._onFabricMouseMove.bind(this),
25 | mouseup: this._onFabricMouseUp.bind(this)
26 | };
27 | }
28 |
29 | /**
30 | * Start drawing arrow mode
31 | * @param {{width: ?number, color: ?string,radius:?number,dimension:?object} [setting] - Brush width & color
32 | */
33 | start(setting) {
34 | const canvas = this.getCanvas();
35 |
36 | canvas.defaultCursor = 'crosshair';
37 | canvas.selection = false;
38 |
39 | canvas.forEachObject((obj) => {
40 | obj.set({
41 | evented: false
42 | });
43 | });
44 | this.setBrush(setting);
45 | canvas.on({
46 | 'mouse:down': this._listeners.mousedown
47 | });
48 | }
49 |
50 | /**
51 | * Set brush
52 | * @param {{width: ?number, color: ?string,radius:?number,dimension:?object} [setting] - Brush width & color
53 | */
54 | setBrush(setting) {
55 | const brush = this.getCanvas().freeDrawingBrush;
56 |
57 | setting = setting || {};
58 | this._width = setting.width || this._width;
59 | this._radius = setting.radius || this._radius;
60 | this._dimension = Object.assign(this._dimension, setting.dimension);
61 |
62 | if (setting.color) {
63 | this._oColor = new fabric.Color(setting.color);
64 | }
65 | brush.width = this._width;
66 | brush.color = this._oColor.toRgba();
67 | }
68 |
69 | /**
70 | * Set obj style
71 | * @param {object} activeObj - Current selected text object
72 | * @param {object} styleObj - Initial styles
73 | */
74 | setStyle(activeObj, styleObj) {
75 | activeObj.set(styleObj);
76 | this.getCanvas().renderAll();
77 | }
78 |
79 | /**
80 | * End drawing line mode
81 | */
82 | end() {
83 | const canvas = this.getCanvas();
84 |
85 | canvas.defaultCursor = 'default';
86 | canvas.selection = false;
87 |
88 | canvas.forEachObject((obj) => {
89 | obj.set({
90 | evented: true
91 | });
92 | });
93 |
94 | canvas.off('mouse:down', this._listeners.mousedown);
95 | }
96 |
97 | /**
98 | * Mousedown event handler in fabric canvas
99 | * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object
100 | * @private
101 | */
102 | _onFabricMouseDown(fEvent) {
103 | const canvas = this.getCanvas();
104 | // if(fEvent.target && fEvent.target.customType === 'arrow') {
105 | // canvas.trigger('object:selected', {target: fEvent.target});
106 | // return;
107 | // }
108 | const pointer = (this.startPointer = canvas.getPointer(fEvent.e));
109 | //this.drawArrow(pointer,pointer);
110 | let group = (this.group = new fabric.Group(
111 | [
112 | /*this.line, this.arrow, this.circle*/
113 | ],
114 | {
115 | left: pointer.x,
116 | top: pointer.y
117 | // originX: 'center',
118 | // originY: 'center',
119 | // selection:true,
120 | // transparentCorners: true,
121 | // hasControls :true,
122 | // hasBorders :true
123 | }
124 | ));
125 | this.group.set(consts.fObjectOptions.SELECTION_STYLE);
126 | // this.group.set('selectable', true);
127 | group.customType = 'arrow';
128 | canvas.add(group);
129 | canvas.renderAll();
130 | canvas.on({
131 | 'mouse:move': this._listeners.mousemove,
132 | 'mouse:up': this._listeners.mouseup
133 | });
134 | }
135 |
136 | drawArrow(startPointer, endPointer) {
137 | const points = [startPointer.x, startPointer.y, endPointer.x, endPointer.y];
138 | const line = (this.line = new fabric.Line(points, {
139 | stroke: this._oColor.toRgba(),
140 | strokeWidth: this._width,
141 | padding: 5,
142 | originX: 'center',
143 | originY: 'center'
144 | }));
145 |
146 | let centerX = (line.x1 + line.x2) / 2,
147 | centerY = (line.y1 + line.y2) / 2;
148 | let deltaX = line.left - centerX,
149 | deltaY = line.top - centerY;
150 |
151 | const arrow = (this.arrow = new fabric.Triangle({
152 | left: line.get('x1') + deltaX,
153 | top: line.get('y1') + deltaY,
154 | originX: 'center',
155 | originY: 'center',
156 | pointType: 'arrow_start',
157 | angle:
158 | startPointer.x === endPointer.x && startPointer.y === endPointer.y
159 | ? -45
160 | : this.calcArrowAngle(
161 | startPointer.x,
162 | startPointer.y,
163 | endPointer.x,
164 | endPointer.y
165 | ) - 90,
166 | width: this._dimension.width,
167 | height: this._dimension.height,
168 | fill: this._oColor.toRgba()
169 | }));
170 | const circle = (this.circle = new fabric.Circle({
171 | left: line.get('x2') + deltaX,
172 | top: line.get('y2') + deltaY,
173 | radius: this._radius,
174 | stroke: this._oColor.toRgba(),
175 | strokeWidth: this._width,
176 | originX: 'center',
177 | originY: 'center',
178 | pointType: 'arrow_end',
179 | fill: this._oColor.toRgba()
180 | }));
181 | line.customType = arrow.customType = circle.customType = 'arrow';
182 | }
183 |
184 | /**
185 | * Mousemove event handler in fabric canvas
186 | * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object
187 | * @private
188 | */
189 | _onFabricMouseMove(fEvent) {
190 | const canvas = this.getCanvas();
191 | const pointer = canvas.getPointer(fEvent.e);
192 | const { x, y } = pointer.x;
193 | if (Math.abs(x - this.startPointer.x) + Math.abs(y - this.startPointer.y) > 5) {
194 | this.group.remove(this.line, this.arrow, this.circle);
195 | this.drawArrow(pointer, this.startPointer);
196 | this.group.addWithUpdate(this.arrow);
197 | this.group.addWithUpdate(this.line);
198 | this.group.addWithUpdate(this.circle);
199 | canvas.renderAll();
200 | }
201 | }
202 |
203 | /**
204 | * Mouseup event handler in fabric canvas
205 | * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object
206 | * @private
207 | */
208 | _onFabricMouseUp() {
209 | const canvas = this.getCanvas();
210 |
211 | this.line = null;
212 | // canvas.setActiveObject(this.group);
213 |
214 | canvas.off({
215 | 'mouse:move': this._listeners.mousemove,
216 | 'mouse:up': this._listeners.mouseup
217 | });
218 | }
219 |
220 | calcArrowAngle(x1, y1, x2, y2) {
221 | var angle = 0,
222 | x,
223 | y;
224 | x = x2 - x1;
225 | y = y2 - y1;
226 | if (x === 0) {
227 | angle = y === 0 ? 0 : y > 0 ? Math.PI / 2 : (Math.PI * 3) / 2;
228 | } else if (y === 0) {
229 | angle = x > 0 ? 0 : Math.PI;
230 | } else {
231 | angle =
232 | x < 0
233 | ? Math.atan(y / x) + Math.PI
234 | : y < 0
235 | ? Math.atan(y / x) + 2 * Math.PI
236 | : Math.atan(y / x);
237 | }
238 | return (angle * 180) / Math.PI;
239 | }
240 | }
241 |
--------------------------------------------------------------------------------
/src/modules/base.js:
--------------------------------------------------------------------------------
1 | /*
2 | * module base class
3 | * all modules should inherite this base class
4 | */
5 | export default class {
6 | /**
7 | * Save image(background) of canvas
8 | * @param {string} name - Name of image
9 | * @param {fabric.Image} oImage - Fabric image instance
10 | */
11 | setCanvasImage(name, oImage) {
12 | this.getRoot().setCanvasImage(name, oImage);
13 | }
14 |
15 | /**
16 | * Returns canvas element of fabric.Canvas[[lower-canvas]]
17 | * @returns {HTMLCanvasElement}
18 | */
19 | getCanvasElement() {
20 | return this.getRoot().getCanvasElement();
21 | }
22 |
23 | /**
24 | * Get fabric.Canvas instance
25 | * @returns {fabric.Canvas}
26 | */
27 | getCanvas() {
28 | return this.getRoot().getCanvas();
29 | }
30 |
31 | /**
32 | * Get canvasImage (fabric.Image instance)
33 | * @returns {fabric.Image}
34 | */
35 | getCanvasImage() {
36 | return this.getRoot().getCanvasImage();
37 | }
38 |
39 | /**
40 | * Get image name
41 | * @returns {string}
42 | */
43 | getImageName() {
44 | return this.getRoot().getImageName();
45 | }
46 |
47 | /**
48 | * Get image editor
49 | * @returns {ImageEditor}
50 | */
51 | getEditor() {
52 | return this.getRoot().getEditor();
53 | }
54 |
55 | /**
56 | * Return component name
57 | * @returns {string}
58 | */
59 | getName() {
60 | return this.name;
61 | }
62 |
63 | /**
64 | * Set image properties
65 | * @param {object} setting - Image properties
66 | * @param {boolean} [withRendering] - If true, The changed image will be reflected in the canvas
67 | */
68 | setImageProperties(setting, withRendering) {
69 | this.getRoot().setImageProperties(setting, withRendering);
70 | }
71 |
72 | /**
73 | * Set canvas dimension - css only
74 | * @param {object} dimension - Canvas css dimension
75 | */
76 | setCanvasCssDimension(dimension) {
77 | this.getRoot().setCanvasCssDimension(dimension);
78 | }
79 |
80 | /**
81 | * Set canvas dimension - css only
82 | * @param {object} dimension - Canvas backstore dimension
83 | */
84 | setCanvasBackstoreDimension(dimension) {
85 | this.getRoot().setCanvasBackstoreDimension(dimension);
86 | }
87 |
88 | /**
89 | * Set parent
90 | * @param {Component|null} parent - Parent
91 | */
92 | setParent(parent) {
93 | this._parent = parent || null;
94 | }
95 |
96 | /**
97 | * Adjust canvas dimension with scaling image
98 | */
99 | adjustCanvasDimension() {
100 | this.getRoot().adjustCanvasDimension();
101 | }
102 |
103 | /**
104 | * Return parent.
105 | * If the view is root, return null
106 | * @returns {Component|null}
107 | */
108 | getParent() {
109 | return this._parent;
110 | }
111 |
112 | /**
113 | * Return root
114 | * @returns {Module}
115 | */
116 | getRoot() {
117 | let next = this.getParent();
118 | let current = this; // eslint-disable-line consistent-this
119 |
120 | while (next) {
121 | current = next;
122 | next = current.getParent();
123 | }
124 |
125 | return current;
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/modules/cropper.js:
--------------------------------------------------------------------------------
1 | import { fabric } from 'fabric';
2 |
3 | import Base from './base.js';
4 | import consts from '../consts';
5 | import Cropzone from '../shape/cropzone';
6 | import util from '../lib/util';
7 |
8 | const { MOUSE_MOVE_THRESHOLD } = consts;
9 | const { clamp, bind } = util;
10 | const { keyCodes } = consts;
11 |
12 | export default class Cropper extends Base {
13 | constructor(parent) {
14 | super();
15 |
16 | this.setParent(parent);
17 |
18 | this.name = consts.moduleNames.CROPPER;
19 |
20 | this._cropzone = null;
21 |
22 | this._startX = null;
23 |
24 | this._startY = null;
25 |
26 | this._withShiftKey = false;
27 |
28 | this._listeners = {
29 | keydown: bind(this._onKeyDown, this),
30 | keyup: bind(this._onKeyUp, this),
31 | mousedown: bind(this._onFabricMouseDown, this),
32 | mousemove: bind(this._onFabricMouseMove, this),
33 | mouseup: bind(this._onFabricMouseUp, this)
34 | };
35 | }
36 |
37 | start() {
38 | if (this._cropzone) {
39 | return;
40 | }
41 | const canvas = this.getCanvas();
42 | canvas.forEachObject((obj) => {
43 | // {@link http://fabricjs.com/docs/fabric.Object.html#evented}
44 | obj.evented = false;
45 | });
46 | let canvasCssWidth = parseInt(canvas.wrapperEl.style['width'], 10),
47 | canvasCssHeight = parseInt(canvas.wrapperEl.style['height'], 10),
48 | canvasWidth = canvas.upperCanvasEl.width;
49 | let radio = canvasCssWidth / canvasWidth;
50 | let marginLeft = (canvasCssWidth * 0.1) / radio;
51 | let marginTop = (canvasCssHeight * 0.1) / radio;
52 | let width = (canvasCssWidth * 0.8) / radio;
53 | let height = (canvasCssHeight * 0.8) / radio;
54 |
55 | this._cropzone = new Cropzone({
56 | left: marginLeft,
57 | top: marginTop,
58 | width: width,
59 | height: height,
60 | strokeWidth: 0, // {@link https://github.com/kangax/fabric.js/issues/2860}
61 | cornerStyle: 'circle',
62 | cornerColor: '#FFFFFF',
63 | cornerStrokeColor: '#118BFB',
64 | cornerSize: 15,
65 | fill: 'transparent',
66 | hasRotatingPoint: false,
67 | hasBorders: false,
68 | lockScalingFlip: true,
69 | lockRotation: true
70 | });
71 | canvas.deactivateAll();
72 | canvas.add(this._cropzone);
73 | canvas.on('mouse:down', this._listeners.mousedown);
74 | canvas.selection = false;
75 | canvas.defaultCursor = 'crosshair';
76 | canvas.setActiveObject(this._cropzone);
77 |
78 | fabric.util.addListener(document, 'keydown', this._listeners.keydown);
79 | fabric.util.addListener(document, 'keyup', this._listeners.keyup);
80 | }
81 |
82 | /**
83 | * End cropping
84 | * @param {boolean} isApplying - Is applying or not
85 | * @returns {?{imageName: string, url: string}} cropped Image data
86 | */
87 | end(isApplying) {
88 | const canvas = this.getCanvas();
89 | const cropzone = this._cropzone;
90 | let data;
91 | canvas.off('mouse:down', this._listeners.mousedown);
92 | fabric.util.removeListener(document, 'keydown', this._listeners.keydown);
93 | fabric.util.removeListener(document, 'keyup', this._listeners.keyup);
94 | if (!cropzone) {
95 | return null;
96 | }
97 | cropzone.remove();
98 | canvas.selection = false;
99 | canvas.defaultCursor = 'default';
100 | canvas.forEachObject((obj) => {
101 | obj.evented = true;
102 | });
103 | if (isApplying) {
104 | data = this._getCroppedImageData();
105 | }
106 | this._cropzone = null;
107 |
108 | return data;
109 | }
110 |
111 | _onFabricMouseDown(fEvent) {
112 | const canvas = this.getCanvas();
113 |
114 | if (fEvent.target) {
115 | return;
116 | }
117 |
118 | canvas.selection = false;
119 | const coord = canvas.getPointer(fEvent.e);
120 |
121 | this._startX = coord.x;
122 | this._startY = coord.y;
123 |
124 | canvas.on({
125 | 'mouse:move': this._listeners.mousemove,
126 | 'mouse:up': this._listeners.mouseup
127 | });
128 | }
129 |
130 | _onFabricMouseMove(fEvent) {
131 | const canvas = this.getCanvas();
132 | const pointer = canvas.getPointer(fEvent.e);
133 | const x = pointer.x;
134 | const y = pointer.y;
135 | const cropzone = this._cropzone;
136 |
137 | if (Math.abs(x - this._startX) + Math.abs(y - this._startY) > MOUSE_MOVE_THRESHOLD) {
138 | cropzone.remove();
139 | cropzone.set(this._calcRectDimensionFromPoint(x, y));
140 |
141 | canvas.add(cropzone);
142 | }
143 | }
144 |
145 | /**
146 | * Get rect dimension setting from Canvas-Mouse-Position(x, y)
147 | * @param {number} x - Canvas-Mouse-Position x
148 | * @param {number} y - Canvas-Mouse-Position Y
149 | * @returns {{left: number, top: number, width: number, height: number}}
150 | * @private
151 | */
152 | _calcRectDimensionFromPoint(x, y) {
153 | const canvas = this.getCanvas();
154 | const canvasWidth = canvas.getWidth();
155 | const canvasHeight = canvas.getHeight();
156 | const startX = this._startX;
157 | const startY = this._startY;
158 | let left = clamp(x, 0, startX);
159 | let top = clamp(y, 0, startY);
160 | let width = clamp(x, startX, canvasWidth) - left; // (startX <= x(mouse) <= canvasWidth) - left
161 | let height = clamp(y, startY, canvasHeight) - top; // (startY <= y(mouse) <= canvasHeight) - top
162 |
163 | if (this._withShiftKey) {
164 | // make fixed ratio cropzone
165 | if (width > height) {
166 | height = width;
167 | } else if (height > width) {
168 | width = height;
169 | }
170 |
171 | if (startX >= x) {
172 | left = startX - width;
173 | }
174 |
175 | if (startY >= y) {
176 | top = startY - height;
177 | }
178 | }
179 |
180 | return {
181 | left,
182 | top,
183 | width,
184 | height
185 | };
186 | }
187 |
188 | _onFabricMouseUp() {
189 | const cropzone = this._cropzone;
190 | const listeners = this._listeners;
191 | const canvas = this.getCanvas();
192 |
193 | canvas.setActiveObject(cropzone);
194 | canvas.off({
195 | 'mouse:move': listeners.mousemove,
196 | 'mouse:up': listeners.mouseup
197 | });
198 | }
199 |
200 | _getCroppedImageData() {
201 | const cropzone = this._cropzone;
202 |
203 | if (!cropzone.isValid()) {
204 | return null;
205 | }
206 |
207 | const cropInfo = {
208 | left: cropzone.getLeft(),
209 | top: cropzone.getTop(),
210 | width: cropzone.getWidth(),
211 | height: cropzone.getHeight()
212 | };
213 |
214 | return {
215 | imageName: this.getImageName(),
216 | url: this.getCanvas().toDataURL(cropInfo)
217 | };
218 | }
219 |
220 | _onKeyDown(e) {
221 | if (e.keyCode === keyCodes.SHIFT) {
222 | this._withShiftKey = true;
223 | }
224 | }
225 |
226 | _onKeyUp(e) {
227 | if (e.keyCode === keyCodes.SHIFT) {
228 | this._withShiftKey = false;
229 | }
230 | }
231 | }
232 |
--------------------------------------------------------------------------------
/src/modules/draw.js:
--------------------------------------------------------------------------------
1 | import { fabric } from 'fabric';
2 | import Base from './base.js';
3 | import consts from '../consts';
4 |
5 | export default class FreeDrawing extends Base {
6 | constructor(parent) {
7 | super();
8 | this.setParent(parent);
9 | this.name = consts.moduleNames.FREE_DRAWING;
10 | this.width = 12;
11 | this.oColor = new fabric.Color('rgba(0, 0, 0, 0.5)');
12 | }
13 |
14 | /**
15 | * Start free drawing mode
16 | * @param {{width: ?number, color: ?string}} [setting] - Brush width & color
17 | */
18 | start(setting) {
19 | const canvas = this.getCanvas();
20 | canvas.isDrawingMode = true;
21 | this.setBrush(setting);
22 | }
23 |
24 | /**
25 | * Set brush
26 | * @param {{width: ?number, color: ?string}} [setting] - Brush width & color
27 | */
28 | setBrush(setting) {
29 | let brush = this.getCanvas().freeDrawingBrush;
30 | setting = setting || {};
31 | this.width = setting.width || this.width;
32 | if (setting.color) {
33 | this.oColor = new fabric.Color(setting.color);
34 | }
35 | brush.width = this.width;
36 | brush.color = this.oColor.toRgba();
37 | }
38 |
39 | /**
40 | * Set obj style
41 | * @param {object} activeObj - Current selected text object
42 | * @param {object} styleObj - Initial styles
43 | */
44 | setStyle(activeObj, styleObj) {
45 | activeObj.set(styleObj);
46 | this.getCanvas().renderAll();
47 | }
48 |
49 | /**
50 | * End free drawing mode
51 | */
52 | end() {
53 | const canvas = this.getCanvas();
54 | canvas.isDrawingMode = false;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/modules/image-loader.js:
--------------------------------------------------------------------------------
1 | import Base from './base.js';
2 | import consts from '../consts';
3 |
4 | const {moduleNames, rejectMessages} = consts;
5 | const imageOption = {
6 | padding: 0,
7 | crossOrigin: 'anonymous'
8 | };
9 |
10 | export default class ImageLoader extends Base {
11 | constructor(parent) {
12 | super();
13 | this.setParent(parent);
14 | this.name = moduleNames.IMAGE_LOADER;
15 | }
16 |
17 | /**
18 | * Load image from url
19 | * @param {?string} imageName - File name
20 | * @param {?(fabric.Image|string)} img - fabric.Image instance or URL of an image
21 | * @returns {jQuery.Deferred} deferred
22 | */
23 | load(imageName, img) {
24 | let promise;
25 |
26 | if (!imageName && !img) { // Back to the initial state, not error.
27 | const canvas = this.getCanvas();
28 |
29 | canvas.backgroundImage = null;
30 | canvas.renderAll();
31 |
32 | promise = new Promise(resolve => {
33 | this.setCanvasImage('', null);
34 | resolve();
35 | });
36 | } else {
37 | promise = this._setBackgroundImage(img).then(oImage => {
38 | this.setCanvasImage(imageName, oImage);
39 | this.adjustCanvasDimension();
40 |
41 | return oImage;
42 | });
43 | }
44 |
45 | return promise;
46 | }
47 |
48 | /**
49 | * Set background image
50 | * @param {?(fabric.Image|String)} img fabric.Image instance or URL of an image to set background to
51 | * @returns {$.Deferred} deferred
52 | * @private
53 | */
54 | _setBackgroundImage(img) {
55 | if (!img) {
56 | return Promise.reject(rejectMessages.loadImage);
57 | }
58 |
59 | return new Promise((resolve, reject) => {
60 | const canvas = this.getCanvas();
61 |
62 | canvas.setBackgroundImage(img, () => {
63 | const oImage = canvas.backgroundImage;
64 |
65 | if (oImage.getElement()) {
66 | resolve(oImage);
67 | } else {
68 | reject();
69 | }
70 | }, imageOption);
71 | });
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/modules/line.js:
--------------------------------------------------------------------------------
1 | import { fabric } from 'fabric';
2 | import Base from './base';
3 | import consts from '../consts';
4 |
5 | export default class Line extends Base {
6 | constructor(parent) {
7 | super();
8 | this.setParent(parent);
9 | this.name = consts.moduleNames.LINE;
10 | this._width = 12;
11 | this._oColor = new fabric.Color('rgba(0, 0, 0, 0.5)');
12 |
13 | this._listeners = {
14 | mousedown: this._onFabricMouseDown.bind(this),
15 | mousemove: this._onFabricMouseMove.bind(this),
16 | mouseup: this._onFabricMouseUp.bind(this)
17 | };
18 | }
19 |
20 | /**
21 | * Start drawing line mode
22 | * @param {{width: ?number, color: ?string}} [setting] - Brush width & color
23 | */
24 | start(setting) {
25 | const canvas = this.getCanvas();
26 |
27 | canvas.defaultCursor = 'crosshair';
28 | canvas.selection = false;
29 |
30 | this.setBrush(setting);
31 |
32 | canvas.forEachObject((obj) => {
33 | obj.set({
34 | evented: false
35 | });
36 | });
37 |
38 | canvas.on({
39 | 'mouse:down': this._listeners.mousedown
40 | });
41 | }
42 |
43 | /**
44 | * Set brush
45 | * @param {{width: ?number, color: ?string}} [setting] - Brush width & color
46 | */
47 | setBrush(setting) {
48 | const brush = this.getCanvas().freeDrawingBrush;
49 |
50 | setting = setting || {};
51 | this._width = setting.width || this._width;
52 |
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 drawing line mode
62 | */
63 | end() {
64 | const canvas = this.getCanvas();
65 |
66 | canvas.defaultCursor = 'default';
67 | canvas.selection = false;
68 |
69 | canvas.forEachObject((obj) => {
70 | obj.set({
71 | evented: true
72 | });
73 | });
74 |
75 | canvas.off('mouse:down', this._listeners.mousedown);
76 | }
77 |
78 | /**
79 | * Mousedown event handler in fabric canvas
80 | * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object
81 | * @private
82 | */
83 | _onFabricMouseDown(fEvent) {
84 | const canvas = this.getCanvas();
85 | const pointer = canvas.getPointer(fEvent.e);
86 | const points = [pointer.x, pointer.y, pointer.x, pointer.y];
87 |
88 | this._line = new fabric.Line(points, {
89 | stroke: this._oColor.toRgba(),
90 | strokeWidth: this._width,
91 | evented: false
92 | });
93 |
94 | this._line.set(consts.fObjectOptions.SELECTION_STYLE);
95 |
96 | canvas.add(this._line);
97 |
98 | canvas.on({
99 | 'mouse:move': this._listeners.mousemove,
100 | 'mouse:up': this._listeners.mouseup
101 | });
102 | }
103 |
104 | /**
105 | * Mousemove event handler in fabric canvas
106 | * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object
107 | * @private
108 | */
109 | _onFabricMouseMove(fEvent) {
110 | const canvas = this.getCanvas();
111 | const pointer = canvas.getPointer(fEvent.e);
112 |
113 | this._line.set({
114 | x2: pointer.x,
115 | y2: pointer.y
116 | });
117 |
118 | this._line.setCoords();
119 |
120 | canvas.renderAll();
121 | }
122 |
123 | /**
124 | * Mouseup event handler in fabric canvas
125 | * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object
126 | * @private
127 | */
128 | _onFabricMouseUp() {
129 | const canvas = this.getCanvas();
130 |
131 | this._line = null;
132 |
133 | canvas.off({
134 | 'mouse:move': this._listeners.mousemove,
135 | 'mouse:up': this._listeners.mouseup
136 | });
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/src/modules/mosaic.1.js:
--------------------------------------------------------------------------------
1 | import Base from './base.js';
2 | import consts from '../consts';
3 |
4 | export default class Mosaic extends Base {
5 | constructor(parent) {
6 | super();
7 | this.setParent(parent);
8 |
9 | this.name = consts.moduleNames.MOSAIC;
10 |
11 | this._dimensions = 16;
12 |
13 | this._listeners = {
14 | mousedown: this._onFabricMouseDown.bind(this),
15 | mousemove: this._onFabricMouseMove.bind(this),
16 | mouseup: this._onFabricMouseUp.bind(this)
17 | };
18 | }
19 |
20 | /**
21 | * @param {{dimensions: ?number}} [setting] - Mosaic width
22 | */
23 | start(setting) {
24 | const canvas = this.getCanvas();
25 |
26 | canvas.defaultCursor = 'crosshair';
27 | canvas.selection = false;
28 |
29 | setting = setting || {};
30 | this._dimensions = parseInt(setting.dimensions) || this._dimensions;
31 |
32 | canvas.forEachObject((obj) => {
33 | obj.set({
34 | evented: false
35 | });
36 | });
37 |
38 | canvas.on({
39 | 'mouse:down': this._listeners.mousedown
40 | });
41 | }
42 |
43 | end() {
44 | const canvas = this.getCanvas();
45 |
46 | canvas.defaultCursor = 'default';
47 | canvas.selection = false;
48 |
49 | canvas.forEachObject((obj) => {
50 | obj.set({
51 | evented: true
52 | });
53 | });
54 |
55 | canvas.off('mouse:down', this._listeners.mousedown);
56 | }
57 |
58 | _onFabricMouseDown(fEvent) {
59 | const canvas = this.getCanvas();
60 | const pointer = (this.pointer = canvas.getPointer(fEvent.e));
61 | this._mosaicGroup = new fabric.Group([], {
62 | left: pointer.x,
63 | top: pointer.y,
64 | originX: 'center',
65 | originY: 'center'
66 | });
67 | canvas.add(this._mosaicGroup);
68 | this._mosaicGroup.set('selectable', false);
69 | canvas.renderAll();
70 | canvas.on({
71 | 'mouse:move': this._listeners.mousemove,
72 | 'mouse:up': this._listeners.mouseup
73 | });
74 | }
75 | _onFabricMouseMove(fEvent) {
76 | let ratio = this.getCanvasRatio();
77 | ratio = Math.ceil(ratio);
78 | let dimensions = this._dimensions * ratio;
79 | const canvas = this.getCanvas();
80 | const pointer = canvas.getPointer(fEvent.e);
81 | let imageData = canvas.contextContainer.getImageData(
82 | parseInt(pointer.x),
83 | parseInt(pointer.y),
84 | dimensions,
85 | dimensions
86 | );
87 | // let imageData = canvas.getContext().getImageData(parseInt(pointer.x), parseInt(pointer.y), this._dimensions, this._dimensions);
88 | let rgba = [0, 0, 0, 0];
89 | let length = imageData.data.length / 4;
90 | for (let i = 0; i < length; i++) {
91 | rgba[0] += imageData.data[i * 4];
92 | rgba[1] += imageData.data[i * 4 + 1];
93 | rgba[2] += imageData.data[i * 4 + 2];
94 | rgba[3] += imageData.data[i * 4 + 3];
95 | }
96 | let mosaicRect = new fabric.Rect({
97 | fill: `rgb(${parseInt(rgba[0] / length)},${parseInt(rgba[1] / length)},${parseInt(
98 | rgba[2] / length
99 | )})`,
100 | height: dimensions,
101 | width: dimensions,
102 | left: pointer.x,
103 | top: pointer.y
104 | });
105 | //this._mosaicGroup.addWithUpdate(mosaicRect);
106 | canvas.add(mosaicRect);
107 | canvas.renderAll();
108 | }
109 |
110 | _onFabricMouseUp() {
111 | const canvas = this.getCanvas();
112 | this._mosaicGroup = null;
113 | this.pointer = null;
114 | canvas.off({
115 | 'mouse:move': this._listeners.mousemove,
116 | 'mouse:up': this._listeners.mouseup
117 | });
118 | }
119 | /**
120 | * Get ratio value of canvas
121 | * @returns {number} Ratio value
122 | */
123 | getCanvasRatio() {
124 | const canvasElement = this.getCanvasElement();
125 | const cssWidth = parseInt(canvasElement.style.width, 10);
126 | const originWidth = canvasElement.width;
127 | const ratio = originWidth / cssWidth;
128 | return ratio;
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/src/modules/mosaic.2.js:
--------------------------------------------------------------------------------
1 | import Base from './base.js';
2 | import consts from '../consts';
3 | import MosaicShape from '../shape/mosaic.js';
4 |
5 | export default class Mosaic extends Base {
6 | constructor(parent) {
7 | super();
8 | this.setParent(parent);
9 |
10 | this.name = consts.moduleNames.MOSAIC;
11 |
12 | this._dimensions = 20;
13 |
14 | this._listeners = {
15 | mousedown: this._onFabricMouseDown.bind(this),
16 | mousemove: this._onFabricMouseMove.bind(this),
17 | mouseup: this._onFabricMouseUp.bind(this)
18 | };
19 | }
20 |
21 | /**
22 | * @param {{dimensions: ?number}} [setting] - Mosaic width
23 | */
24 | start(setting) {
25 | const canvas = this.getCanvas();
26 |
27 | canvas.defaultCursor = 'crosshair';
28 | canvas.selection = false;
29 |
30 | setting = setting || {};
31 | this._dimensions = parseInt(setting.dimensions) || this._dimensions;
32 |
33 | canvas.forEachObject((obj) => {
34 | obj.set({
35 | evented: false
36 | });
37 | });
38 |
39 | canvas.on({
40 | 'mouse:down': this._listeners.mousedown
41 | });
42 | }
43 |
44 | end() {
45 | const canvas = this.getCanvas();
46 |
47 | canvas.defaultCursor = 'default';
48 | canvas.selection = false;
49 |
50 | canvas.forEachObject((obj) => {
51 | obj.set({
52 | evented: true
53 | });
54 | });
55 |
56 | canvas.off('mouse:down', this._listeners.mousedown);
57 | }
58 |
59 | _onFabricMouseDown(fEvent) {
60 | const canvas = this.getCanvas();
61 | const pointer = (this.pointer = canvas.getPointer(fEvent.e));
62 | this._mosaicShape = new MosaicShape({
63 | mosaicRects: [],
64 | selectable: false,
65 | left: pointer.x,
66 | top: pointer.y,
67 | originX: 'center',
68 | originY: 'center'
69 | });
70 | canvas.add(this._mosaicShape);
71 | canvas.renderAll();
72 | canvas.on({
73 | 'mouse:move': this._listeners.mousemove,
74 | 'mouse:up': this._listeners.mouseup
75 | });
76 | }
77 | _onFabricMouseMove(fEvent) {
78 | let ratio = this.getCanvasRatio();
79 | ratio = Math.ceil(ratio);
80 | let dimensions = this._dimensions * ratio;
81 | const canvas = this.getCanvas();
82 | const pointer = canvas.getPointer(fEvent.e);
83 | let imageData = canvas.contextContainer.getImageData(
84 | parseInt(pointer.x),
85 | parseInt(pointer.y),
86 | dimensions,
87 | dimensions
88 | );
89 | // let imageData = canvas.getContext().getImageData(parseInt(pointer.x), parseInt(pointer.y), this._dimensions, this._dimensions);
90 | let rgba = [0, 0, 0, 0];
91 | let length = imageData.data.length / 4;
92 | for (let i = 0; i < length; i++) {
93 | rgba[0] += imageData.data[i * 4];
94 | rgba[1] += imageData.data[i * 4 + 1];
95 | rgba[2] += imageData.data[i * 4 + 2];
96 | rgba[3] += imageData.data[i * 4 + 3];
97 | }
98 | this._mosaicShape.addMosicRectWithUpdate({
99 | left: pointer.x,
100 | top: pointer.y,
101 | fill: `rgb(${parseInt(rgba[0] / length)},${parseInt(rgba[1] / length)},${parseInt(
102 | rgba[2] / length
103 | )})`,
104 | dimensions: dimensions
105 | });
106 | canvas.renderAll();
107 | }
108 |
109 | _onFabricMouseUp() {
110 | const canvas = this.getCanvas();
111 | this._mosaicShape = null;
112 | canvas.off({
113 | 'mouse:move': this._listeners.mousemove,
114 | 'mouse:up': this._listeners.mouseup
115 | });
116 | }
117 |
118 | /**
119 | * Get ratio value of canvas
120 | * @returns {number} Ratio value
121 | */
122 | getCanvasRatio() {
123 | const canvasElement = this.getCanvasElement();
124 | const cssWidth = parseInt(canvasElement.style.width, 10);
125 | const originWidth = canvasElement.width;
126 | const ratio = originWidth / cssWidth;
127 | return ratio;
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/src/modules/mosaic.js:
--------------------------------------------------------------------------------
1 | import Base from './base.js';
2 | import consts from '../consts';
3 | import MosaicShape from '../shape/mosaic.js';
4 |
5 | export default class Mosaic extends Base {
6 | constructor(parent) {
7 | super();
8 | this.setParent(parent);
9 |
10 | this.name = consts.moduleNames.MOSAIC;
11 |
12 | this._dimensions = 8;
13 |
14 | this._listeners = {
15 | mousedown: this._onFabricMouseDown.bind(this),
16 | mousemove: this._onFabricMouseMove.bind(this),
17 | mouseup: this._onFabricMouseUp.bind(this)
18 | };
19 | }
20 |
21 | /**
22 | * @param {{dimensions: ?number}} [setting] - Mosaic width
23 | */
24 | start(setting) {
25 | const canvas = this.getCanvas();
26 |
27 | canvas.defaultCursor = 'crosshair';
28 | canvas.selection = false;
29 |
30 | setting = setting || {};
31 | this._dimensions = parseInt(setting.dimensions, 10) || this._dimensions;
32 |
33 | canvas.forEachObject((obj) => {
34 | obj.set({
35 | evented: false
36 | });
37 | });
38 |
39 | canvas.on({
40 | 'mouse:down': this._listeners.mousedown
41 | });
42 | }
43 |
44 | end() {
45 | const canvas = this.getCanvas();
46 |
47 | canvas.defaultCursor = 'default';
48 | canvas.selection = false;
49 |
50 | canvas.forEachObject((obj) => {
51 | obj.set({
52 | evented: true
53 | });
54 | });
55 | canvas.off('mouse:down', this._listeners.mousedown);
56 | }
57 |
58 | _onFabricMouseDown() {
59 | const canvas = this.getCanvas();
60 | let lowerCanvas = canvas.getElement();
61 | let mosaicLayer = (this.mosaicLayer = lowerCanvas.cloneNode(true));
62 | mosaicLayer.classList.remove('lower-canvas');
63 | mosaicLayer.classList.add('mosaic-canvas');
64 | this.mosaicArr = [];
65 | lowerCanvas.insertAdjacentElement('afterend', mosaicLayer);
66 | canvas.on({
67 | 'mouse:move': this._listeners.mousemove,
68 | 'mouse:up': this._listeners.mouseup
69 | });
70 | }
71 |
72 | _onFabricMouseMove(fEvent) {
73 | let ratio = this.getCanvasRatio();
74 | ratio = Math.ceil(ratio);
75 | let dimensions = this._dimensions * ratio;
76 | const canvas = this.getCanvas();
77 | const pointer = canvas.getPointer(fEvent.e);
78 | let imageData = canvas.contextContainer.getImageData(
79 | parseInt(pointer.x, 10),
80 | parseInt(pointer.y, 10),
81 | dimensions,
82 | dimensions
83 | );
84 | // let imageData = canvas.getContext().getImageData(parseInt(pointer.x), parseInt(pointer.y), this._dimensions, this._dimensions);
85 | let rgba = [0, 0, 0, 0];
86 | let length = imageData.data.length / 4;
87 | for (let i = 0; i < length; i++) {
88 | rgba[0] += imageData.data[i * 4];
89 | rgba[1] += imageData.data[i * 4 + 1];
90 | rgba[2] += imageData.data[i * 4 + 2];
91 | rgba[3] += imageData.data[i * 4 + 3];
92 | }
93 | let mosaicRect = {
94 | left: pointer.x,
95 | top: pointer.y,
96 | fill: `rgb(${Number.parseInt(rgba[0] / length, 10)},${Number.parseInt(
97 | rgba[1] / length,
98 | 10
99 | )},${Number.parseInt(rgba[2] / length, 10)})`,
100 | dimensions
101 | };
102 | this.mosaicArr.push(mosaicRect);
103 | let ctx = this.mosaicLayer.getContext('2d');
104 | ctx.fillStyle = mosaicRect.fill;
105 | ctx.fillRect(mosaicRect.left, mosaicRect.top, mosaicRect.dimensions, mosaicRect.dimensions);
106 | }
107 |
108 | _onFabricMouseUp() {
109 | const canvas = this.getCanvas();
110 | if (this.mosaicArr && this.mosaicArr.length > 0) {
111 | let __mosaicShape = new MosaicShape({
112 | mosaicRects: this.mosaicArr,
113 | selectable: false,
114 | left: 0,
115 | top: 0,
116 | originX: 'center',
117 | originY: 'center'
118 | });
119 | canvas.add(__mosaicShape);
120 | canvas.renderAll();
121 | }
122 | if (this.mosaicLayer) {
123 | this.mosaicLayer.parentNode.removeChild(this.mosaicLayer);
124 | }
125 | this.mosaicArr = [];
126 | canvas.off({
127 | 'mouse:move': this._listeners.mousemove,
128 | 'mouse:up': this._listeners.mouseup
129 | });
130 | }
131 |
132 | /**
133 | * Get ratio value of canvas
134 | * @returns {number} Ratio value
135 | */
136 | getCanvasRatio() {
137 | const canvasElement = this.getCanvasElement();
138 | const cssWidth = parseInt(canvasElement.style.width, 10);
139 | const originWidth = canvasElement.width;
140 | const ratio = originWidth / cssWidth;
141 | return ratio;
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/src/modules/pan.js:
--------------------------------------------------------------------------------
1 | import Base from './base';
2 | import consts from '../consts';
3 |
4 | export default class Pan extends Base {
5 | constructor(parent) {
6 | super();
7 | this.setParent(parent);
8 | this.name = consts.moduleNames.PAN;
9 | this._listeners = {
10 | mousedown: this._onFabricMouseDown.bind(this),
11 | mousemove: this._onFabricMouseMove.bind(this),
12 | mouseup: this._onFabricMouseUp.bind(this)
13 | };
14 | }
15 |
16 | start() {
17 | const canvas = this.getCanvas();
18 |
19 | canvas.defaultCursor = 'move';
20 | canvas.selection = false;
21 |
22 | canvas.forEachObject((obj) => {
23 | obj.set({
24 | evented: false
25 | });
26 | });
27 |
28 | canvas.on({
29 | 'mouse:down': this._listeners.mousedown
30 | });
31 | }
32 |
33 | end() {
34 | const canvas = this.getCanvas();
35 |
36 | canvas.defaultCursor = 'default';
37 | canvas.selection = false;
38 |
39 | canvas.forEachObject((obj) => {
40 | obj.set({
41 | evented: true
42 | });
43 | });
44 |
45 | canvas.off('mouse:down', this._listeners.mousedown);
46 | }
47 |
48 | /**
49 | * Mousedown event handler in fabric canvas
50 | * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object
51 | * @private
52 | */
53 | _onFabricMouseDown(fEvent) {
54 | const canvas = this.getCanvas();
55 | this.pointer = canvas.getPointer(fEvent.e);
56 | this.$lower = canvas.lowerCanvasEl;
57 | this.$upper = canvas.upperCanvasEl;
58 | this.$wrapper = canvas.wrapperEl;
59 | this.deltaX = parseInt(window.getComputedStyle(this.$lower).left, 10);
60 | this.deltaY = parseInt(window.getComputedStyle(this.$lower).top, 10);
61 | this.deltaWidth =
62 | this.$upper.getBoundingClientRect().width - this.$wrapper.getBoundingClientRect().width;
63 | this.deltaHeight =
64 | this.$upper.getBoundingClientRect().height -
65 | this.$wrapper.getBoundingClientRect().height;
66 | canvas.on({
67 | 'mouse:move': this._listeners.mousemove,
68 | 'mouse:up': this._listeners.mouseup
69 | });
70 | }
71 |
72 | /**
73 | * Mousemove event handler in fabric canvas
74 | * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object
75 | * @private
76 | */
77 | _onFabricMouseMove(fEvent) {
78 | // go out of use because of transform opver
79 | // var delta = new fabric.Point(fEvent.e.movementX, fEvent.e.movementY);
80 | // canvas.relativePan(delta);
81 |
82 | //safari9 not work for movement event
83 | // let deltaX = this.deltaX + fEvent.e.movementX;
84 | // let deltaY = this.deltaY + fEvent.e.movementY;
85 | const canvas = this.getCanvas();
86 | const movePointer = canvas.getPointer(fEvent.e);
87 |
88 | let deltaX = this.deltaX + movePointer.x - this.pointer.x;
89 | let deltaY = this.deltaY + movePointer.y - this.pointer.y;
90 |
91 | if (this.deltaWidth > Math.abs(deltaX) && deltaX < 0) {
92 | this.$lower.style.left = deltaX;
93 | this.$upper.style.left = deltaX;
94 | this.deltaX = deltaX;
95 | }
96 | if (this.deltaHeight > Math.abs(deltaY) && deltaY < 0) {
97 | this.$lower.style.top = deltaY;
98 | this.$upper.style.top = deltaY;
99 | this.deltaY = deltaY;
100 | }
101 | }
102 |
103 | /**
104 | * Mouseup event handler in fabric canvas
105 | * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object
106 | * @private
107 | */
108 | _onFabricMouseUp() {
109 | const canvas = this.getCanvas();
110 | this.pointer = null;
111 | this.$lower = null;
112 | this.$upper = null;
113 | canvas.off({
114 | 'mouse:move': this._listeners.mousemove,
115 | 'mouse:up': this._listeners.mouseup
116 | });
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/modules/rotation.js:
--------------------------------------------------------------------------------
1 | import { fabric } from 'fabric';
2 | import Base from './base';
3 | import consts from '../consts';
4 |
5 | const { rejectMessages } = consts;
6 |
7 | export default class Rotation extends Base {
8 | constructor(parent) {
9 | super();
10 | this.setParent(parent);
11 | this.name = consts.moduleNames.ROTATION;
12 | }
13 |
14 | getCurrentAngle() {
15 | return this.getCanvasImage().angle;
16 | }
17 |
18 | /**
19 | * Set angle of the image
20 | *
21 | * Do not call "this.setImageProperties" for setting angle directly.
22 | * Before setting angle, The originX,Y of image should be set to center.
23 | * See "http://fabricjs.com/docs/fabric.Object.html#setAngle"
24 | *
25 | * @param {number} angle - Angle value
26 | * @returns {jQuery.Deferred}
27 | */
28 | setAngle(angle) {
29 | const oldAngle = this.getCurrentAngle() % 360; // The angle is lower than 2*PI(===360 degrees)
30 |
31 | angle %= 360;
32 | if (angle === oldAngle) {
33 | return Promise.reject(rejectMessages.rotation);
34 | }
35 | const canvasImage = this.getCanvasImage();
36 | const oldImageCenter = canvasImage.getCenterPoint();
37 | canvasImage.setAngle(angle).setCoords();
38 | this.adjustCanvasDimension();
39 | const newImageCenter = canvasImage.getCenterPoint();
40 | this._rotateForEachObject(oldImageCenter, newImageCenter, angle - oldAngle);
41 |
42 | return Promise.resolve(angle);
43 | }
44 |
45 | /**
46 | * Rotate for each object
47 | * @param {fabric.Point} oldImageCenter - Image center point before rotation
48 | * @param {fabric.Point} newImageCenter - Image center point after rotation
49 | * @param {number} angleDiff - Image angle difference after rotation
50 | * @private
51 | */
52 | _rotateForEachObject(oldImageCenter, newImageCenter, angleDiff) {
53 | const canvas = this.getCanvas();
54 | const centerDiff = {
55 | x: oldImageCenter.x - newImageCenter.x,
56 | y: oldImageCenter.y - newImageCenter.y
57 | };
58 |
59 | canvas.forEachObject((obj) => {
60 | const objCenter = obj.getCenterPoint();
61 | const radian = fabric.util.degreesToRadians(angleDiff);
62 | const newObjCenter = fabric.util.rotatePoint(objCenter, oldImageCenter, radian);
63 |
64 | obj.set({
65 | left: newObjCenter.x - centerDiff.x,
66 | top: newObjCenter.y - centerDiff.y,
67 | angle: (obj.angle + angleDiff) % 360
68 | });
69 | obj.setCoords();
70 | });
71 | canvas.renderAll();
72 | }
73 |
74 | /**
75 | * Rotate the image
76 | * @param {number} additionalAngle - Additional angle
77 | * @returns {jQuery.Deferred}
78 | */
79 | rotate(additionalAngle) {
80 | const current = this.getCurrentAngle();
81 |
82 | return this.setAngle(current + additionalAngle);
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/modules/shape.js:
--------------------------------------------------------------------------------
1 | import { fabric } from 'fabric';
2 |
3 | import Base from './base';
4 | import consts from '../consts';
5 | import util from '../lib/util';
6 |
7 | import resizeHelper from '../lib/shape-resize-helper.js';
8 |
9 | const { inArray, extend } = util;
10 |
11 | const KEY_CODES = consts.keyCodes;
12 | const DEFAULT_TYPE = 'rect';
13 | const DEFAULT_OPTIONS = {
14 | strokeWidth: 1,
15 | stroke: '#000000',
16 | fill: '#ffffff',
17 | width: 1,
18 | height: 1,
19 | rx: 0,
20 | ry: 0,
21 | lockSkewingX: true,
22 | lockSkewingY: true,
23 | lockUniScaling: false,
24 | bringForward: true,
25 | isRegular: false
26 | };
27 |
28 | const shapeType = ['rect', 'circle', 'triangle'];
29 |
30 | export default class Shape extends Base {
31 | constructor(parent) {
32 | super();
33 | this.setParent(parent);
34 | this.name = consts.moduleNames.SHAPE;
35 | this._shapeObj = null;
36 |
37 | this._type = DEFAULT_TYPE;
38 |
39 | /**
40 | * Options to draw the shape
41 | */
42 | this._options = DEFAULT_OPTIONS;
43 |
44 | /**
45 | * Whether the shape object is selected or not
46 | */
47 | this._isSelected = false;
48 |
49 | /**
50 | * Pointer for drawing shape (x, y)
51 | */
52 | this._startPoint = {};
53 |
54 | /**
55 | * Using shortcut on drawing shape
56 | */
57 | this._withShiftKey = false;
58 |
59 | this._handlers = {
60 | mousedown: this._onFabricMouseDown.bind(this),
61 | mousemove: this._onFabricMouseMove.bind(this),
62 | mouseup: this._onFabricMouseUp.bind(this),
63 | keydown: this._onKeyDown.bind(this),
64 | keyup: this._onKeyUp.bind(this)
65 | };
66 | }
67 |
68 | /**
69 | * Start to draw the shape on canvas
70 | */
71 | startDrawingMode() {
72 | const canvas = this.getCanvas();
73 |
74 | this._isSelected = false;
75 |
76 | canvas.defaultCursor = 'crosshair';
77 | canvas.selection = false;
78 | canvas.uniScaleTransform = true;
79 | canvas.on({
80 | 'mouse:down': this._handlers.mousedown
81 | });
82 |
83 | fabric.util.addListener(document, 'keydown', this._handlers.keydown);
84 | fabric.util.addListener(document, 'keyup', this._handlers.keyup);
85 | }
86 |
87 | /**
88 | * End to draw the shape on canvas
89 | */
90 | endDrawingMode() {
91 | const canvas = this.getCanvas();
92 |
93 | this._isSelected = false;
94 |
95 | canvas.defaultCursor = 'default';
96 | canvas.selection = false;
97 | canvas.uniScaleTransform = false;
98 | canvas.off({
99 | 'mouse:down': this._handlers.mousedown
100 | });
101 |
102 | fabric.util.removeListener(document, 'keydown', this._handlers.keydown);
103 | fabric.util.removeListener(document, 'keyup', this._handlers.keyup);
104 | }
105 |
106 | /**
107 | * Set states of the current drawing shape
108 | * @param {string} type - Shape type (ex: 'rect', 'circle')
109 | * @param {object} [options] - Shape options
110 | * @param {string} [options.fill] - Shape foreground color (ex: '#fff', 'transparent')
111 | * @param {string} [options.stoke] - Shape outline color
112 | * @param {number} [options.strokeWidth] - Shape outline width
113 | * @param {number} [options.width] - Width value (When type option is 'rect', this options can use)
114 | * @param {number} [options.height] - Height value (When type option is 'rect', this options can use)
115 | * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use)
116 | * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use)
117 | */
118 | setStates(type, options) {
119 | this._type = type;
120 |
121 | if (options) {
122 | this._options = Object.assign(this._options, options);
123 | }
124 | }
125 |
126 | /**
127 | * Add the shape
128 | * @param {string} type - Shape type (ex: 'rect', 'circle')
129 | * @param {object} options - Shape options
130 | * @param {string} [options.fill] - Shape foreground color (ex: '#fff', 'transparent')
131 | * @param {string} [options.stroke] - Shape outline color
132 | * @param {number} [options.strokeWidth] - Shape outline width
133 | * @param {number} [options.width] - Width value (When type option is 'rect', this options can use)
134 | * @param {number} [options.height] - Height value (When type option is 'rect', this options can use)
135 | * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use)
136 | * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use)
137 | * @param {number} [options.isRegular] - Whether scaling shape has 1:1 ratio or not
138 | */
139 | add(type, options) {
140 | const canvas = this.getCanvas();
141 | options = this._createOptions(options);
142 | const shapeObj = this._createInstance(type, options);
143 |
144 | this._bindEventOnShape(shapeObj);
145 |
146 | canvas.add(shapeObj);
147 | }
148 |
149 | /**
150 | * Change the shape
151 | * @param {fabric.Object} shapeObj - Selected shape object on canvas
152 | * @param {object} options - Shape options
153 | * @param {string} [options.fill] - Shape foreground color (ex: '#fff', 'transparent')
154 | * @param {string} [options.stroke] - Shape outline color
155 | * @param {number} [options.strokeWidth] - Shape outline width
156 | * @param {number} [options.width] - Width value (When type option is 'rect', this options can use)
157 | * @param {number} [options.height] - Height value (When type option is 'rect', this options can use)
158 | * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use)
159 | * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use)
160 | * @param {number} [options.isRegular] - Whether scaling shape has 1:1 ratio or not
161 | */
162 | change(shapeObj, options) {
163 | if (inArray(shapeObj.get('type'), shapeType) < 0) {
164 | return;
165 | }
166 |
167 | shapeObj.set(options);
168 | this.getCanvas().renderAll();
169 | }
170 |
171 | /**
172 | * Create the instance of shape
173 | * @param {string} type - Shape type
174 | * @param {object} options - Options to creat the shape
175 | * @returns {fabric.Object} Shape instance
176 | */
177 | _createInstance(type, options) {
178 | let instance;
179 |
180 | switch (type) {
181 | case 'rect':
182 | instance = new fabric.Rect(options);
183 | break;
184 | case 'circle':
185 | instance = new fabric.Ellipse(
186 | extend(
187 | {
188 | type: 'circle'
189 | },
190 | options
191 | )
192 | );
193 | break;
194 | case 'triangle':
195 | instance = new fabric.Triangle(options);
196 | break;
197 | default:
198 | instance = {};
199 | }
200 |
201 | return instance;
202 | }
203 |
204 | /**
205 | * Get the options to create the shape
206 | * @param {object} options - Options to creat the shape
207 | * @returns {object} Shape options
208 | */
209 | _createOptions(options) {
210 | const selectionStyles = consts.fObjectOptions.SELECTION_STYLE;
211 |
212 | options = Object.assign({}, DEFAULT_OPTIONS, selectionStyles, options);
213 |
214 | if (options.isRegular) {
215 | options.lockUniScaling = true;
216 | }
217 |
218 | return options;
219 | }
220 |
221 | /**
222 | * Bind fabric events on the creating shape object
223 | * @param {fabric.Object} shapeObj - Shape object
224 | */
225 | _bindEventOnShape(shapeObj) {
226 | const self = this;
227 | const canvas = this.getCanvas();
228 |
229 | shapeObj.on({
230 | added() {
231 | self._shapeObj = this;
232 | resizeHelper.setOrigins(self._shapeObj);
233 | },
234 | selected() {
235 | self._isSelected = true;
236 | self._shapeObj = this;
237 | canvas.uniScaleTransform = true;
238 | canvas.defaultCursor = 'default';
239 | resizeHelper.setOrigins(self._shapeObj);
240 | },
241 | deselected() {
242 | self._isSelected = false;
243 | self._shapeObj = null;
244 | canvas.defaultCursor = 'crosshair';
245 | canvas.uniScaleTransform = false;
246 | },
247 | modified() {
248 | const currentObj = self._shapeObj;
249 |
250 | resizeHelper.adjustOriginToCenter(currentObj);
251 | resizeHelper.setOrigins(currentObj);
252 | },
253 | scaling(fEvent) {
254 | const pointer = canvas.getPointer(fEvent.e);
255 | const currentObj = self._shapeObj;
256 |
257 | canvas.setCursor('crosshair');
258 | resizeHelper.resize(currentObj, pointer, true);
259 | }
260 | });
261 | }
262 |
263 | /**
264 | * MouseDown event handler on canvas
265 | * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object
266 | */
267 | _onFabricMouseDown(fEvent) {
268 | if (!this._isSelected && !this._shapeObj) {
269 | const canvas = this.getCanvas();
270 | this._startPoint = canvas.getPointer(fEvent.e);
271 |
272 | canvas.on({
273 | 'mouse:move': this._handlers.mousemove,
274 | 'mouse:up': this._handlers.mouseup
275 | });
276 | }
277 | }
278 |
279 | /**
280 | * MouseDown event handler on canvas
281 | * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object
282 | */
283 | _onFabricMouseMove(fEvent) {
284 | const canvas = this.getCanvas();
285 | const pointer = canvas.getPointer(fEvent.e);
286 | const startPointX = this._startPoint.x;
287 | const startPointY = this._startPoint.y;
288 | const width = startPointX - pointer.x;
289 | const height = startPointY - pointer.y;
290 | const shape = this._shapeObj;
291 |
292 | if (!shape) {
293 | this.add(this._type, {
294 | left: startPointX,
295 | top: startPointY,
296 | width,
297 | height
298 | });
299 | } else {
300 | this._shapeObj.set({
301 | isRegular: this._withShiftKey
302 | });
303 | resizeHelper.resize(shape, pointer);
304 | canvas.renderAll();
305 | }
306 | }
307 |
308 | /**
309 | * MouseUp event handler on canvas
310 | */
311 | _onFabricMouseUp() {
312 | const canvas = this.getCanvas();
313 | const shape = this._shapeObj;
314 |
315 | if (shape) {
316 | resizeHelper.adjustOriginToCenter(shape);
317 | }
318 |
319 | this._shapeObj = null;
320 |
321 | canvas.off({
322 | 'mouse:move': this._handlers.mousemove,
323 | 'mouse:up': this._handlers.mouseup
324 | });
325 | }
326 |
327 | /**
328 | * Keydown event handler on document
329 | * @param {KeyboardEvent} e - Event object
330 | */
331 | _onKeyDown(e) {
332 | if (e.keyCode === KEY_CODES.SHIFT) {
333 | this._withShiftKey = true;
334 |
335 | if (this._shapeObj) {
336 | this._shapeObj.isRegular = true;
337 | }
338 | }
339 | }
340 |
341 | /**
342 | * Keyup event handler on document
343 | * @param {KeyboardEvent} e - Event object
344 | */
345 | _onKeyUp(e) {
346 | if (e.keyCode === KEY_CODES.SHIFT) {
347 | this._withShiftKey = false;
348 |
349 | if (this._shapeObj) {
350 | this._shapeObj.isRegular = false;
351 | }
352 | }
353 | }
354 | }
355 |
--------------------------------------------------------------------------------
/src/shape/arrow.js:
--------------------------------------------------------------------------------
1 | import { fabric } from 'fabric';
2 |
3 | const Arrow = fabric.util.createClass(fabric.Path, {
4 | /**
5 | * Constructor
6 | * @param {Object} options Options object
7 | * @override
8 | */
9 | initialize(options) {
10 | options.type = 'arrow';
11 | this.callSuper('initialize', options);
12 | this.on({
13 | moving: this._onMoving,
14 | scaling: this._onScaling
15 | });
16 | },
17 | objectCaching: false,
18 |
19 | /**
20 | * Render Crop-zone
21 | * @param {CanvasRenderingContext2D} ctx - Context
22 | * @private
23 | * @override
24 | */
25 | _render(ctx) {
26 | const cropzoneDashLineWidth = 7;
27 | const cropzoneDashLineOffset = 7;
28 | this.callSuper('_render', ctx);
29 |
30 | // Calc original scale
31 | const originalFlipX = this.flipX ? -1 : 1;
32 | const originalFlipY = this.flipY ? -1 : 1;
33 | const originalScaleX = originalFlipX / this.scaleX;
34 | const originalScaleY = originalFlipY / this.scaleY;
35 |
36 | // Set original scale
37 | ctx.scale(originalScaleX, originalScaleY);
38 |
39 | // Render outer rect
40 | this._fillOuterRect(ctx, 'rgba(0, 0, 0, 0.55)');
41 |
42 | // Black dash line
43 | this._strokeBorder(ctx, 'rgb(0, 0, 0)', cropzoneDashLineWidth);
44 |
45 | // White dash line
46 | this._strokeBorder(
47 | ctx,
48 | 'rgb(255, 255, 255)',
49 | cropzoneDashLineWidth,
50 | cropzoneDashLineOffset
51 | );
52 |
53 | // Reset scale
54 | ctx.scale(1 / originalScaleX, 1 / originalScaleY);
55 | },
56 |
57 | drawControls(ctx) {
58 | if (!this.hasControls) {
59 | return this;
60 | }
61 | var wh = this._calculateCurrentDimensions(),
62 | width = wh.x,
63 | height = wh.y,
64 | scaleOffset = this.cornerSize,
65 | left = -(width + scaleOffset) / 2,
66 | top = -(height + scaleOffset) / 2,
67 | methodName = this.transparentCorners ? 'stroke' : 'fill';
68 | ctx.save();
69 | ctx.strokeStyle = ctx.fillStyle = this.cornerColor;
70 | if (!this.transparentCorners) {
71 | ctx.strokeStyle = this.cornerStrokeColor;
72 | }
73 | this._setLineDash(ctx, this.cornerDashArray, null);
74 | // top-left
75 | this._drawControl('tl', ctx, methodName, left, top);
76 | // top-right
77 | this._drawControl('tr', ctx, methodName, left + width, top);
78 | // bottom-left
79 | this._drawControl('bl', ctx, methodName, left, top + height);
80 | // bottom-right
81 | this._drawControl('br', ctx, methodName, left + width, top + height);
82 | if (!this.get('lockUniScaling')) {
83 | // middle-top
84 | this._drawControl('mt', ctx, methodName, left + width / 2, top);
85 | // middle-bottom
86 | this._drawControl('mb', ctx, methodName, left + width / 2, top + height);
87 | // middle-right
88 | this._drawControl('mr', ctx, methodName, left + width, top + height / 2);
89 | // middle-left
90 | this._drawControl('ml', ctx, methodName, left, top + height / 2);
91 | }
92 | // middle-top-rotate
93 | if (this.hasRotatingPoint) {
94 | this._drawControl(
95 | 'mtr',
96 | ctx,
97 | methodName,
98 | left + width / 2,
99 | top - this.rotatingPointOffset
100 | );
101 | }
102 | ctx.restore();
103 | }
104 | });
105 | export default Arrow;
106 |
--------------------------------------------------------------------------------
/src/shape/mosaic.js:
--------------------------------------------------------------------------------
1 | import { fabric } from 'fabric';
2 | // var cacheProperties = fabric.Object.prototype.cacheProperties.concat();
3 | // cacheProperties.push('_mosaicRects');
4 | const Mosaic = fabric.util.createClass(fabric.Object, {
5 | type: 'mosaic',
6 | // statefullCache:true,
7 | // cacheProperties:cacheProperties,
8 | // objectCaching: true,
9 | objectCaching: false,
10 |
11 | initialize: function(options) {
12 | options || (options = {});
13 |
14 | this._minPoint = { left: 0, top: 0 };
15 |
16 | this._maxPoint = { left: 0, top: 0 };
17 |
18 | this._mosaicRects = [];
19 |
20 | this.callSuper('initialize', options);
21 |
22 | this.addMosicRectWithUpdate(options.mosaicRects || []);
23 | },
24 |
25 | toObject: function() {
26 | return fabric.util.object.extend(this.callSuper('toObject'), {
27 | _mosaicRects: this.get('_mosaicRects')
28 | });
29 | },
30 |
31 | _render: function(ctx) {
32 | this._mosaicRects.forEach((item, i) => {
33 | ctx.fillStyle = item.fill;
34 | ctx.fillRect(item.left, item.top, item.dimensions, item.dimensions);
35 | });
36 | },
37 |
38 | addMosaicRect: function(objects) {
39 | objects.forEach((object) => {
40 | if (object.left < this._minPoint.left || object.top < this._minPoint.top) {
41 | this._minPoint = {
42 | left: object.left,
43 | top: object.top
44 | };
45 | }
46 | if (object.left > this._maxPoint.left || object.top > this._maxPoint.top) {
47 | this._maxPoint = {
48 | left: object.left,
49 | top: object.top
50 | };
51 | }
52 | this._mosaicRects.push({
53 | left: object.left,
54 | top: object.top,
55 | dimensions: object.dimensions,
56 | fill: object.fill
57 | });
58 | });
59 | },
60 |
61 | addMosicRectWithUpdate: function(objects) {
62 | this.addMosaicRect(objects);
63 | this.set({
64 | width: this._maxPoint.left - this._minPoint.left,
65 | height: this._maxPoint.top - this._minPoint.top,
66 | left: this._minPoint.left,
67 | top: this._minPoint.top,
68 | selectable: false
69 | });
70 | }
71 | });
72 | export default Mosaic;
73 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const { resolve } = require('path');
2 | const webpack = require('webpack');
3 | const path = require('path');
4 |
5 | module.exports = {
6 | entry: {
7 | index: ['./demo/index.js']
8 | },
9 | output: {
10 | filename: '[name].js',
11 | sourceMapFilename: '[file].map',
12 | path: resolve(__dirname, 'public'),
13 | publicPath: '/public'
14 | },
15 | devtool: 'cheap-module-eval-source-map',
16 |
17 | devServer: {
18 | contentBase: [path.join(__dirname, 'html'), path.join(__dirname, 'public')],
19 | compress: true,
20 | port: parseInt(process.env.PORT, 10) || 9876,
21 | host: '0.0.0.0',
22 | hot: true,
23 | inline: true,
24 | publicPath: '/dist/',
25 | historyApiFallback: {
26 | rewrites: [
27 | {
28 | from: /^\/$/,
29 | to: '/html/index.html'
30 | }
31 | ]
32 | },
33 | watchContentBase: true
34 | },
35 | performance: {
36 | hints: false
37 | },
38 | module: {
39 | rules: [
40 | {
41 | test: /\.js$/,
42 | use: [
43 | {
44 | loader: 'babel-loader'
45 | }
46 | ],
47 | exclude: [/node_modules/]
48 | },
49 | {
50 | test: /\.css$/,
51 | use: ['style-loader', 'css-loader', 'postcss-loader']
52 | },
53 | {
54 | test: /\.scss$/,
55 | use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader']
56 | },
57 | {
58 | test: /\.(png|jpg|jpeg|gif|woff|svg|eot|ttf|woff2)$/i,
59 | use: ['url-loader']
60 | }
61 | ]
62 | },
63 | externals: {
64 | jquery: 'jQuery',
65 | lodash: '_'
66 | },
67 | plugins: [new webpack.HotModuleReplacementPlugin()]
68 | };
69 |
--------------------------------------------------------------------------------
/website/.fatherrc.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | esm: 'rollup',
3 | cjs: 'rollup',
4 | };
5 |
--------------------------------------------------------------------------------
/website/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /npm-debug.log*
6 | /yarn-error.log
7 | /yarn.lock
8 | /package-lock.json
9 |
10 | # production
11 | /dist
12 | /docs-dist
13 |
14 | # misc
15 | .DS_Store
16 |
17 | # umi
18 | .umi
19 | .umi-production
20 | .umi-test
21 | .env.local
22 |
23 | # ide
24 | /.vscode
25 | /.idea
26 |
--------------------------------------------------------------------------------
/website/.npmrc:
--------------------------------------------------------------------------------
1 | registry=https://registry.npm.taobao.org/
2 | disturl=https://npm.taobao.org/dist
3 |
--------------------------------------------------------------------------------
/website/.umirc.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'dumi';
2 |
3 | export default defineConfig({
4 | title: 'fabric-photo',
5 | favicon:
6 | 'https://user-images.githubusercontent.com/9554297/83762004-a0761b00-a6a9-11ea-83b4-9c8ff721d4b8.png',
7 | logo:
8 | 'https://user-images.githubusercontent.com/9554297/83762004-a0761b00-a6a9-11ea-83b4-9c8ff721d4b8.png',
9 | outputPath: 'dist',
10 | hash: process.env.NODE_ENV !== 'development',
11 | base: '/fabric-photo',
12 | publicPath:
13 | process.env.NODE_ENV !== 'development' ? 'https://ximing.github.io/fabric-photo/' : '/',
14 | exportStatic: {}
15 |
16 | // more config: https://d.umijs.org/config
17 | });
18 |
--------------------------------------------------------------------------------
/website/README.md:
--------------------------------------------------------------------------------
1 | # website
2 |
3 | ## Getting Started
4 |
5 | Install dependencies,
6 |
7 | ```bash
8 | $ npm i
9 | ```
10 |
11 | Start the dev server,
12 |
13 | ```bash
14 | $ npm start
15 | ```
16 |
17 | Build documentation,
18 |
19 | ```bash
20 | $ npm run docs:build
21 | ```
22 |
23 | Build library via `father-build`,
24 |
25 | ```bash
26 | $ npm run build
27 | ```
28 |
--------------------------------------------------------------------------------
/website/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "name": "website",
4 | "version": "1.0.0",
5 | "scripts": {
6 | "start": "dumi dev",
7 | "docs:build": "dumi build",
8 | "docs:deploy": "gh-pages -d dist",
9 | "build": "father-build",
10 | "deploy": "npm run docs:build && npm run docs:deploy",
11 | "release": "npm run build && npm publish",
12 | "prettier": "prettier --write \"**/*.{js,jsx,tsx,ts,less,md,json}\"",
13 | "test": "umi-test",
14 | "test:coverage": "umi-test --coverage"
15 | },
16 | "main": "dist/index.js",
17 | "module": "dist/index.esm.js",
18 | "typings": "dist/index.d.ts",
19 | "dependencies": {
20 | "react": "^16.12.0"
21 | },
22 | "devDependencies": {
23 | "@umijs/plugin-sass": "^1.1.1",
24 | "@umijs/test": "^3.0.5",
25 | "dumi": "^1.0.13",
26 | "father-build": "^1.17.2",
27 | "gh-pages": "^3.0.0",
28 | "yorkie": "^2.0.0"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/website/public/images/demo.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ximing/fabric-photo/5e59eff9a37166f7ce14eed250c00329b444a091/website/public/images/demo.jpeg
--------------------------------------------------------------------------------
/website/src/index.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ximing/fabric-photo/5e59eff9a37166f7ce14eed250c00329b444a091/website/src/index.ts
--------------------------------------------------------------------------------
/website/src/scss/iconfont.scss:
--------------------------------------------------------------------------------
1 | // font-face
2 | // @icon-url: 字体源文件的地址
3 | @font-face {
4 |
5 |
6 | font-family: 'dxicon';
7 |
8 | src: url('#{$icon-url}.eot'); /* IE9*/
9 | src: url('#{$icon-url}.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
10 | url('#{$icon-url}.woff') format('woff'), /* chrome、firefox */
11 | url('#{$icon-url}.ttf') format('truetype'), /* chrome、firefox、opera、Safari, Android, iOS 4.2+*/
12 | url('#{$icon-url}.svg#dxicon') format('svg'); /* iOS 4.1- */
13 |
14 | }
15 |
16 | .#{$iconfont-css-prefix} {
17 | @include iconfont-mixin;
18 | -webkit-font-smoothing: antialiased;
19 | -moz-osx-font-smoothing: grayscale;
20 | }
21 |
22 | .#{$iconfont-css-prefix}-setting:before {
23 | content: "\e654";
24 | }
25 |
26 | .#{$iconfont-css-prefix}-group-setting:before {
27 | content: "\e676";
28 | }
29 |
30 | .#{$iconfont-css-prefix}-todo:before {
31 | content: "\e62d";
32 | }
33 |
34 | .#{$iconfont-css-prefix}-right:before {
35 | content: "\e678";
36 | }
37 |
38 | .#{$iconfont-css-prefix}-news:before {
39 | content: "\e657";
40 | }
41 |
42 | .#{$iconfont-css-prefix}-contact:before {
43 | content: "\e651";
44 | }
45 |
46 | .#{$iconfont-css-prefix}-group:before {
47 | content: "\e655";
48 | }
49 |
50 | .#{$iconfont-css-prefix}-group1:before {
51 | content: "\e675";
52 | }
53 |
54 | .#{$iconfont-css-prefix}-group2:before {
55 | content: "\e68c";
56 | }
57 |
58 | .#{$iconfont-css-prefix}-addchat:before {
59 | content: "\e686";
60 | }
61 |
62 | .#{$iconfont-css-prefix}-addapp:before {
63 | content: "\e602";
64 | }
65 |
66 | .#{$iconfont-css-prefix}-xiangshang:before {
67 | content: "\e62c";
68 | }
69 |
70 | .#{$iconfont-css-prefix}-arrow-down:before {
71 | content: "\e66b";
72 | }
73 |
74 | //实心向下尖头
75 |
76 | .#{$iconfont-css-prefix}-arrow-right:before {
77 | content: "\e66a";
78 | }
79 |
80 | .#{$iconfont-css-prefix}-mute:before {
81 | content: "\e670";
82 | }
83 |
84 | .#{$iconfont-css-prefix}-round_close:before {
85 | content: "\e673";
86 | }
87 |
88 | //缺失
89 | .#{$iconfont-css-prefix}-search:before {
90 | content: "\e66e";
91 | }
92 |
93 | .#{$iconfont-css-prefix}-unrecognized:before {
94 | content: "\e66f";
95 | }
96 |
97 | .#{$iconfont-css-prefix}-zhankai:before {
98 | content: "\e656";
99 | }
100 |
101 | .#{$iconfont-css-prefix}-search-close:before {
102 | content: "\e679";
103 | }
104 |
105 | .#{$iconfont-css-prefix}-pull_right:before {
106 | content: "\e674";
107 | }
108 |
109 | .#{$iconfont-css-prefix}-message:before {
110 | content: "\e690";
111 | }
112 |
113 | .#{$iconfont-css-prefix}-checkbox:before {
114 | content: "\e668";
115 | }
116 |
117 | .#{$iconfont-css-prefix}-checkbox-checked:before {
118 | content: "\e669";
119 | }
120 |
121 | .#{$iconfont-css-prefix}-radio_box:before {
122 | content: "\e604";
123 | }
124 |
125 | .#{$iconfont-css-prefix}-radio_box_fill:before {
126 | content: "\e605";
127 | }
128 |
129 | .#{$iconfont-css-prefix}-gender-woman:before {
130 | content: "\e66d";
131 | }
132 |
133 | .#{$iconfont-css-prefix}-gender-man:before {
134 | content: "\e66c";
135 | }
136 |
137 | .#{$iconfont-css-prefix}-star-full:before {
138 | content: "\e680";
139 | }
140 |
141 | .#{$iconfont-css-prefix}-star:before {
142 | content: "\e67f";
143 | }
144 |
145 | .#{$iconfont-css-prefix}-folder:before {
146 | content: "\e67e";
147 | }
148 |
149 | .#{$iconfont-css-prefix}-sendfile:before {
150 | content: "\e61b";
151 | }
152 |
153 | .#{$iconfont-css-prefix}-screenshot:before {
154 | content: "\e62b";
155 | }
156 |
157 | .#{$iconfont-css-prefix}-unfold:before {
158 | content: "\e67b";
159 | }
160 |
161 | .#{$iconfont-css-prefix}-emoji:before {
162 | content: "\e610";
163 | }
164 |
165 | .#{$iconfont-css-prefix}-plus:before {
166 | content: "\e691";
167 | }
168 |
169 | .#{$iconfont-css-prefix}-post:before {
170 | content: "\e600";
171 | }
172 |
173 | //
174 | .#{$iconfont-css-prefix}-more:before {
175 | content: "\e652";
176 | }
177 |
178 | .#{$iconfont-css-prefix}-file:before {
179 | content: "\e650";
180 | }
181 |
182 | .#{$iconfont-css-prefix}-kefu:before {
183 | content: "\e653";
184 | }
185 |
186 |
187 | //toastr 使用的
188 | .#{$iconfont-css-prefix}-error:before {
189 | content: "\e693";
190 | }
191 |
192 | .#{$iconfont-css-prefix}-confirm:before {
193 | content: "\e684";
194 | }
195 |
196 | //app 自定义配置
197 | .#{$iconfont-css-prefix}-confirm-fill:before {
198 | content: "\e603";
199 | }
200 |
201 | .#{$iconfont-css-prefix}-remove-fill:before {
202 | content: "\e601";
203 | }
204 |
205 | .#{$iconfont-css-prefix}-left_voice_0:before {
206 | content: "\e681";
207 | }
208 |
209 | .#{$iconfont-css-prefix}-left_voice_1:before {
210 | content: "\e682";
211 | }
212 |
213 | .#{$iconfont-css-prefix}-left_voice_2:before {
214 | content: "\e683";
215 | }
216 |
217 | .#{$iconfont-css-prefix}-right_voice_0:before {
218 | content: "\e60b";
219 | }
220 |
221 | .#{$iconfont-css-prefix}-right_voice_1:before {
222 | content: "\e60c";
223 | }
224 |
225 | .#{$iconfont-css-prefix}-right_voice_2:before {
226 | content: "\e60d";
227 | }
228 |
229 | .#{$iconfont-css-prefix}-remove:before {
230 | content: "\e673";
231 | }
232 |
233 | .#{$iconfont-css-prefix}-warning:before {
234 | content: "\e672";
235 | }
236 |
237 | .#{$iconfont-css-prefix}-remove-group:before {
238 | content: "\e694";
239 | }
240 |
241 | .#{$iconfont-css-prefix}-round_down:before {
242 | content: "\e687";
243 | }
244 |
245 | .#{$iconfont-css-prefix}-quit:before {
246 | content: "\e67a";
247 | }
248 |
249 | .#{$iconfont-css-prefix}-crown:before {
250 | content: "\e667";
251 | }
252 |
253 | .#{$iconfont-css-prefix}-info:before {
254 | content: "\e677";
255 | }
256 |
257 | .#{$iconfont-css-prefix}-bubble-leader:before {
258 | content: "\e607";
259 | }
260 |
261 | .#{$iconfont-css-prefix}-bubble-star:before {
262 | content: "\e608";
263 | }
264 |
265 | .#{$iconfont-css-prefix}-yunpan:before {
266 | content: "\e613";
267 | }
268 |
269 | .#{$iconfont-css-prefix}-yunpan-close:before {
270 | content: "\e60a";
271 | }
272 |
273 | .#{$iconfont-css-prefix}-gongzongpingtai:before {
274 | content: "\e68b";
275 | }
276 |
277 | .#{$iconfont-css-prefix}-dalaba:before {
278 | content: "\e689";
279 | }
280 |
281 | .#{$iconfont-css-prefix}-message-fail:before {
282 | content: "\e606";
283 | }
284 |
285 | .#{$iconfont-css-prefix}-quote:before {
286 | content: "\e627";
287 | }
288 |
289 | .#{$iconfont-css-prefix}-chehui:before {
290 | content: "\e626";
291 | }
292 |
293 | .#{$iconfont-css-prefix}-close_notice:before {
294 | content: "\e670";
295 | }
296 |
297 | .#{$iconfont-css-prefix}-feedback:before {
298 | content: "\e614";
299 | }
300 |
301 | .#{$iconfont-css-prefix}-open-qr:before {
302 | content: "\e61a";
303 | }
304 |
305 | .#{$iconfont-css-prefix}-close-qr:before {
306 | content: "\e619";
307 | }
308 |
309 | .#{$iconfont-css-prefix}-view-qr:before {
310 | content: "\e618";
311 | }
312 |
313 | .#{$iconfont-css-prefix}-lfc:before { //left full corner
314 | content: "\e615";
315 | }
316 |
317 | .#{$iconfont-css-prefix}-llc:before {
318 | content: "\e616";
319 | }
320 |
321 | .#{$iconfont-css-prefix}-rfc:before { //left full corner
322 | content: "\e611";
323 | }
324 |
325 | .#{$iconfont-css-prefix}-rlc:before {
326 | content: "\e617";
327 | }
328 | .#{$iconfont-css-prefix}-i1000:before {
329 | content: "\e60e";
330 | }
331 |
332 | .#{$iconfont-css-prefix}-survey:before {
333 | content: "\e60f";
334 | }
335 |
336 | .#{$iconfont-css-prefix}-moremessage:before {
337 | content: "\e622";
338 | }
339 | .#{$iconfont-css-prefix}-forward:before {
340 | content: "\e628";
341 | }
342 |
343 | .#{$iconfont-css-prefix}-checkboxChecked:before {
344 | content: "\e620";
345 | }
346 | .#{$iconfont-css-prefix}-checkboxUncheck:before {
347 | content: "\e61f";
348 | }
349 | .#{$iconfont-css-prefix}-cancelChecked:before {
350 | content: "\e61e";
351 | }
352 |
353 | .#{$iconfont-css-prefix}-tag-receipt:before {
354 | content: "\e61d";
355 | }
356 |
357 | .#{$iconfont-css-prefix}-send-receipt:before {
358 | content: "\e61c";
359 | }
360 |
361 | .#{$iconfont-css-prefix}-daiban:before {
362 | content: "\e625";
363 | }
364 | .#{$iconfont-css-prefix}-backArrow:before {
365 | content: "\e623";
366 | }
367 | .#{$iconfont-css-prefix}-update:before {
368 | content: "\e624";
369 | }
370 | .#{$iconfont-css-prefix}-checked:before {
371 | content: "\e629";
372 | }
373 | .#{$iconfont-css-prefix}-toastr-close:before {
374 | content: "\e62a";
375 | }
376 | .#{$iconfont-css-prefix}-question-mark:before {
377 | content: "\e621";
378 | }
379 | .#{$iconfont-css-prefix}-daily-qun:before {
380 | content: "\e62e";
381 | }
382 | /*图片编辑器*/
383 | .#{$iconfont-css-prefix}-image-fangda:before {
384 | content: "\e638";
385 | }
386 | .#{$iconfont-css-prefix}-image-gou:before {
387 | content: "\e637";
388 | }
389 | .#{$iconfont-css-prefix}-image-xuanzhuan:before {
390 | content: "\e636";
391 | }
392 | .#{$iconfont-css-prefix}-image-masaike:before {
393 | content: "\e635";
394 | }
395 | .#{$iconfont-css-prefix}-image-suoxiao:before {
396 | content: "\e634";
397 | }
398 | .#{$iconfont-css-prefix}-image-text:before {
399 | content: "\e633";
400 | }
401 | .#{$iconfont-css-prefix}-image-huabi:before {
402 | content: "\e632";
403 | }
404 | .#{$iconfont-css-prefix}-image-jiantou:before {
405 | content: "\e631";
406 | }
407 | .#{$iconfont-css-prefix}-image-guanbi:before {
408 | content: "\e630";
409 | }
410 | .#{$iconfont-css-prefix}-image-jiancai:before {
411 | content: "\e62f";
412 | }
413 | /* end */
414 |
--------------------------------------------------------------------------------
/website/src/scss/index.scss:
--------------------------------------------------------------------------------
1 | @import "./variable";
2 | @import "./mixin/iconfont";
3 | @import "./mixin/clearfix";
4 | @import "./theme/_default";
5 | @import "./normalize";
6 | @import "./iconfont";
7 | @import "./main";
8 |
--------------------------------------------------------------------------------
/website/src/scss/main.scss:
--------------------------------------------------------------------------------
1 | .wrap_inner {
2 | width: 700px;
3 | height: 500px;
4 | margin: 0 auto;
5 | display: block;
6 | position: relative;
7 | .main {
8 | height: 100%;
9 | width: 100%;
10 | margin-top:50px;
11 | .upload-file-image-preview {
12 | height: 400px;
13 | width: 700px;
14 | text-align: center;
15 | .xm-fabric-photo-editor-canvas-container {
16 | display: inline-block;
17 | }
18 | }
19 | .file-button {
20 | height: 100px;
21 | width: 100%
22 | }
23 | .file-info-progress {
24 | height: 4px;
25 | background-color: $chat-bg-color;
26 | border-radius: 2px;
27 | position: absolute;
28 | bottom: 0px;
29 | left: 0;
30 | right: 0;
31 | .file-info-progress-bar {
32 | width: 0;
33 | height: 4px;
34 | background-color: $success;
35 | border-radius: 2px;
36 | }
37 | }
38 | .file-button.upload-success {
39 | width: 100%;
40 | height: 64px;
41 | }
42 | .file-button {
43 | display: flex;
44 | flex-direction: row;
45 | justify-content: space-between;
46 | padding: 14px 20px;
47 | box-sizing: border-box;
48 | .ctn-tips {
49 | flex: 1;
50 | position: relative;
51 | padding: 18px 16px;
52 | -webkit-user-select: none;
53 | -moz-user-select: none;
54 | .text {
55 | color: $primary;
56 | cursor: pointer;
57 | }
58 | .moretips {
59 | position: absolute;
60 | top: -108px;
61 | background: rgba(0, 0, 0, 0.87);
62 | border-radius: 4px;
63 | color: white;
64 | width: 520px;
65 | padding: 13px 20px 10px 20px;
66 | ol {
67 | padding: 0 20px;
68 | li {
69 | list-style: initial;
70 | }
71 | }
72 | .close {
73 | color: white;
74 | top: 0;
75 | right: 0;
76 | .dxicon {
77 | font-style: 10px;
78 | color: rgba(255, 255, 255, 0.69);
79 | }
80 | }
81 | .arrow {
82 | position: absolute;
83 | bottom: -13px;
84 | left: 40px;
85 | .dxicon {
86 | color: rgba(0, 0, 0, 0.87);
87 | font-size: 17px;
88 | }
89 | }
90 | }
91 | }
92 | .image-thumb-btns {
93 | vertical-align: middle;
94 | font-size: 0;
95 | &:before {
96 | content: '';
97 | display: inline-block;
98 | vertical-align: middle;
99 | font-size: 0;
100 | width: 0;
101 | height: 100%;
102 | }
103 | .thumb-divider {
104 | display: inline-block;
105 | height: 2px;
106 | border-bottom: 1px solid #eee;
107 | width: 60px;
108 | margin: 0 9px;
109 | }
110 | i {
111 | font-size: 18px;
112 | cursor: pointer;
113 | &:hover {
114 | color: $primary;
115 | }
116 | }
117 | }
118 | .image-tools-btns {
119 | vertical-align: middle;
120 | position: relative;
121 | .tools-divider {
122 | width: 1px;
123 | height: 24px;
124 | background: rgba(0,0,0,0.10);
125 | display: inline-block;
126 | vertical-align: middle;
127 | }
128 | i {
129 | font-size: 24px;
130 | margin-left: 14px;
131 | cursor: pointer;
132 | &:hover {
133 | color: $primary;
134 | }
135 | }
136 | .file-button-cancel {
137 | background: #FFFFFF;
138 | border: 1px solid rgba(0, 0, 0, 0.38);
139 | border-radius: 2px;
140 | height: 20px;
141 | width: 34px;
142 | font-size: 12px;
143 | color: rgba(0, 0, 0, 0.54);
144 | letter-spacing: 0;
145 | line-height: 18px;
146 | padding: 2px;
147 | cursor: pointer;
148 | }
149 | .tools-panel {
150 | position: absolute;
151 | top: 40px;
152 | width: 350px;
153 | height: 46px;
154 | box-sizing: border-box;
155 | background: #FFFFFF;
156 | box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.24);
157 | border-radius: 4px;
158 | padding: 8px 0;
159 | .tools-panel-brush {
160 | display: inline-block;
161 | div {
162 | height: 24px;
163 | width: 24px;
164 | display: inline-block;
165 | margin-left: 14px;
166 | text-align: center;
167 | vertical-align: middle;
168 | cursor: pointer;
169 | &:hover {
170 | span {
171 | background: rgba(0, 0, 0, 0.54);
172 | }
173 | }
174 | span {
175 | background: rgba(0, 0, 0, 0.24);
176 | &.active {
177 | background: rgba(0, 0, 0, 0.54);
178 | }
179 | }
180 | .small-brush {
181 | border-radius: 50%;
182 | width: 4px;
183 | height: 4px;
184 | display: inline-block;
185 | }
186 | .normal-brush {
187 | border-radius: 50%;
188 | width: 8px;
189 | height: 8px;
190 | display: inline-block;
191 | }
192 | .big-brush {
193 | border-radius: 50%;
194 | width: 12px;
195 | height: 12px;
196 | display: inline-block;
197 | }
198 | }
199 | }
200 | .tools-panel-color {
201 | display: inline-block;
202 | .color {
203 | border: 1px solid rgba(0, 0, 0, 0.10);
204 | border-radius: 2px;
205 | height: 16px;
206 | width: 16px;
207 | display: inline-block;
208 | margin-right: 8px;
209 | cursor: pointer;
210 | &.active {
211 | height: 22px;
212 | width: 22px;
213 | }
214 | &:hover {
215 | height: 22px;
216 | width: 22px;
217 | }
218 | &.red {
219 | background: #FF3440;
220 | }
221 | &.yellow {
222 | background: #FFCF50;
223 | }
224 | &.green {
225 | background: #00A344;
226 | }
227 | &.blue {
228 | background: #0DA9D6;
229 | }
230 | &.grey {
231 | background: #999999;
232 | }
233 | &.black {
234 | background: #000000;
235 | }
236 | &.white {
237 | background: #FFFFFF;
238 | }
239 | }
240 | }
241 | }
242 | }
243 | .ctn-btns {
244 | text-align: center;
245 | button {
246 | width: 88px;
247 | height: 36px;
248 | padding: 8px 0;
249 | }
250 | }
251 | &.upload-success {
252 | width: 100%;
253 | height: 56px;
254 | margin: 10px auto 0 auto;
255 | }
256 | }
257 | .file-button--pc {
258 | justify-content: flex-end;
259 | }
260 | }
261 | }
262 |
--------------------------------------------------------------------------------
/website/src/scss/mixin/clearfix.scss:
--------------------------------------------------------------------------------
1 | // mixins for clearfix
2 | // ------------------------
3 | @mixin clearfix() {
4 | zoom: 1;
5 | &:before,
6 | &:after {
7 | content: " ";
8 | display: table;
9 | }
10 | &:after {
11 | clear: both;
12 | visibility: hidden;
13 | font-size: 0;
14 | height: 0;
15 | }
16 | }
--------------------------------------------------------------------------------
/website/src/scss/mixin/common.scss:
--------------------------------------------------------------------------------
1 | @mixin cyclize-avatar($radius) {
2 | width: $radius * 2;
3 | border-radius: $radius;
4 | }
5 |
--------------------------------------------------------------------------------
/website/src/scss/mixin/iconfont.scss:
--------------------------------------------------------------------------------
1 | @mixin iconfont-mixin {
2 | display: inline-block;
3 | font-style: normal;
4 | vertical-align: middle;
5 | text-align: center;
6 | text-transform: none;
7 | text-rendering: auto;
8 | line-height: 1;
9 | font-size: 14px;
10 |
11 | &:before {
12 | display: block;
13 | font-family: "dxicon" !important;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/website/src/scss/normalize.scss:
--------------------------------------------------------------------------------
1 | /*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */
2 |
3 | //
4 | // 1. Set default font family to sans-serif.
5 | // 2. Prevent iOS and IE text size adjust after device orientation change,
6 | // without disabling user zoom.
7 | //
8 |
9 | html {
10 | font-family: sans-serif; // 1
11 | -ms-text-size-adjust: 100%; // 2
12 | -webkit-text-size-adjust: 100%; // 2
13 | }
14 |
15 | //
16 | // Remove default margin.
17 | //
18 |
19 | body {
20 | margin: 0;
21 | }
22 |
23 | // HTML5 display definitions
24 | // ==========================================================================
25 |
26 | //
27 | // Correct `block` display not defined for any HTML5 element in IE 8/9.
28 | // Correct `block` display not defined for `details` or `summary` in IE 10/11
29 | // and Firefox.
30 | // Correct `block` display not defined for `main` in IE 11.
31 | //
32 |
33 | article,
34 | aside,
35 | details,
36 | figcaption,
37 | figure,
38 | footer,
39 | header,
40 | hgroup,
41 | main,
42 | menu,
43 | nav,
44 | section,
45 | summary {
46 | display: block;
47 | }
48 |
49 | //
50 | // 1. Correct `inline-block` display not defined in IE 8/9.
51 | // 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
52 | //
53 |
54 | audio,
55 | canvas,
56 | progress,
57 | video {
58 | display: inline-block; // 1
59 | vertical-align: baseline; // 2
60 | }
61 |
62 | //
63 | // Prevent modern browsers from displaying `audio` without controls.
64 | // Remove excess height in iOS 5 devices.
65 | //
66 |
67 | audio:not([controls]) {
68 | display: none;
69 | height: 0;
70 | }
71 |
72 | //
73 | // Address `[hidden]` styling not present in IE 8/9/10.
74 | // Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22.
75 | //
76 |
77 | [hidden],
78 | template {
79 | display: none;
80 | }
81 |
82 | // Links
83 | // ==========================================================================
84 |
85 | //
86 | // Remove the gray background color from active links in IE 10.
87 | //
88 |
89 | a {
90 | background-color: transparent;
91 | }
92 |
93 | //
94 | // Improve readability of focused elements when they are also in an
95 | // active/hover state.
96 | //
97 |
98 | a:active,
99 | a:hover {
100 | outline: 0;
101 | }
102 |
103 | // Text-level semantics
104 | // ==========================================================================
105 |
106 | //
107 | // Address styling not present in IE 8/9/10/11, Safari, and Chrome.
108 | //
109 |
110 | abbr[title] {
111 | border-bottom: 1px dotted;
112 | }
113 |
114 | //
115 | // Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
116 | //
117 |
118 | b,
119 | strong {
120 | font-weight: bold;
121 | }
122 |
123 | //
124 | // Address styling not present in Safari and Chrome.
125 | //
126 |
127 | dfn {
128 | font-style: italic;
129 | }
130 |
131 | //
132 | // Address variable `h1` font-size and margin within `section` and `article`
133 | // contexts in Firefox 4+, Safari, and Chrome.
134 | //
135 |
136 | h1 {
137 | font-size: 2em;
138 | margin: 0.67em 0;
139 | }
140 |
141 | //
142 | // Address styling not present in IE 8/9.
143 | //
144 |
145 | mark {
146 | background: #ff0;
147 | color: #000;
148 | }
149 |
150 | //
151 | // Address inconsistent and variable font size in all browsers.
152 | //
153 |
154 | small {
155 | font-size: 80%;
156 | }
157 |
158 | //
159 | // Prevent `sub` and `sup` affecting `line-height` in all browsers.
160 | //
161 |
162 | sub,
163 | sup {
164 | font-size: 75%;
165 | line-height: 0;
166 | position: relative;
167 | vertical-align: baseline;
168 | }
169 |
170 | sup {
171 | top: -0.5em;
172 | }
173 |
174 | sub {
175 | bottom: -0.25em;
176 | }
177 |
178 | // Embedded content
179 | // ==========================================================================
180 |
181 | //
182 | // Remove border when inside `a` element in IE 8/9/10.
183 | //
184 |
185 | img {
186 | border: 0;
187 | }
188 |
189 | //
190 | // Correct overflow not hidden in IE 9/10/11.
191 | //
192 |
193 | svg:not(:root) {
194 | overflow: hidden;
195 | }
196 |
197 | // Grouping content
198 | // ==========================================================================
199 |
200 | //
201 | // Address margin not present in IE 8/9 and Safari.
202 | //
203 |
204 | figure {
205 | margin: 1em 40px;
206 | }
207 |
208 | //
209 | // Address differences between Firefox and other browsers.
210 | //
211 |
212 | hr {
213 | box-sizing: content-box;
214 | height: 0;
215 | }
216 |
217 | //
218 | // Contain overflow in all browsers.
219 | //
220 |
221 | //pre {
222 | // overflow: auto;
223 | //}
224 |
225 | //
226 | // Address odd `em`-unit font size rendering in all browsers.
227 | //
228 |
229 | code,
230 | kbd,
231 | pre,
232 | samp {
233 | font-family: monospace, monospace;
234 | font-size: 1em;
235 | }
236 |
237 | // Forms
238 | // ==========================================================================
239 |
240 | //
241 | // Known limitation: by default, Chrome and Safari on OS X allow very limited
242 | // styling of `select`, unless a `border` property is set.
243 | //
244 |
245 | //
246 | // 1. Correct color not being inherited.
247 | // Known issue: affects color of disabled elements.
248 | // 2. Correct font properties not being inherited.
249 | // 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
250 | //
251 |
252 | button,
253 | input,
254 | optgroup,
255 | select,
256 | textarea {
257 | color: inherit; // 1
258 | font: inherit; // 2
259 | margin: 0; // 3
260 | }
261 |
262 | //
263 | // Address `overflow` set to `hidden` in IE 8/9/10/11.
264 | //
265 |
266 | button {
267 | overflow: visible;
268 | }
269 |
270 | //
271 | // Address inconsistent `text-transform` inheritance for `button` and `select`.
272 | // All other form control elements do not inherit `text-transform` values.
273 | // Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
274 | // Correct `select` style inheritance in Firefox.
275 | //
276 |
277 | button,
278 | select {
279 | text-transform: none;
280 | }
281 |
282 | //
283 | // 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
284 | // and `video` controls.
285 | // 2. Correct inability to style clickable `input` types in iOS.
286 | // 3. Improve usability and consistency of cursor style between image-type
287 | // `input` and others.
288 | //
289 |
290 | button,
291 | html input[type="button"], // 1
292 | input[type="reset"],
293 | input[type="submit"] {
294 | -webkit-appearance: button; // 2
295 | cursor: pointer; // 3
296 | }
297 |
298 | //
299 | // Re-set default cursor for disabled elements.
300 | //
301 |
302 | button[disabled],
303 | html input[disabled] {
304 | cursor: default;
305 | }
306 |
307 | //
308 | // Remove inner padding and border in Firefox 4+.
309 | //
310 |
311 | button::-moz-focus-inner,
312 | input::-moz-focus-inner {
313 | border: 0;
314 | padding: 0;
315 | }
316 |
317 | //
318 | // Address Firefox 4+ groupSetting `line-height` on `input` using `!important` in
319 | // the UA stylesheet.
320 | //
321 |
322 | input {
323 | line-height: normal;
324 | }
325 |
326 | //
327 | // It's recommended that you don't attempt to style these elements.
328 | // Firefox's implementation doesn't respect box-sizing, padding, or width.
329 | //
330 | // 1. Address box sizing set to `content-box` in IE 8/9/10.
331 | // 2. Remove excess padding in IE 8/9/10.
332 | //
333 |
334 | input[type="checkbox"],
335 | input[type="radio"] {
336 | box-sizing: border-box; // 1
337 | padding: 0; // 2
338 | }
339 |
340 | //
341 | // Fix the cursor style for Chrome's increment/decrement buttons. For certain
342 | // `font-size` values of the `input`, it causes the cursor style of the
343 | // decrement button to change from `default` to `text`.
344 | //
345 |
346 | input[type="number"]::-webkit-inner-spin-button,
347 | input[type="number"]::-webkit-outer-spin-button {
348 | height: auto;
349 | }
350 |
351 | //
352 | // 1. Address `appearance` set to `searchfield` in Safari and Chrome.
353 | // 2. Address `box-sizing` set to `border-box` in Safari and Chrome.
354 | //
355 |
356 | input[type="search"] {
357 | -webkit-appearance: textfield; // 1
358 | box-sizing: content-box; //2
359 | }
360 |
361 | //
362 | // Remove inner padding and search cancel button in Safari and Chrome on OS X.
363 | // Safari (but not Chrome) clips the cancel button when the search input has
364 | // padding (and `textfield` appearance).
365 | //
366 |
367 | input[type="search"]::-webkit-search-cancel-button,
368 | input[type="search"]::-webkit-search-decoration {
369 | -webkit-appearance: none;
370 | }
371 |
372 | //
373 | // Define consistent border, margin, and padding.
374 | //
375 |
376 | fieldset {
377 | border: 1px solid #c0c0c0;
378 | margin: 0 2px;
379 | padding: 0.35em 0.625em 0.75em;
380 | }
381 |
382 | //
383 | // 1. Correct `color` not being inherited in IE 8/9/10/11.
384 | // 2. Remove padding so people aren't caught out if they zero out fieldsets.
385 | //
386 |
387 | legend {
388 | border: 0; // 1
389 | padding: 0; // 2
390 | }
391 |
392 | //
393 | // Remove default vertical scrollbar in IE 8/9/10/11.
394 | //
395 |
396 | textarea {
397 | overflow: auto;
398 | }
399 |
400 | //
401 | // Don't inherit the `font-weight` (applied by a rule above).
402 | // NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
403 | //
404 |
405 | optgroup {
406 | font-weight: bold;
407 | }
408 |
409 | // Tables
410 | // ==========================================================================
411 |
412 | //
413 | // Remove most spacing between table cells.
414 | //
415 |
416 | table {
417 | border-collapse: collapse;
418 | border-spacing: 0;
419 | }
420 |
421 | td,
422 | th {
423 | padding: 0;
424 | }
425 |
426 | ul, li{
427 | padding:0;
428 | margin:0;
429 | }
430 | li{
431 | list-style:none;
432 | }
433 |
--------------------------------------------------------------------------------
/website/src/scss/theme/_default.scss:
--------------------------------------------------------------------------------
1 | //上面的六种颜色是要删掉的,不要再项目中引用了
2 | //灰白色,这里只是取一些更普通的名称
3 | //$color-darker : rgba(0,0,0,.87);/*#333333*/
4 | //$color-dark : rgba(0,0,0,.54);/*#666666*/
5 | //$color-base : rgba(0,0,0,.38);/*#666666*/
6 | //$color-medium : #CCCCCC;
7 | //$color-light : #E5E5E5;
8 | //$color-lighter : #F8F8F8;
9 | //$color-lightest : #FFFFFF;
10 |
11 | $border-color : rgba(0,0,0,0.1);
12 | $chat-bg-color : #F3F5F7;//右侧聊天窗口的背景色 rgba(255,255,255, 0.75)
13 | $session-bg-color : #E8EBEF;//右侧聊天窗口的背景色 rgba(255,255,255, 0.65)
14 | $right-bg-color : #F3F5F7;
15 | $right-chat-bg-color : transparent;
16 | $search-ipt-bg-color : rgba(255,255,255, 0.5);
17 |
18 | //主色
19 | $primary : #118BFB;
20 | $navigation-bg : #2E3E51;
21 | $navigation-hover-bg : rgba($primary, 0.1);
22 | $navigation-active-bg : rgba(16,139,251,0.15);
23 | $navigation-active-border : $primary;
24 | $navigation-color : rgba($color-lightest, 0.5);//导航栏icon颜色
25 |
26 | $divider-color : rgba($color-lightest,0.1);
27 |
28 |
29 | $hover-color: rgba($primary, 0.05);//hover时候的颜色
30 | $active-color: rgba($primary, 0.15);//激活时候的颜色
31 |
32 |
33 | //辅助颜色
34 | $toast-common : #F3F9FF;
35 | $toast-abnormal : #FFFAF2;
36 | $toast-error : #FFF7F6;
37 | $toast-success : #F7FBF5;
38 |
39 |
40 |
41 | $color-grey : #F4F4F4;//搜索框
42 | $color-you : #CAE5F9;
43 | $color-me : #E5E5E5;
44 |
45 | //输入框背景色
46 | $input-background-color : #FBFBFB;
47 |
48 |
49 |
50 | $bg-image : $chat-bg-color;//背景图片
51 |
52 | $main-bg-style : url($chat-bg-color) left bottom/cover no-repeat at-2x;
53 | $group-file-bg-image : '';
54 | $group-file-bg-group-path : rgba(0,0,0,0.02);
55 | $group-file-bg-group-path-border: 1px solid rgba(130, 130, 130, 0.1);
56 | $group-file-search-border: 1px solid rgba(0, 0, 0, 0.1);
57 | $group-file-header-tab-background-color: transparent;
58 |
59 |
60 | $btn-bg-base : $primary;
61 | $border-color-base : darken($primary, 5%);
62 | $btn-primary-color : #fff;
63 | $btn-primary-bg : $primary;
64 |
65 | $btn-ghost-color : #FFFFFF;
66 | $btn-ghost-bg : $supplement;
67 | $btn-ghost-border : darken($supplement, 5%);
68 |
69 | $btn-disable-color : #ccc;
70 | $btn-disable-bg : #f3f5f7;
71 | $btn-disable-border : $border-color-base;
72 |
73 | $btn-danger-color : #FFFFFF;
74 | $btn-danger-bg : $danger;
75 | $btn-danger-border : $danger;
76 |
77 |
78 |
79 | //new css
80 | $yunpan-bg : #FFFFFF
81 |
--------------------------------------------------------------------------------
/website/src/scss/variable.scss:
--------------------------------------------------------------------------------
1 | //这里定义了一些基础的变量
2 |
3 | //通用基础颜色
4 | $color-darker : rgba(0,0,0,.87);/*#333333*/
5 | $color-dark : rgba(0,0,0,.54);/*#666666*/
6 | $color-base : rgba(0,0,0,.38);/*#999999*/
7 | $color-medium : rgba(0,0,0,.24);/*#CCCCCC*/
8 | $color-light : rgba(0,0,0,.1);/*#E5E5E5*/
9 | $color-light-1 : rgba(0,0,0,.05);
10 | $color-lighter : #F8F8F8;
11 | $color-lightest : #FFFFFF;
12 |
13 | //通用辅助颜色
14 | $supplement : #FF9801;
15 | $danger : #FF5D4A;
16 | $success : #5ABB3C;
17 | $name-other : #596E8F;
18 | $name-own : #CC841B;
19 | $color-pink : #F37D9A;//女性
20 | $color-blue : #46BEEF;//男性
21 |
22 |
23 | //存放一些宽度
24 |
25 | $main-title-height : 70px;//顶部70px
26 | $nav-width :60px;
27 | $left-width :280px;//内容区域左边列表宽度
28 |
29 |
30 | //z-index.scss
31 | $min-zindex : 1;
32 | $medium-zindex : 10;
33 | $large-zindex : 100;
34 | $max-zindex : 1000;
35 |
36 | //普通元素的padding
37 | $ctn-padding-xmin : 1px;
38 | $ctn-padding-min : 2px;
39 | $ctn-padding-normal : 5px;
40 | $ctn-padding-large : 10px;
41 | $ctn-padding-xlarge : 15px;
42 | $ctn-padding-xxlarge : 20px;
43 |
44 | //profile 宽高度
45 |
46 | //thumb 头像大小(头像的尺寸:80,60,40,36,28.)
47 | $thumb-base : 28px;
48 | $thumb-large : 36px;
49 | $thumb-xlarge : 40px;
50 | $thumb-xxlarge : 60px;
51 | $thumb-xxxlarge : 80px;
52 |
53 |
54 | //原有的头像尺寸(需要废弃)
55 | //$thumb
56 | //$thumb-min : 12px;
57 | //$thumb-xxxxlarge : $thumb-min * 6;
58 | //$thumb-big : 80px;
59 |
60 |
61 |
62 | //这里存字体相关的
63 | $font-family : "Helvetica Neue",Helvetica,"Apple Color Emoji",'Segoe UI Emoji', 'Segoe UI Symbol',Arial,"PingFang SC","Heiti SC", "Hiragino Sans GB","Microsoft YaHei","微软雅黑",sans-serif;
64 | $code-family : Consolas,Menlo,Courier,monospace;
65 | $font-size-small : 12px; //针对h6,说明文字
66 | $font-size-lSmall : 13px; //针对h6,说明文字
67 | $font-size-base : 14px; //正文,链接,h5
68 | $font-size-large : 16px; //h4
69 | $font-size-xlarge : 18px; //h3
70 | $font-size-xxlarge : 20px; //h2
71 | $font-size-xxxlarge : 24px; //h1
72 | $line-height-base : 1.5;
73 | $line-height-computed : floor(($font-size-base * $line-height-base));
74 |
75 | $border-radius-base : 2px;
76 | $border-radius-normal : 4px;
77 | $border-radius-large : 5px;
78 | $border-radius-xlarge : 10px;
79 | $border-radius-xxlarge : 15px;
80 |
81 |
82 |
83 | // ICONFONT
84 | $iconfont-css-prefix : dxicon;
85 |
86 | $icon-url : "//at.alicdn.com/t/font_ovsogmhgit4ndn29"; //经常会改变,以后会换成本地的地址
87 |
88 |
89 |
90 | // Animation
91 | $ease-out : cubic-bezier(0.215, 0.61, 0.355, 1);
92 | $ease-in : cubic-bezier(0.55, 0.055, 0.675, 0.19);
93 | $ease-in-out : cubic-bezier(0.645, 0.045, 0.355, 1);
94 | $ease-out-back : cubic-bezier(0.12, 0.4, 0.29, 1.46);
95 | $ease-in-back : cubic-bezier(0.71, -0.46, 0.88, 0.6);
96 | $ease-in-out-back : cubic-bezier(0.71, -0.46, 0.29, 1.46);
97 | $ease-out-circ : cubic-bezier(0.08, 0.82, 0.17, 1);
98 | $ease-in-circ : cubic-bezier(0.6, 0.04, 0.98, 0.34);
99 | $ease-in-out-circ : cubic-bezier(0.78, 0.14, 0.15, 0.86);
100 | $ease-out-quint : cubic-bezier(0.23, 1, 0.32, 1);
101 | $ease-in-quint : cubic-bezier(0.755, 0.05, 0.855, 0.06);
102 | $ease-in-out-quint : cubic-bezier(0.86, 0, 0.07, 1);
103 |
104 |
105 | // 按钮边框颜色,理论上应该是背景色加深一点就可以了
106 |
107 | //$border-color-base : #d9d9d9; // base border outline a component
108 | //$box-shadow-base : 0 0 4px rgba(0, 0, 0, 0.17);
109 | //$border-color-split : #e9e9e9; // split border inside a component
110 | $cursor-disabled : not-allowed;
111 | $btn-font-weight : normal;
112 |
113 |
114 |
115 |
116 |
117 | $btn-default-color : $color-darker;
118 | $btn-default-bg : $color-lighter;
119 | $btn-default-border : $color-light;
120 |
121 |
122 |
123 |
124 |
125 |
126 | $btn-padding-base : 8px 31px;
127 | $btn-border-radius-base : 4px;
128 |
129 | $btn-font-size-lg : 14px;
130 | $btn-padding-lg : 4px 11px 5px 11px;
131 | $btn-border-radius-lg : $btn-border-radius-base;
132 |
133 | $btn-padding-sm : 1px 7px;
134 | $btn-border-radius-sm : $btn-border-radius-base;
135 |
136 | $btn-circle-size : 28px;
137 | $btn-circle-size-lg : 32px;
138 | $btn-circle-size-sm : 22px;
139 |
140 | $msg-title-height: 70px;
141 | $msg-border-color: $color-light;
142 |
143 |
144 |
145 |
146 |
147 | //气泡页部分宽度定义
148 | $msg-medium-min-width: 630px;
149 | $msg-medium-max-width: 1000px;
150 | $msg-medium-width : 860px;
151 | $msg-medium-left : 0px;
152 | $msg-medium-right : 0px;
153 | $msg-medium-top: 0px;
154 | $msg-medium-bottom: 0px;
155 | $slidepanel-width: 470px;
156 |
157 |
--------------------------------------------------------------------------------
/website/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "esnext",
5 | "moduleResolution": "node",
6 | "importHelpers": true,
7 | "jsx": "react",
8 | "esModuleInterop": true,
9 | "sourceMap": true,
10 | "baseUrl": "./",
11 | "strict": true,
12 | "paths": {
13 | "@/*": ["src/*"],
14 | "@@/*": ["src/.umi/*"]
15 | },
16 | "allowSyntheticDefaultImports": true
17 | },
18 | "exclude": [
19 | "node_modules",
20 | "lib",
21 | "es",
22 | "dist",
23 | "typings",
24 | "**/__test__",
25 | "test",
26 | "docs",
27 | "tests"
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/website/typings.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.css';
2 | declare module '*.less';
3 |
--------------------------------------------------------------------------------