jellies-draw 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -2
- package/src/components/ToolButtons.vue +3 -2
- package/src/components/functions/canvas.js +3 -3
- package/src/components/functions/clipboard.js +14 -3
- package/src/components/functions/histories.js +2 -0
- package/src/components/functions/tools/arrow.js +8 -1
- package/src/components/functions/tools/ellipse.js +7 -1
- package/src/components/functions/tools/eraser.js +5 -1
- package/src/components/functions/tools/helper/attachedText.js +386 -0
- package/src/components/functions/tools/line.js +8 -1
- package/src/components/functions/tools/pen.js +5 -1
- package/src/components/functions/tools/rectangle.js +8 -2
- package/src/components/functions/tools/text.js +29 -61
- package/src/components/functions/transformer.js +21 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jellies-draw",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "A drawer for jellies",
|
|
5
5
|
"private": false,
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -59,5 +59,8 @@
|
|
|
59
59
|
"> 1%",
|
|
60
60
|
"last 2 versions",
|
|
61
61
|
"not dead"
|
|
62
|
-
]
|
|
62
|
+
],
|
|
63
|
+
"resolutions": {
|
|
64
|
+
"vue-loader": "^15.11.1"
|
|
65
|
+
}
|
|
63
66
|
}
|
|
@@ -117,10 +117,11 @@ export default {
|
|
|
117
117
|
if (action === 'clear') {
|
|
118
118
|
Tools.clear.clear()
|
|
119
119
|
}
|
|
120
|
-
if (this.currentTool !== action &&
|
|
120
|
+
if (this.currentTool !== action &&
|
|
121
121
|
(
|
|
122
122
|
(this.toolsCanBeLocked.includes(this.currentTool) && this.isCurrentToolLocked) ||
|
|
123
|
-
this.toolsCanBeUsedContinuously.includes(this.currentTool)
|
|
123
|
+
this.toolsCanBeUsedContinuously.includes(this.currentTool) ||
|
|
124
|
+
this.currentTool === 'selector'
|
|
124
125
|
)
|
|
125
126
|
) {
|
|
126
127
|
this.latestTool = {
|
|
@@ -4,6 +4,7 @@ import Clipboard from './clipboard';
|
|
|
4
4
|
import Properties from './properties';
|
|
5
5
|
import Tools from './tools';
|
|
6
6
|
import Histories from './histories';
|
|
7
|
+
import AttachedText from './tools/helper/attachedText';
|
|
7
8
|
export default {
|
|
8
9
|
layer: null,
|
|
9
10
|
stage: null,
|
|
@@ -29,6 +30,7 @@ export default {
|
|
|
29
30
|
});
|
|
30
31
|
this.layer = new Konva.Layer();
|
|
31
32
|
this.stage.add(this.layer);
|
|
33
|
+
AttachedText.attachLayerListeners(this.layer);
|
|
32
34
|
document.addEventListener('keydown', this.keydownHandler);
|
|
33
35
|
document.addEventListener('keyup', this.keyupHandler);
|
|
34
36
|
Transformer.initialize();
|
|
@@ -38,9 +40,7 @@ export default {
|
|
|
38
40
|
this.stage.on('pointerup', this.handleCanvasPointerUp);
|
|
39
41
|
this.stage.on('click tap', this.clickHandler);
|
|
40
42
|
this.stage.on('pointerdown', () => {
|
|
41
|
-
|
|
42
|
-
window.addEventListener('pointerup', this.handleGlobalPointerUp, { once: true });
|
|
43
|
-
}
|
|
43
|
+
window.addEventListener('pointerup', this.handleGlobalPointerUp, { once: true });
|
|
44
44
|
});
|
|
45
45
|
this.stage.on('mousemove', this._handleMouseMove.bind(this));
|
|
46
46
|
},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Canvas from './canvas';
|
|
2
2
|
import Transformer from './transformer';
|
|
3
3
|
import Tools from './tools';
|
|
4
|
+
import Histories from './histories';
|
|
4
5
|
export default {
|
|
5
6
|
copiedNodes: [],
|
|
6
7
|
copy() {
|
|
@@ -11,7 +12,7 @@ export default {
|
|
|
11
12
|
Transformer.removeSelectedNodes();
|
|
12
13
|
},
|
|
13
14
|
paste() {
|
|
14
|
-
if (this.copiedNodes) {
|
|
15
|
+
if (this.copiedNodes && this.copiedNodes.length > 0) {
|
|
15
16
|
this.copiedNodes.forEach((node) => {
|
|
16
17
|
node.x(node.x() + 10);
|
|
17
18
|
node.y(node.y() + 10);
|
|
@@ -20,13 +21,23 @@ export default {
|
|
|
20
21
|
});
|
|
21
22
|
Transformer.selectNodes(this.copiedNodes);
|
|
22
23
|
this.copiedNodes = Transformer.copySelectedNodes();
|
|
24
|
+
Histories.record();
|
|
23
25
|
}
|
|
24
26
|
},
|
|
25
27
|
_addEventsToCopiedNode(node) {
|
|
26
|
-
|
|
28
|
+
const className = node.getClassName();
|
|
29
|
+
if (className === 'Text') {
|
|
27
30
|
Tools.text.bindEvents(node);
|
|
28
|
-
} else if (
|
|
31
|
+
} else if (className === 'Arrow') {
|
|
29
32
|
Tools.arrow.bindEvents(node);
|
|
33
|
+
} else if (className === 'Rect') {
|
|
34
|
+
Tools.rectangle.bindEvents(node);
|
|
35
|
+
} else if (className === 'Ellipse') {
|
|
36
|
+
Tools.ellipse.bindEvents(node);
|
|
37
|
+
} else if (className === 'Line') {
|
|
38
|
+
Tools.line.bindEvents(node);
|
|
39
|
+
} else if (className === 'Shape' && node.attrs.nodeType === 'pen') {
|
|
40
|
+
Tools.pen.bindEvents(node);
|
|
30
41
|
}
|
|
31
42
|
}
|
|
32
43
|
}
|
|
@@ -23,12 +23,14 @@ export default new Vue({
|
|
|
23
23
|
},
|
|
24
24
|
undo() {
|
|
25
25
|
if (this.historyIndex > 0) {
|
|
26
|
+
clearTimeout(this.recordTimer)
|
|
26
27
|
this.historyIndex--
|
|
27
28
|
this.refreshNodes()
|
|
28
29
|
}
|
|
29
30
|
},
|
|
30
31
|
redo() {
|
|
31
32
|
if (this.historyIndex < this.histories.length - 1) {
|
|
33
|
+
clearTimeout(this.recordTimer)
|
|
32
34
|
this.historyIndex++
|
|
33
35
|
this.refreshNodes()
|
|
34
36
|
}
|
|
@@ -3,6 +3,7 @@ import Properties from '../properties'
|
|
|
3
3
|
import Canvas from '../canvas'
|
|
4
4
|
import Histories from '../histories'
|
|
5
5
|
import AngleSnap from './helper/angleSnap'
|
|
6
|
+
import AttachedText from './helper/attachedText'
|
|
6
7
|
|
|
7
8
|
export default {
|
|
8
9
|
temporalShape: null,
|
|
@@ -58,18 +59,24 @@ export default {
|
|
|
58
59
|
})
|
|
59
60
|
newArrow.on('transformend', Histories.record)
|
|
60
61
|
newArrow.on('dragend', Histories.record)
|
|
62
|
+
AttachedText.bindShapeEvents(newArrow)
|
|
61
63
|
},
|
|
62
|
-
generate({ x, y, scaleX, scaleY, points, stroke, strokeWidth, pointerWidth, pointerLength }) {
|
|
64
|
+
generate({ x, y, scaleX, scaleY, skewX, rotation, points, stroke, strokeWidth, pointerWidth, pointerLength, attachedText, attachedFontSize, attachedFill }) {
|
|
63
65
|
const newArrow = new Konva.Arrow({
|
|
64
66
|
x,
|
|
65
67
|
y,
|
|
66
68
|
scaleX,
|
|
67
69
|
scaleY,
|
|
70
|
+
skewX,
|
|
71
|
+
rotation,
|
|
68
72
|
points,
|
|
69
73
|
stroke,
|
|
70
74
|
strokeWidth,
|
|
71
75
|
pointerWidth,
|
|
72
76
|
pointerLength,
|
|
77
|
+
attachedText,
|
|
78
|
+
attachedFontSize,
|
|
79
|
+
attachedFill,
|
|
73
80
|
name: 'node',
|
|
74
81
|
nodeType: 'arrow',
|
|
75
82
|
lineCap: 'round',
|
|
@@ -3,6 +3,7 @@ import Properties from '../properties'
|
|
|
3
3
|
import Canvas from '../canvas'
|
|
4
4
|
import Histories from '../histories'
|
|
5
5
|
import ShapeConstraint from './helper/shapeConstraint'
|
|
6
|
+
import AttachedText from './helper/attachedText'
|
|
6
7
|
|
|
7
8
|
export default {
|
|
8
9
|
temporalShape: null,
|
|
@@ -55,10 +56,11 @@ export default {
|
|
|
55
56
|
if (!this.temporalShape && newEllipse.radiusX() < 5 && newEllipse.radiusY() < 5) {
|
|
56
57
|
newEllipse.radiusX(15)
|
|
57
58
|
newEllipse.radiusY(15)
|
|
59
|
+
Histories.record()
|
|
58
60
|
}
|
|
59
61
|
}, 200)
|
|
60
62
|
},
|
|
61
|
-
generate({ x, y, radiusX, radiusY, scaleX, scaleY, skewX, rotation, fill, stroke, strokeWidth }) {
|
|
63
|
+
generate({ x, y, radiusX, radiusY, scaleX, scaleY, skewX, rotation, fill, stroke, strokeWidth, attachedText, attachedFontSize, attachedFill }) {
|
|
62
64
|
const newEllipse = new Konva.Ellipse({
|
|
63
65
|
x,
|
|
64
66
|
y,
|
|
@@ -71,6 +73,9 @@ export default {
|
|
|
71
73
|
fill,
|
|
72
74
|
stroke,
|
|
73
75
|
strokeWidth,
|
|
76
|
+
attachedText,
|
|
77
|
+
attachedFontSize,
|
|
78
|
+
attachedFill,
|
|
74
79
|
name: 'node',
|
|
75
80
|
nodeType: 'ellipse',
|
|
76
81
|
strokeScaleEnabled: false
|
|
@@ -83,5 +88,6 @@ export default {
|
|
|
83
88
|
newEllipse.off('dragend')
|
|
84
89
|
newEllipse.on('transformend', Histories.record)
|
|
85
90
|
newEllipse.on('dragend', Histories.record)
|
|
91
|
+
AttachedText.bindShapeEvents(newEllipse)
|
|
86
92
|
}
|
|
87
93
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Konva from 'konva'
|
|
2
2
|
import Properties from '../properties'
|
|
3
3
|
import Canvas from '../canvas'
|
|
4
|
+
import AttachedText from './helper/attachedText'
|
|
4
5
|
|
|
5
6
|
export default {
|
|
6
7
|
temporalShape: null,
|
|
@@ -87,7 +88,10 @@ export default {
|
|
|
87
88
|
},
|
|
88
89
|
|
|
89
90
|
_destroyErasedNodes() {
|
|
90
|
-
this.erasedNodes.forEach(node =>
|
|
91
|
+
this.erasedNodes.forEach(node => {
|
|
92
|
+
AttachedText.deleteVisibleNode(node)
|
|
93
|
+
node.destroy()
|
|
94
|
+
})
|
|
91
95
|
this.erasedNodes.clear()
|
|
92
96
|
},
|
|
93
97
|
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import Konva from 'konva'
|
|
2
|
+
import Canvas from '../../canvas'
|
|
3
|
+
import Properties from '../../properties'
|
|
4
|
+
import Histories from '../../histories'
|
|
5
|
+
|
|
6
|
+
const SHAPE_CLASS_NAMES = ['Rect', 'Ellipse', 'Line', 'Arrow']
|
|
7
|
+
const CHANGE_EVENTS_BY_CLASS = {
|
|
8
|
+
Rect: ['xChange', 'yChange', 'scaleXChange', 'scaleYChange', 'skewXChange', 'rotationChange', 'widthChange', 'heightChange', 'strokeChange'],
|
|
9
|
+
Ellipse: ['xChange', 'yChange', 'scaleXChange', 'scaleYChange', 'skewXChange', 'rotationChange', 'radiusXChange', 'radiusYChange', 'strokeChange'],
|
|
10
|
+
Line: ['xChange', 'yChange', 'scaleXChange', 'scaleYChange', 'skewXChange', 'rotationChange', 'pointsChange', 'strokeChange'],
|
|
11
|
+
Arrow: ['xChange', 'yChange', 'scaleXChange', 'scaleYChange', 'skewXChange', 'rotationChange', 'pointsChange', 'strokeChange']
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let activeEditor = null
|
|
15
|
+
|
|
16
|
+
function isPathShape(shape) {
|
|
17
|
+
return shape.className === 'Line' || shape.className === 'Arrow'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function supportsAttachedText(node) {
|
|
21
|
+
return SHAPE_CLASS_NAMES.includes(node.className)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getCenterMetrics(shape) {
|
|
25
|
+
if (shape.className === 'Rect') {
|
|
26
|
+
return {
|
|
27
|
+
cx: shape.width() / 2,
|
|
28
|
+
cy: shape.height() / 2,
|
|
29
|
+
width: shape.width(),
|
|
30
|
+
height: shape.height()
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (shape.className === 'Ellipse') {
|
|
34
|
+
return {
|
|
35
|
+
cx: 0,
|
|
36
|
+
cy: 0,
|
|
37
|
+
width: shape.radiusX() * 2,
|
|
38
|
+
height: shape.radiusY() * 2
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return null
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function freshShapeTransform(shape) {
|
|
45
|
+
const m = new Konva.Transform()
|
|
46
|
+
const a = shape.attrs
|
|
47
|
+
const x = a.x || 0
|
|
48
|
+
const y = a.y || 0
|
|
49
|
+
if (x !== 0 || y !== 0) m.translate(x, y)
|
|
50
|
+
const rotation = Konva.getAngle(a.rotation || 0)
|
|
51
|
+
if (rotation !== 0) m.rotate(rotation)
|
|
52
|
+
const skewX = a.skewX || 0
|
|
53
|
+
const skewY = a.skewY || 0
|
|
54
|
+
if (skewX !== 0 || skewY !== 0) m.skew(skewX, skewY)
|
|
55
|
+
const sx = a.scaleX === undefined ? 1 : a.scaleX
|
|
56
|
+
const sy = a.scaleY === undefined ? 1 : a.scaleY
|
|
57
|
+
if (sx !== 1 || sy !== 1) m.scale(sx, sy)
|
|
58
|
+
const ox = a.offsetX || 0
|
|
59
|
+
const oy = a.offsetY || 0
|
|
60
|
+
if (ox !== 0 || oy !== 0) m.translate(-ox, -oy)
|
|
61
|
+
return m
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function getPathMidpoint(shape) {
|
|
65
|
+
const points = shape.getPoints()
|
|
66
|
+
if (!points || points.length < 4) return null
|
|
67
|
+
const midX = (points[0] + points[points.length - 2]) / 2
|
|
68
|
+
const midY = (points[1] + points[points.length - 1]) / 2
|
|
69
|
+
return freshShapeTransform(shape).point({ x: midX, y: midY })
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function findVisibleNode(shape) {
|
|
73
|
+
if (shape._attachedVisibleNode && shape._attachedVisibleNode.getStage()) {
|
|
74
|
+
return shape._attachedVisibleNode
|
|
75
|
+
}
|
|
76
|
+
shape._attachedVisibleNode = null
|
|
77
|
+
return null
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function syncCenterText(shape, textNode) {
|
|
81
|
+
const metrics = getCenterMetrics(shape)
|
|
82
|
+
if (!metrics) return
|
|
83
|
+
const padding = 4
|
|
84
|
+
const innerWidth = Math.max(20, metrics.width - padding * 2)
|
|
85
|
+
textNode.fill(shape.stroke() || Properties.strokeColor)
|
|
86
|
+
textNode.width(innerWidth)
|
|
87
|
+
textNode.align('center')
|
|
88
|
+
textNode.verticalAlign('middle')
|
|
89
|
+
textNode.scaleX(shape.scaleX() || 1)
|
|
90
|
+
textNode.scaleY(shape.scaleY() || 1)
|
|
91
|
+
textNode.skewX(shape.skewX() || 0)
|
|
92
|
+
textNode.rotation(shape.rotation() || 0)
|
|
93
|
+
textNode.offsetX(innerWidth / 2)
|
|
94
|
+
textNode.offsetY(textNode.height() / 2)
|
|
95
|
+
const worldCenter = freshShapeTransform(shape).point({ x: metrics.cx, y: metrics.cy })
|
|
96
|
+
textNode.absolutePosition(worldCenter)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function syncPathText(shape, textNode) {
|
|
100
|
+
const midpoint = getPathMidpoint(shape)
|
|
101
|
+
if (!midpoint) return
|
|
102
|
+
textNode.fill(shape.stroke() || Properties.strokeColor)
|
|
103
|
+
textNode.setAttr('width', undefined)
|
|
104
|
+
textNode.align('left')
|
|
105
|
+
textNode.verticalAlign('top')
|
|
106
|
+
textNode.scaleX(1)
|
|
107
|
+
textNode.scaleY(1)
|
|
108
|
+
textNode.skewX(0)
|
|
109
|
+
textNode.rotation(0)
|
|
110
|
+
textNode.offsetX(textNode.width() / 2)
|
|
111
|
+
textNode.offsetY(textNode.height() / 2)
|
|
112
|
+
textNode.absolutePosition(midpoint)
|
|
113
|
+
applyLineGap(shape, textNode)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function applyLineGap(shape, textNode) {
|
|
117
|
+
const text = textNode.text()
|
|
118
|
+
const points = shape.getPoints()
|
|
119
|
+
if (!text || !points || points.length < 4) {
|
|
120
|
+
shape.dashEnabled(false)
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
const transform = freshShapeTransform(shape)
|
|
124
|
+
const start = transform.point({ x: points[0], y: points[1] })
|
|
125
|
+
const end = transform.point({ x: points[points.length - 2], y: points[points.length - 1] })
|
|
126
|
+
const screenLength = Math.sqrt(Math.pow(end.x - start.x, 2) + Math.pow(end.y - start.y, 2))
|
|
127
|
+
const padding = 6
|
|
128
|
+
const screenGap = textNode.width() + padding * 2
|
|
129
|
+
if (screenLength > screenGap + 4) {
|
|
130
|
+
const halfRemaining = (screenLength - screenGap) / 2
|
|
131
|
+
shape.dash([halfRemaining, screenGap, halfRemaining])
|
|
132
|
+
shape.dashEnabled(true)
|
|
133
|
+
} else {
|
|
134
|
+
shape.dashEnabled(false)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function syncVisibleNode(shape) {
|
|
139
|
+
const visible = findVisibleNode(shape)
|
|
140
|
+
if (!visible) return
|
|
141
|
+
if (isPathShape(shape)) {
|
|
142
|
+
syncPathText(shape, visible)
|
|
143
|
+
} else {
|
|
144
|
+
syncCenterText(shape, visible)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function createVisibleText(shape, options = {}) {
|
|
149
|
+
const text = shape.attrs.attachedText
|
|
150
|
+
if (!options.allowEmpty && (text === undefined || text === null || text === '')) {
|
|
151
|
+
return null
|
|
152
|
+
}
|
|
153
|
+
if (!shape.getStage()) return null
|
|
154
|
+
const existing = findVisibleNode(shape)
|
|
155
|
+
if (existing) {
|
|
156
|
+
existing.text(text || '')
|
|
157
|
+
syncVisibleNode(shape)
|
|
158
|
+
return existing
|
|
159
|
+
}
|
|
160
|
+
const fontSize = shape.attrs.attachedFontSize || Properties.fontSize
|
|
161
|
+
const fill = shape.stroke() || Properties.strokeColor
|
|
162
|
+
const visible = new Konva.Text({
|
|
163
|
+
text: text || '',
|
|
164
|
+
fontSize,
|
|
165
|
+
fill,
|
|
166
|
+
align: isPathShape(shape) ? 'left' : 'center',
|
|
167
|
+
verticalAlign: isPathShape(shape) ? 'top' : 'middle',
|
|
168
|
+
name: 'attached',
|
|
169
|
+
listening: false,
|
|
170
|
+
strokeEnabled: false
|
|
171
|
+
})
|
|
172
|
+
shape._attachedVisibleNode = visible
|
|
173
|
+
Canvas.layer.add(visible)
|
|
174
|
+
syncVisibleNode(shape)
|
|
175
|
+
raiseTransformerControls()
|
|
176
|
+
return visible
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function raiseTransformerControls() {
|
|
180
|
+
if (!Canvas.layer) return
|
|
181
|
+
const dragArea = Canvas.layer.findOne('.dragArea')
|
|
182
|
+
if (dragArea) dragArea.moveToTop()
|
|
183
|
+
const transformer = Canvas.layer.findOne('Transformer')
|
|
184
|
+
if (transformer) transformer.moveToTop()
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function deleteVisibleNode(shape) {
|
|
188
|
+
const visible = findVisibleNode(shape)
|
|
189
|
+
if (visible) {
|
|
190
|
+
visible.destroy()
|
|
191
|
+
}
|
|
192
|
+
shape._attachedVisibleNode = null
|
|
193
|
+
if (isPathShape(shape)) {
|
|
194
|
+
shape.dashEnabled(false)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function bindShapeEvents(shape) {
|
|
199
|
+
if (!supportsAttachedText(shape)) return
|
|
200
|
+
const events = CHANGE_EVENTS_BY_CLASS[shape.className] || []
|
|
201
|
+
const eventStr = events.map(e => `${e}.attached`).join(' ')
|
|
202
|
+
shape.off('.attached')
|
|
203
|
+
if (eventStr) {
|
|
204
|
+
shape.on(eventStr, () => syncVisibleNode(shape))
|
|
205
|
+
}
|
|
206
|
+
shape.on('dblclick.attached dbltap.attached', () => startEdit(shape))
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function startEdit(shape) {
|
|
210
|
+
if (Canvas.isEditingText) return
|
|
211
|
+
if (!supportsAttachedText(shape)) return
|
|
212
|
+
Canvas.isEditingText = true
|
|
213
|
+
Properties.tool = 'text'
|
|
214
|
+
let visible = findVisibleNode(shape)
|
|
215
|
+
if (!visible) {
|
|
216
|
+
visible = createVisibleText(shape, { allowEmpty: true })
|
|
217
|
+
}
|
|
218
|
+
if (!visible) {
|
|
219
|
+
Canvas.isEditingText = false
|
|
220
|
+
Properties.tool = 'selector'
|
|
221
|
+
return
|
|
222
|
+
}
|
|
223
|
+
Canvas.layer.batchDraw()
|
|
224
|
+
openEditor(shape, visible)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function openEditor(shape, visible) {
|
|
228
|
+
if (!visible) {
|
|
229
|
+
Canvas.isEditingText = false
|
|
230
|
+
Properties.tool = 'selector'
|
|
231
|
+
return
|
|
232
|
+
}
|
|
233
|
+
if (activeEditor) {
|
|
234
|
+
activeEditor.cleanup(false)
|
|
235
|
+
activeEditor = null
|
|
236
|
+
}
|
|
237
|
+
const stage = Canvas.stage
|
|
238
|
+
const originalText = shape.attrs.attachedText || ''
|
|
239
|
+
const fontSize = shape.attrs.attachedFontSize || Properties.fontSize
|
|
240
|
+
const fill = shape.stroke() || Properties.strokeColor
|
|
241
|
+
const containerOffset = {
|
|
242
|
+
x: stage.container().offsetLeft,
|
|
243
|
+
y: stage.container().offsetTop
|
|
244
|
+
}
|
|
245
|
+
const textarea = document.createElement('textarea')
|
|
246
|
+
document.body.appendChild(textarea)
|
|
247
|
+
textarea.value = originalText
|
|
248
|
+
textarea.style.position = 'absolute'
|
|
249
|
+
textarea.style.fontSize = `${fontSize}px`
|
|
250
|
+
textarea.style.lineHeight = '1'
|
|
251
|
+
textarea.style.color = 'transparent'
|
|
252
|
+
textarea.style.caretColor = fill
|
|
253
|
+
textarea.style.fontFamily = visible.fontFamily()
|
|
254
|
+
textarea.style.border = 'none'
|
|
255
|
+
textarea.style.padding = '0'
|
|
256
|
+
textarea.style.margin = '0'
|
|
257
|
+
textarea.style.background = 'transparent'
|
|
258
|
+
textarea.style.outline = 'none'
|
|
259
|
+
textarea.style.resize = 'none'
|
|
260
|
+
textarea.style.textAlign = 'center'
|
|
261
|
+
textarea.style.overflow = 'hidden'
|
|
262
|
+
textarea.style.zIndex = '10000'
|
|
263
|
+
textarea.style.transformOrigin = 'center center'
|
|
264
|
+
textarea.setAttribute('wrap', isPathShape(shape) ? 'off' : 'soft')
|
|
265
|
+
|
|
266
|
+
const state = { closed: false }
|
|
267
|
+
|
|
268
|
+
function applyLiveValue() {
|
|
269
|
+
if (!visible) return
|
|
270
|
+
visible.text(textarea.value)
|
|
271
|
+
syncVisibleNode(shape)
|
|
272
|
+
const w = visible.width() || fontSize
|
|
273
|
+
const h = visible.height() || fontSize
|
|
274
|
+
const cx = visible.x()
|
|
275
|
+
const cy = visible.y()
|
|
276
|
+
textarea.style.width = `${w}px`
|
|
277
|
+
textarea.style.height = `${h}px`
|
|
278
|
+
textarea.style.left = `${containerOffset.x + cx - w / 2}px`
|
|
279
|
+
textarea.style.top = `${containerOffset.y + cy - h / 2}px`
|
|
280
|
+
const rotation = visible.rotation()
|
|
281
|
+
textarea.style.transform = rotation ? `rotate(${rotation}deg)` : ''
|
|
282
|
+
Canvas.layer.batchDraw()
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
applyLiveValue()
|
|
286
|
+
textarea.focus()
|
|
287
|
+
textarea.setSelectionRange(textarea.value.length, textarea.value.length)
|
|
288
|
+
|
|
289
|
+
function keyHandler(event) {
|
|
290
|
+
if (event.key === 'Enter' && !event.shiftKey) {
|
|
291
|
+
event.preventDefault()
|
|
292
|
+
cleanup(true)
|
|
293
|
+
} else if (event.key === 'Escape') {
|
|
294
|
+
event.preventDefault()
|
|
295
|
+
cleanup(false)
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function inputHandler() {
|
|
300
|
+
applyLiveValue()
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function mouseHandler(event) {
|
|
304
|
+
if (event.target !== textarea) {
|
|
305
|
+
cleanup(true)
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function cleanup(commit) {
|
|
310
|
+
if (state.closed) return
|
|
311
|
+
state.closed = true
|
|
312
|
+
textarea.removeEventListener('keydown', keyHandler)
|
|
313
|
+
textarea.removeEventListener('input', inputHandler)
|
|
314
|
+
window.removeEventListener('mousedown', mouseHandler, true)
|
|
315
|
+
if (textarea.parentNode) textarea.parentNode.removeChild(textarea)
|
|
316
|
+
activeEditor = null
|
|
317
|
+
if (commit) {
|
|
318
|
+
const value = textarea.value
|
|
319
|
+
if (value) {
|
|
320
|
+
shape.setAttr('attachedText', value)
|
|
321
|
+
shape.setAttr('attachedFontSize', fontSize)
|
|
322
|
+
const node = findVisibleNode(shape)
|
|
323
|
+
if (node) {
|
|
324
|
+
node.text(value)
|
|
325
|
+
syncVisibleNode(shape)
|
|
326
|
+
} else {
|
|
327
|
+
createVisibleText(shape)
|
|
328
|
+
}
|
|
329
|
+
} else {
|
|
330
|
+
shape.setAttr('attachedText', '')
|
|
331
|
+
deleteVisibleNode(shape)
|
|
332
|
+
}
|
|
333
|
+
} else {
|
|
334
|
+
shape.setAttr('attachedText', originalText)
|
|
335
|
+
const node = findVisibleNode(shape)
|
|
336
|
+
if (originalText) {
|
|
337
|
+
if (node) {
|
|
338
|
+
node.text(originalText)
|
|
339
|
+
syncVisibleNode(shape)
|
|
340
|
+
} else {
|
|
341
|
+
createVisibleText(shape)
|
|
342
|
+
}
|
|
343
|
+
} else if (node) {
|
|
344
|
+
deleteVisibleNode(shape)
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
Canvas.isEditingText = false
|
|
348
|
+
Properties.tool = 'selector'
|
|
349
|
+
Canvas.layer.batchDraw()
|
|
350
|
+
if (commit) Histories.record()
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
textarea.addEventListener('keydown', keyHandler)
|
|
354
|
+
textarea.addEventListener('input', inputHandler)
|
|
355
|
+
setTimeout(() => {
|
|
356
|
+
if (!state.closed) {
|
|
357
|
+
window.addEventListener('mousedown', mouseHandler, true)
|
|
358
|
+
}
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
activeEditor = { cleanup }
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function handleNodeAdded(event) {
|
|
365
|
+
const node = event.child
|
|
366
|
+
if (!node || !node.attrs) return
|
|
367
|
+
if (!supportsAttachedText(node)) return
|
|
368
|
+
if (node.attrs.attachedText && node.getStage()) {
|
|
369
|
+
createVisibleText(node)
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function attachLayerListeners(layer) {
|
|
374
|
+
if (!layer || layer._attachedLayerHooked) return
|
|
375
|
+
layer._attachedLayerHooked = true
|
|
376
|
+
layer.on('add', handleNodeAdded)
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export default {
|
|
380
|
+
bindShapeEvents,
|
|
381
|
+
syncVisibleNode,
|
|
382
|
+
createVisibleText,
|
|
383
|
+
deleteVisibleNode,
|
|
384
|
+
startEdit,
|
|
385
|
+
attachLayerListeners
|
|
386
|
+
}
|
|
@@ -3,6 +3,7 @@ import Properties from '../properties'
|
|
|
3
3
|
import Canvas from '../canvas'
|
|
4
4
|
import Histories from '../histories'
|
|
5
5
|
import AngleSnap from './helper/angleSnap'
|
|
6
|
+
import AttachedText from './helper/attachedText'
|
|
6
7
|
|
|
7
8
|
export default {
|
|
8
9
|
temporalShape: null,
|
|
@@ -41,15 +42,20 @@ export default {
|
|
|
41
42
|
}
|
|
42
43
|
this.temporalShape = null
|
|
43
44
|
},
|
|
44
|
-
generate({ x, y, scaleX, scaleY, points, stroke, strokeWidth }) {
|
|
45
|
+
generate({ x, y, scaleX, scaleY, skewX, rotation, points, stroke, strokeWidth, attachedText, attachedFontSize, attachedFill }) {
|
|
45
46
|
const newLine = new Konva.Line({
|
|
46
47
|
x,
|
|
47
48
|
y,
|
|
48
49
|
scaleX,
|
|
49
50
|
scaleY,
|
|
51
|
+
skewX,
|
|
52
|
+
rotation,
|
|
50
53
|
points,
|
|
51
54
|
stroke,
|
|
52
55
|
strokeWidth,
|
|
56
|
+
attachedText,
|
|
57
|
+
attachedFontSize,
|
|
58
|
+
attachedFill,
|
|
53
59
|
name: 'node',
|
|
54
60
|
nodeType: 'line',
|
|
55
61
|
lineCap: 'round',
|
|
@@ -64,5 +70,6 @@ export default {
|
|
|
64
70
|
newLine.off('dragend')
|
|
65
71
|
newLine.on('transformend', Histories.record)
|
|
66
72
|
newLine.on('dragend', Histories.record)
|
|
73
|
+
AttachedText.bindShapeEvents(newLine)
|
|
67
74
|
}
|
|
68
75
|
}
|
|
@@ -77,7 +77,7 @@ export default {
|
|
|
77
77
|
this.temporalShape = null
|
|
78
78
|
},
|
|
79
79
|
|
|
80
|
-
generate({ points, widths, stroke, x, y, width, height }) {
|
|
80
|
+
generate({ points, widths, stroke, x, y, width, height, scaleX, scaleY, skewX, rotation }) {
|
|
81
81
|
const tool = this
|
|
82
82
|
|
|
83
83
|
const shape = new Konva.Shape({
|
|
@@ -85,6 +85,10 @@ export default {
|
|
|
85
85
|
y: y || 0,
|
|
86
86
|
width: width || 0,
|
|
87
87
|
height: height || 0,
|
|
88
|
+
scaleX,
|
|
89
|
+
scaleY,
|
|
90
|
+
skewX,
|
|
91
|
+
rotation,
|
|
88
92
|
stroke,
|
|
89
93
|
points,
|
|
90
94
|
widths,
|
|
@@ -3,6 +3,7 @@ import Properties from '../properties'
|
|
|
3
3
|
import Canvas from '../canvas'
|
|
4
4
|
import Histories from '../histories'
|
|
5
5
|
import ShapeConstraint from './helper/shapeConstraint'
|
|
6
|
+
import AttachedText from './helper/attachedText'
|
|
6
7
|
|
|
7
8
|
export default {
|
|
8
9
|
temporalShape: null,
|
|
@@ -53,14 +54,15 @@ export default {
|
|
|
53
54
|
if (!this.temporalShape && newRectangle.width() < 5 && newRectangle.height() < 5) {
|
|
54
55
|
newRectangle.width(30)
|
|
55
56
|
newRectangle.height(30)
|
|
57
|
+
Histories.record()
|
|
56
58
|
}
|
|
57
59
|
}, 200)
|
|
58
60
|
},
|
|
59
|
-
generate({ x, y, scaleX, scaleY, width, height, skewX, rotation, fill, stroke, strokeWidth }) {
|
|
61
|
+
generate({ x, y, scaleX, scaleY, width, height, skewX, rotation, fill, stroke, strokeWidth, attachedText, attachedFontSize, attachedFill }) {
|
|
60
62
|
const newRectangle = new Konva.Rect({
|
|
61
63
|
x,
|
|
62
64
|
y,
|
|
63
|
-
scaleX,
|
|
65
|
+
scaleX,
|
|
64
66
|
scaleY,
|
|
65
67
|
width,
|
|
66
68
|
height,
|
|
@@ -69,6 +71,9 @@ export default {
|
|
|
69
71
|
fill,
|
|
70
72
|
stroke,
|
|
71
73
|
strokeWidth,
|
|
74
|
+
attachedText,
|
|
75
|
+
attachedFontSize,
|
|
76
|
+
attachedFill,
|
|
72
77
|
name: 'node',
|
|
73
78
|
nodeType: 'rectangle',
|
|
74
79
|
strokeScaleEnabled: false
|
|
@@ -81,5 +86,6 @@ export default {
|
|
|
81
86
|
newRectangle.off('dragend')
|
|
82
87
|
newRectangle.on('transformend', Histories.record)
|
|
83
88
|
newRectangle.on('dragend', Histories.record)
|
|
89
|
+
AttachedText.bindShapeEvents(newRectangle)
|
|
84
90
|
}
|
|
85
91
|
}
|
|
@@ -60,111 +60,79 @@ export default {
|
|
|
60
60
|
Canvas.isEditingText = true;
|
|
61
61
|
Properties.tool = 'text'
|
|
62
62
|
this.temporalText = newText;
|
|
63
|
-
newText.hide();
|
|
64
63
|
Transformer.deselectAllNodes();
|
|
65
64
|
|
|
66
|
-
const textPosition = newText.absolutePosition();
|
|
67
|
-
const areaPosition = {
|
|
68
|
-
x: Canvas.stage.container().offsetLeft + textPosition.x,
|
|
69
|
-
y: Canvas.stage.container().offsetTop + textPosition.y
|
|
70
|
-
};
|
|
71
|
-
|
|
72
65
|
if (this.textarea) {
|
|
73
66
|
this.textarea.removeEventListener('keydown', this.textareaEditingHandler);
|
|
67
|
+
if (this.textarea.parentNode) {
|
|
68
|
+
this.textarea.parentNode.removeChild(this.textarea);
|
|
69
|
+
}
|
|
74
70
|
}
|
|
75
71
|
|
|
76
72
|
this.textarea = document.createElement('textarea');
|
|
77
73
|
document.body.appendChild(this.textarea);
|
|
78
|
-
|
|
74
|
+
|
|
79
75
|
this.textarea.value = newText.text();
|
|
80
76
|
this.textarea.style.position = 'absolute';
|
|
81
|
-
this.textarea.style.top = `${areaPosition.y}px`;
|
|
82
|
-
this.textarea.style.left = `${areaPosition.x}px`;
|
|
83
|
-
this.textarea.style.width = `${newText.width() - newText.padding() * 2}px`;
|
|
84
|
-
this.textarea.style.height =
|
|
85
|
-
`${ newText.height() - newText.padding() * 2 + 5 }px`;
|
|
86
77
|
this.textarea.style.fontSize = `${ newText.fontSize() }px`;
|
|
87
78
|
this.textarea.style.border = 'none';
|
|
88
79
|
this.textarea.style.padding = '0';
|
|
89
80
|
this.textarea.style.margin = '0';
|
|
90
81
|
this.textarea.style.overflow = 'hidden';
|
|
91
|
-
this.textarea.style.background = '
|
|
82
|
+
this.textarea.style.background = 'transparent';
|
|
92
83
|
this.textarea.style.outline = 'none';
|
|
93
84
|
this.textarea.style.resize = 'none';
|
|
94
85
|
this.textarea.style.lineHeight = newText.lineHeight();
|
|
95
86
|
this.textarea.style.fontFamily = newText.fontFamily();
|
|
96
|
-
this.textarea.style.transformOrigin = 'left top';
|
|
97
87
|
this.textarea.style.textAlign = newText.align();
|
|
98
|
-
this.textarea.style.color =
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
transform += `rotateZ(${ rotation }deg)`;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
let px = 0;
|
|
106
|
-
|
|
107
|
-
const isFirefox =
|
|
108
|
-
navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
|
|
109
|
-
if (isFirefox) {
|
|
110
|
-
px += 2 + Math.round(newText.fontSize() / 20);
|
|
111
|
-
}
|
|
112
|
-
transform += `translateY(-${ px }px)`;
|
|
113
|
-
|
|
114
|
-
this.textarea.style.transform = transform;
|
|
115
|
-
|
|
116
|
-
this.textarea.style.height = 'auto';
|
|
117
|
-
this.textarea.style.height = `${this.textarea.scrollHeight + 3}px`;
|
|
88
|
+
this.textarea.style.color = 'transparent';
|
|
89
|
+
this.textarea.style.caretColor = newText.fill();
|
|
90
|
+
this.textarea.style.transformOrigin = 'left top';
|
|
91
|
+
this.textarea.style.zIndex = '10000';
|
|
118
92
|
|
|
93
|
+
this._syncTextareaToText(newText);
|
|
119
94
|
this.textarea.focus();
|
|
95
|
+
this.textarea.setSelectionRange(this.textarea.value.length, this.textarea.value.length);
|
|
120
96
|
|
|
121
97
|
this.textareaEditingHandler = this._handleTextareaEditing.bind(this);
|
|
122
98
|
this.disableTextareaHandler = this._handleDisableTextarea.bind(this);
|
|
123
99
|
this.textarea.addEventListener('keydown', this.textareaEditingHandler);
|
|
100
|
+
this.textarea.addEventListener('input', this.textareaEditingHandler);
|
|
124
101
|
setTimeout(() => {
|
|
125
102
|
window.addEventListener('mousedown', this.disableTextareaHandler);
|
|
126
103
|
});
|
|
127
104
|
},
|
|
105
|
+
_syncTextareaToText(newText) {
|
|
106
|
+
const textPosition = newText.absolutePosition();
|
|
107
|
+
const areaPosition = {
|
|
108
|
+
x: Canvas.stage.container().offsetLeft + textPosition.x,
|
|
109
|
+
y: Canvas.stage.container().offsetTop + textPosition.y
|
|
110
|
+
};
|
|
111
|
+
this.textarea.style.top = `${areaPosition.y}px`;
|
|
112
|
+
this.textarea.style.left = `${areaPosition.x}px`;
|
|
113
|
+
this.textarea.style.width = `${newText.width() - newText.padding() * 2}px`;
|
|
114
|
+
this.textarea.style.height = `${newText.height() - newText.padding() * 2}px`;
|
|
115
|
+
const rotation = newText.rotation();
|
|
116
|
+
this.textarea.style.transform = rotation ? `rotateZ(${rotation}deg)` : '';
|
|
117
|
+
},
|
|
128
118
|
_handleDisableTextarea(event) {
|
|
129
119
|
if (event.target !== this.textarea) {
|
|
130
120
|
if (this.textarea.value) {
|
|
131
121
|
this.temporalText.text(this.textarea.value)
|
|
132
|
-
this.temporalText.show()
|
|
133
122
|
this.textarea.remove()
|
|
134
123
|
} else {
|
|
135
124
|
this.textarea.remove()
|
|
136
125
|
this.temporalText.remove()
|
|
137
126
|
}
|
|
138
127
|
Properties.tool = 'selector'
|
|
128
|
+
Canvas.isEditingText = false
|
|
139
129
|
window.removeEventListener('mousedown', this.disableTextareaHandler)
|
|
140
130
|
}
|
|
141
131
|
},
|
|
142
132
|
_handleTextareaEditing() {
|
|
143
|
-
|
|
144
|
-
this.
|
|
145
|
-
|
|
146
|
-
this.textarea.style.height =
|
|
147
|
-
`${this.textarea.scrollHeight + this.temporalText.fontSize() }px`;
|
|
148
|
-
},
|
|
149
|
-
_setTextareaWidth(newWidth) {
|
|
150
|
-
if (!newWidth) {
|
|
151
|
-
newWidth = this.textNode.placeholder.length * this.temporalText.fontSize();
|
|
152
|
-
}
|
|
153
|
-
const isSafari = /^((?!chrome|android).)*safari/i.test(
|
|
154
|
-
navigator.userAgent
|
|
155
|
-
);
|
|
156
|
-
const isFirefox =
|
|
157
|
-
navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
|
|
158
|
-
if (isSafari || isFirefox) {
|
|
159
|
-
newWidth = Math.ceil(newWidth);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const isEdge =
|
|
163
|
-
document.documentMode || /Edge/.test(navigator.userAgent);
|
|
164
|
-
if (isEdge) {
|
|
165
|
-
newWidth += 1;
|
|
166
|
-
}
|
|
167
|
-
this.textarea.style.width = `${ newWidth }px`;
|
|
133
|
+
this.temporalText.text(this.textarea.value);
|
|
134
|
+
this._syncTextareaToText(this.temporalText);
|
|
135
|
+
Canvas.layer.batchDraw();
|
|
168
136
|
},
|
|
169
137
|
generate({ text, x, y, fill, fontSize, width, height, scaleX, scaleY, skewX, rotation }) {
|
|
170
138
|
const newText = new Konva.Text({
|
|
@@ -2,6 +2,7 @@ import Konva from 'konva'
|
|
|
2
2
|
import Canvas from './canvas'
|
|
3
3
|
import Properties from './properties'
|
|
4
4
|
import Histories from './histories'
|
|
5
|
+
import AttachedText from './tools/helper/attachedText'
|
|
5
6
|
export default {
|
|
6
7
|
transformer: null,
|
|
7
8
|
linearTransformer: {
|
|
@@ -11,14 +12,21 @@ export default {
|
|
|
11
12
|
},
|
|
12
13
|
initialize() {
|
|
13
14
|
this.transformer = new Konva.Transformer();
|
|
14
|
-
|
|
15
|
+
|
|
15
16
|
// 创建一个透明的拖动区域
|
|
16
17
|
this.dragArea = new Konva.Rect({
|
|
18
|
+
name: 'dragArea',
|
|
17
19
|
fill: 'transparent',
|
|
18
20
|
draggable: true,
|
|
19
21
|
listening: true
|
|
20
22
|
});
|
|
21
|
-
|
|
23
|
+
this.dragArea.on('dblclick dbltap', (event) => {
|
|
24
|
+
const nodes = this._nodes();
|
|
25
|
+
if (nodes.length === 1) {
|
|
26
|
+
nodes[0]._fire('dblclick', { evt: event.evt, target: nodes[0] });
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
22
30
|
Canvas.layer.add(this.dragArea);
|
|
23
31
|
Canvas.layer.add(this.transformer);
|
|
24
32
|
},
|
|
@@ -27,8 +35,16 @@ export default {
|
|
|
27
35
|
return selectedNodes;
|
|
28
36
|
},
|
|
29
37
|
removeSelectedNodes() {
|
|
30
|
-
this._nodes()
|
|
38
|
+
const nodes = this._nodes();
|
|
39
|
+
if (nodes.length === 0) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
nodes.forEach((node) => {
|
|
43
|
+
AttachedText.deleteVisibleNode(node);
|
|
44
|
+
node.destroy();
|
|
45
|
+
});
|
|
31
46
|
this.deselectAllNodes();
|
|
47
|
+
Histories.record();
|
|
32
48
|
},
|
|
33
49
|
setPropertiesOfSelectedNodes(properties) {
|
|
34
50
|
const selectedNodes = this._nodes().slice();
|
|
@@ -279,6 +295,7 @@ export default {
|
|
|
279
295
|
});
|
|
280
296
|
|
|
281
297
|
this.dragArea.off('dragmove');
|
|
298
|
+
this.dragArea.off('dragend');
|
|
282
299
|
this.dragArea.on('dragmove', () => {
|
|
283
300
|
const dx = this.dragArea.x() - box.x;
|
|
284
301
|
const dy = this.dragArea.y() - box.y;
|
|
@@ -289,6 +306,7 @@ export default {
|
|
|
289
306
|
box.x = this.dragArea.x();
|
|
290
307
|
box.y = this.dragArea.y();
|
|
291
308
|
});
|
|
309
|
+
this.dragArea.on('dragend', () => Histories.record());
|
|
292
310
|
},
|
|
293
311
|
_updateTransformerAnchors(targetNodes) {
|
|
294
312
|
if (targetNodes.length === 1) {
|