jellies-draw 0.2.4 → 0.3.0
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 +1 -1
- package/src/components/DrawingCanvas.vue +2 -1
- package/src/components/ToolButton.vue +11 -1
- package/src/components/ToolButtons.vue +31 -17
- package/src/components/functions/canvas.js +45 -5
- package/src/components/functions/clipboard.js +38 -2
- package/src/components/functions/laser.js +188 -0
- package/src/components/functions/properties.js +11 -5
- package/src/components/functions/tools/arrow.js +56 -14
- package/src/components/functions/tools/ellipse.js +10 -1
- package/src/components/functions/tools/eraser.js +5 -1
- package/src/components/functions/tools/helper/attachedText.js +442 -0
- package/src/components/functions/tools/helper/connection.js +381 -0
- package/src/components/functions/tools/index.js +3 -1
- package/src/components/functions/tools/laser.js +5 -0
- package/src/components/functions/tools/line.js +56 -14
- package/src/components/functions/tools/rectangle.js +11 -2
- package/src/components/functions/tools/text.js +29 -61
- package/src/components/functions/transformer.js +56 -12
package/package.json
CHANGED
|
@@ -26,7 +26,7 @@ export default {
|
|
|
26
26
|
default: 'selector'
|
|
27
27
|
},
|
|
28
28
|
shortCut: {
|
|
29
|
-
type: Number,
|
|
29
|
+
type: [Number, String],
|
|
30
30
|
default: null
|
|
31
31
|
},
|
|
32
32
|
isActive: {
|
|
@@ -141,4 +141,14 @@ button {
|
|
|
141
141
|
.tool-clear:before {
|
|
142
142
|
content: "\e7a9";
|
|
143
143
|
}
|
|
144
|
+
.tool-laser:before {
|
|
145
|
+
content: "";
|
|
146
|
+
display: inline-block;
|
|
147
|
+
width: 8px;
|
|
148
|
+
height: 8px;
|
|
149
|
+
border-radius: 50%;
|
|
150
|
+
background: #f33b29;
|
|
151
|
+
box-shadow: 0 0 4px #f33b29, 0 0 8px rgba(243, 59, 41, 0.6);
|
|
152
|
+
vertical-align: middle;
|
|
153
|
+
}
|
|
144
154
|
</style>
|
|
@@ -42,6 +42,7 @@ export default {
|
|
|
42
42
|
data() {
|
|
43
43
|
return {
|
|
44
44
|
tools: [
|
|
45
|
+
'laser',
|
|
45
46
|
'selector',
|
|
46
47
|
'rectangle',
|
|
47
48
|
'ellipse',
|
|
@@ -63,7 +64,8 @@ export default {
|
|
|
63
64
|
toolsCanBeUsedContinuously: [
|
|
64
65
|
'pen',
|
|
65
66
|
'text',
|
|
66
|
-
'eraser'
|
|
67
|
+
'eraser',
|
|
68
|
+
'laser'
|
|
67
69
|
]
|
|
68
70
|
}
|
|
69
71
|
},
|
|
@@ -92,13 +94,8 @@ export default {
|
|
|
92
94
|
Properties.latestTool = newTool;
|
|
93
95
|
}
|
|
94
96
|
},
|
|
95
|
-
isCanvasPenetrable
|
|
96
|
-
|
|
97
|
-
return Properties.isCanvasPenetrable;
|
|
98
|
-
},
|
|
99
|
-
set(isCanvasPenetrable) {
|
|
100
|
-
Properties.isCanvasPenetrable = isCanvasPenetrable;
|
|
101
|
-
}
|
|
97
|
+
isCanvasPenetrable() {
|
|
98
|
+
return Properties.isCanvasPenetrable;
|
|
102
99
|
}
|
|
103
100
|
},
|
|
104
101
|
watch: {
|
|
@@ -108,7 +105,10 @@ export default {
|
|
|
108
105
|
},
|
|
109
106
|
methods: {
|
|
110
107
|
toolShortCut(index) {
|
|
111
|
-
|
|
108
|
+
if (Properties.isUsingText) return null
|
|
109
|
+
if (this.tools[index] === 'laser') return null
|
|
110
|
+
if (index === 10) return 0
|
|
111
|
+
return index
|
|
112
112
|
},
|
|
113
113
|
applyAction(action) {
|
|
114
114
|
if (action === 'image') {
|
|
@@ -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 = {
|
|
@@ -130,7 +131,6 @@ export default {
|
|
|
130
131
|
}
|
|
131
132
|
this.isCurrentToolLocked = this.toolsCanBeLocked.includes(action) && this.currentTool === action
|
|
132
133
|
this.currentTool = action
|
|
133
|
-
this.isCanvasPenetrable = this.currentTool === 'selector' && this.isCurrentToolLocked
|
|
134
134
|
this.releaseInstantTool()
|
|
135
135
|
},
|
|
136
136
|
releaseInstantTool() {
|
|
@@ -147,12 +147,26 @@ export default {
|
|
|
147
147
|
event.target.value = ''
|
|
148
148
|
},
|
|
149
149
|
handleShortCutKey(event) {
|
|
150
|
-
if (this.hasShortCuts
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
150
|
+
if (!this.hasShortCuts) return
|
|
151
|
+
if (this.currentTool === 'text') return
|
|
152
|
+
const tag = document.activeElement && document.activeElement.tagName
|
|
153
|
+
if (tag === 'INPUT' || tag === 'TEXTAREA' || (document.activeElement && document.activeElement.isContentEditable)) return
|
|
154
|
+
if (event.key === '`') {
|
|
155
|
+
if (this.currentTool === 'laser') {
|
|
156
|
+
const fallback = this.latestTool.tool === 'laser' ? 'pen' : this.latestTool.tool
|
|
157
|
+
this.applyAction(fallback)
|
|
158
|
+
} else {
|
|
159
|
+
this.applyAction('laser')
|
|
155
160
|
}
|
|
161
|
+
return
|
|
162
|
+
}
|
|
163
|
+
if (event.altKey || event.ctrlKey || event.metaKey) return
|
|
164
|
+
const key = event.key
|
|
165
|
+
const index = parseInt(key, 10)
|
|
166
|
+
if (isNaN(index)) return
|
|
167
|
+
const targetIdx = index === 0 ? 10 : index
|
|
168
|
+
if (targetIdx < this.tools.length) {
|
|
169
|
+
this.applyAction(this.tools[targetIdx])
|
|
156
170
|
}
|
|
157
171
|
}
|
|
158
172
|
}
|
|
@@ -4,6 +4,9 @@ import Clipboard from './clipboard';
|
|
|
4
4
|
import Properties from './properties';
|
|
5
5
|
import Tools from './tools';
|
|
6
6
|
import Histories from './histories';
|
|
7
|
+
import Laser from './laser';
|
|
8
|
+
import AttachedText from './tools/helper/attachedText';
|
|
9
|
+
import Connection from './tools/helper/connection';
|
|
7
10
|
export default {
|
|
8
11
|
layer: null,
|
|
9
12
|
stage: null,
|
|
@@ -29,6 +32,16 @@ export default {
|
|
|
29
32
|
});
|
|
30
33
|
this.layer = new Konva.Layer();
|
|
31
34
|
this.stage.add(this.layer);
|
|
35
|
+
Laser.initialize(this.stage, this.container);
|
|
36
|
+
this.unwatchLaser = Properties.$watch(
|
|
37
|
+
() => Properties.tool === 'laser',
|
|
38
|
+
(isLaserMode) => {
|
|
39
|
+
if (isLaserMode) Laser.activate();
|
|
40
|
+
else Laser.deactivate();
|
|
41
|
+
},
|
|
42
|
+
{ immediate: true }
|
|
43
|
+
);
|
|
44
|
+
AttachedText.attachLayerListeners(this.layer);
|
|
32
45
|
document.addEventListener('keydown', this.keydownHandler);
|
|
33
46
|
document.addEventListener('keyup', this.keyupHandler);
|
|
34
47
|
Transformer.initialize();
|
|
@@ -41,6 +54,10 @@ export default {
|
|
|
41
54
|
window.addEventListener('pointerup', this.handleGlobalPointerUp, { once: true });
|
|
42
55
|
});
|
|
43
56
|
this.stage.on('mousemove', this._handleMouseMove.bind(this));
|
|
57
|
+
this.containerLeaveHandler = () => {
|
|
58
|
+
if (!this.isDrawing) Connection.hideSnapVisuals();
|
|
59
|
+
};
|
|
60
|
+
this.stage.container().addEventListener('mouseleave', this.containerLeaveHandler);
|
|
44
61
|
},
|
|
45
62
|
generateEventHandlers() {
|
|
46
63
|
this.resizeHandler = this._handleResize.bind(this);
|
|
@@ -72,6 +89,15 @@ export default {
|
|
|
72
89
|
this.stage.off('click tap', this.clickHandler);
|
|
73
90
|
document.removeEventListener('keydown', this.keydownHandler);
|
|
74
91
|
document.removeEventListener('keyup', this.keyupHandler);
|
|
92
|
+
if (this.containerLeaveHandler) {
|
|
93
|
+
this.stage.container().removeEventListener('mouseleave', this.containerLeaveHandler);
|
|
94
|
+
}
|
|
95
|
+
if (this.unwatchLaser) {
|
|
96
|
+
this.unwatchLaser();
|
|
97
|
+
this.unwatchLaser = null;
|
|
98
|
+
}
|
|
99
|
+
Laser.destroy();
|
|
100
|
+
Connection.hideSnapVisuals();
|
|
75
101
|
this.stage.clear()
|
|
76
102
|
this.stage = null;
|
|
77
103
|
window.removeEventListener('resize', this.resizeHandler);
|
|
@@ -100,12 +126,12 @@ export default {
|
|
|
100
126
|
this._handlePropertiesShortCuts(event);
|
|
101
127
|
}
|
|
102
128
|
} else if (event.altKey) {
|
|
103
|
-
Properties.
|
|
129
|
+
Properties.isAltHeld = true;
|
|
104
130
|
}
|
|
105
131
|
},
|
|
106
132
|
_handleKeyup(event) {
|
|
107
133
|
if (event.key === 'Alt') {
|
|
108
|
-
Properties.
|
|
134
|
+
Properties.isAltHeld = false;
|
|
109
135
|
if (Properties.tool === 'selector') {
|
|
110
136
|
Properties.isToolLocked = false;
|
|
111
137
|
Properties.latestTool = {
|
|
@@ -195,11 +221,14 @@ export default {
|
|
|
195
221
|
},
|
|
196
222
|
_handleMouseMove(e) {
|
|
197
223
|
if (Properties.isUsingDrawingTool || Properties.isUsingText) {
|
|
224
|
+
if (Properties.tool === 'line' || Properties.tool === 'arrow') {
|
|
225
|
+
this._handleSnapHoverPreview();
|
|
226
|
+
}
|
|
198
227
|
return;
|
|
199
228
|
}
|
|
200
229
|
|
|
201
230
|
const target = e.target;
|
|
202
|
-
|
|
231
|
+
|
|
203
232
|
if (target === this.stage) {
|
|
204
233
|
this.stage.container().style.cursor = 'default';
|
|
205
234
|
return;
|
|
@@ -208,9 +237,9 @@ export default {
|
|
|
208
237
|
if (target.hasName('node')) {
|
|
209
238
|
const mousePos = this.stage.getPointerPosition();
|
|
210
239
|
const canInteract = this._canInteractWithNode(target, mousePos);
|
|
211
|
-
|
|
240
|
+
|
|
212
241
|
this.stage.container().style.cursor = canInteract ? 'move' : 'default';
|
|
213
|
-
|
|
242
|
+
|
|
214
243
|
if (!canInteract) {
|
|
215
244
|
target.draggable(false);
|
|
216
245
|
} else if (!Properties.isUsingDrawingTool) {
|
|
@@ -218,6 +247,17 @@ export default {
|
|
|
218
247
|
}
|
|
219
248
|
}
|
|
220
249
|
},
|
|
250
|
+
_handleSnapHoverPreview() {
|
|
251
|
+
if (this.isDrawing) return;
|
|
252
|
+
const pos = this.stage.getPointerPosition();
|
|
253
|
+
if (!pos) return;
|
|
254
|
+
const target = Connection.findSnapTarget(pos);
|
|
255
|
+
if (target) {
|
|
256
|
+
Connection.showSnapVisuals(target);
|
|
257
|
+
} else {
|
|
258
|
+
Connection.hideSnapVisuals();
|
|
259
|
+
}
|
|
260
|
+
},
|
|
221
261
|
_canInteractWithNode(node, mousePos) {
|
|
222
262
|
const selectedNodes = Transformer._nodes();
|
|
223
263
|
if (selectedNodes.includes(node)) {
|
|
@@ -2,6 +2,7 @@ import Canvas from './canvas';
|
|
|
2
2
|
import Transformer from './transformer';
|
|
3
3
|
import Tools from './tools';
|
|
4
4
|
import Histories from './histories';
|
|
5
|
+
import Connection from './tools/helper/connection';
|
|
5
6
|
export default {
|
|
6
7
|
copiedNodes: [],
|
|
7
8
|
copy() {
|
|
@@ -13,6 +14,21 @@ export default {
|
|
|
13
14
|
},
|
|
14
15
|
paste() {
|
|
15
16
|
if (this.copiedNodes && this.copiedNodes.length > 0) {
|
|
17
|
+
const idMap = {};
|
|
18
|
+
this.copiedNodes.forEach((node) => {
|
|
19
|
+
if (node.attrs.nodeId) {
|
|
20
|
+
const oldId = node.attrs.nodeId;
|
|
21
|
+
node.setAttr('nodeId', undefined);
|
|
22
|
+
Connection.ensureNodeId(node);
|
|
23
|
+
idMap[oldId] = node.attrs.nodeId;
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
this.copiedNodes.forEach((node) => {
|
|
27
|
+
if (node.className === 'Line' || node.className === 'Arrow') {
|
|
28
|
+
this._remapLineAttachment(node, 'start', idMap);
|
|
29
|
+
this._remapLineAttachment(node, 'end', idMap);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
16
32
|
this.copiedNodes.forEach((node) => {
|
|
17
33
|
node.x(node.x() + 10);
|
|
18
34
|
node.y(node.y() + 10);
|
|
@@ -24,11 +40,31 @@ export default {
|
|
|
24
40
|
Histories.record();
|
|
25
41
|
}
|
|
26
42
|
},
|
|
43
|
+
_remapLineAttachment(line, end, idMap) {
|
|
44
|
+
const oldId = line.attrs[`${end}AttachedTo`];
|
|
45
|
+
if (!oldId) return;
|
|
46
|
+
if (idMap[oldId]) {
|
|
47
|
+
line.setAttr(`${end}AttachedTo`, idMap[oldId]);
|
|
48
|
+
} else {
|
|
49
|
+
line.setAttr(`${end}AttachedTo`, undefined);
|
|
50
|
+
line.setAttr(`${end}AttachedLocalX`, undefined);
|
|
51
|
+
line.setAttr(`${end}AttachedLocalY`, undefined);
|
|
52
|
+
}
|
|
53
|
+
},
|
|
27
54
|
_addEventsToCopiedNode(node) {
|
|
28
|
-
|
|
55
|
+
const className = node.getClassName();
|
|
56
|
+
if (className === 'Text') {
|
|
29
57
|
Tools.text.bindEvents(node);
|
|
30
|
-
} else if (
|
|
58
|
+
} else if (className === 'Arrow') {
|
|
31
59
|
Tools.arrow.bindEvents(node);
|
|
60
|
+
} else if (className === 'Rect') {
|
|
61
|
+
Tools.rectangle.bindEvents(node);
|
|
62
|
+
} else if (className === 'Ellipse') {
|
|
63
|
+
Tools.ellipse.bindEvents(node);
|
|
64
|
+
} else if (className === 'Line') {
|
|
65
|
+
Tools.line.bindEvents(node);
|
|
66
|
+
} else if (className === 'Shape' && node.attrs.nodeType === 'pen') {
|
|
67
|
+
Tools.pen.bindEvents(node);
|
|
32
68
|
}
|
|
33
69
|
}
|
|
34
70
|
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import Konva from 'konva';
|
|
2
|
+
|
|
3
|
+
const FADE_DURATION = 700;
|
|
4
|
+
const LASER_WIDTH = 4;
|
|
5
|
+
const DRAG_THRESHOLD = 2;
|
|
6
|
+
const GLOW_COLOR = '243, 59, 41';
|
|
7
|
+
const CORE_COLOR = '255, 110, 90';
|
|
8
|
+
|
|
9
|
+
export default {
|
|
10
|
+
stage: null,
|
|
11
|
+
container: null,
|
|
12
|
+
layer: null,
|
|
13
|
+
shape: null,
|
|
14
|
+
segments: [],
|
|
15
|
+
isPressed: false,
|
|
16
|
+
hasDragged: false,
|
|
17
|
+
startX: 0,
|
|
18
|
+
startY: 0,
|
|
19
|
+
lastX: 0,
|
|
20
|
+
lastY: 0,
|
|
21
|
+
rafId: null,
|
|
22
|
+
active: false,
|
|
23
|
+
_onMouseDown: null,
|
|
24
|
+
_onMouseMove: null,
|
|
25
|
+
_onMouseUp: null,
|
|
26
|
+
_onTick: null,
|
|
27
|
+
|
|
28
|
+
initialize(stage, container) {
|
|
29
|
+
this.stage = stage;
|
|
30
|
+
this.container = container;
|
|
31
|
+
this._onMouseDown = this._handleMouseDown.bind(this);
|
|
32
|
+
this._onMouseMove = this._handleMouseMove.bind(this);
|
|
33
|
+
this._onMouseUp = this._handleMouseUp.bind(this);
|
|
34
|
+
this._onTick = this._tick.bind(this);
|
|
35
|
+
this.layer = new Konva.Layer({ listening: false });
|
|
36
|
+
this.shape = new Konva.Shape({
|
|
37
|
+
listening: false,
|
|
38
|
+
sceneFunc: (ctx) => this._renderSegments(ctx)
|
|
39
|
+
});
|
|
40
|
+
this.layer.add(this.shape);
|
|
41
|
+
this.layer.hide();
|
|
42
|
+
stage.add(this.layer);
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
destroy() {
|
|
46
|
+
this.deactivate();
|
|
47
|
+
if (this.layer) {
|
|
48
|
+
this.layer.destroy();
|
|
49
|
+
this.layer = null;
|
|
50
|
+
this.shape = null;
|
|
51
|
+
}
|
|
52
|
+
this.stage = null;
|
|
53
|
+
this.container = null;
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
activate() {
|
|
57
|
+
if (this.active || !this.layer) return;
|
|
58
|
+
this.active = true;
|
|
59
|
+
this.layer.show();
|
|
60
|
+
this.layer.moveToTop();
|
|
61
|
+
window.addEventListener('pointerdown', this._onMouseDown, true);
|
|
62
|
+
window.addEventListener('pointermove', this._onMouseMove, true);
|
|
63
|
+
window.addEventListener('pointerup', this._onMouseUp, true);
|
|
64
|
+
window.addEventListener('pointercancel', this._onMouseUp, true);
|
|
65
|
+
this.rafId = requestAnimationFrame(this._onTick);
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
deactivate() {
|
|
69
|
+
if (!this.active) return;
|
|
70
|
+
this.active = false;
|
|
71
|
+
window.removeEventListener('pointerdown', this._onMouseDown, true);
|
|
72
|
+
window.removeEventListener('pointermove', this._onMouseMove, true);
|
|
73
|
+
window.removeEventListener('pointerup', this._onMouseUp, true);
|
|
74
|
+
window.removeEventListener('pointercancel', this._onMouseUp, true);
|
|
75
|
+
if (this.rafId !== null) {
|
|
76
|
+
cancelAnimationFrame(this.rafId);
|
|
77
|
+
this.rafId = null;
|
|
78
|
+
}
|
|
79
|
+
this.segments = [];
|
|
80
|
+
this.isPressed = false;
|
|
81
|
+
this.hasDragged = false;
|
|
82
|
+
if (this.layer) {
|
|
83
|
+
this.layer.hide();
|
|
84
|
+
this.layer.batchDraw();
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
_isWithinContainer(clientX, clientY) {
|
|
89
|
+
if (!this.container) return false;
|
|
90
|
+
const rect = this.container.getBoundingClientRect();
|
|
91
|
+
if (rect.width === 0 || rect.height === 0) return false;
|
|
92
|
+
return clientX >= rect.left && clientX <= rect.right &&
|
|
93
|
+
clientY >= rect.top && clientY <= rect.bottom;
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
_toStageCoords(clientX, clientY) {
|
|
97
|
+
const rect = this.container.getBoundingClientRect();
|
|
98
|
+
return { x: clientX - rect.left, y: clientY - rect.top };
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
_handleMouseDown(e) {
|
|
102
|
+
if (e.button !== 0) return;
|
|
103
|
+
if (!this._isWithinContainer(e.clientX, e.clientY)) return;
|
|
104
|
+
const pt = this._toStageCoords(e.clientX, e.clientY);
|
|
105
|
+
this.isPressed = true;
|
|
106
|
+
this.hasDragged = false;
|
|
107
|
+
this.startX = pt.x;
|
|
108
|
+
this.startY = pt.y;
|
|
109
|
+
this.lastX = pt.x;
|
|
110
|
+
this.lastY = pt.y;
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
_handleMouseMove(e) {
|
|
114
|
+
if ((e.buttons & 1) === 0) {
|
|
115
|
+
this.isPressed = false;
|
|
116
|
+
this.hasDragged = false;
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (!this.isPressed) return;
|
|
120
|
+
const pt = this._toStageCoords(e.clientX, e.clientY);
|
|
121
|
+
if (!this.hasDragged) {
|
|
122
|
+
const dx = pt.x - this.startX;
|
|
123
|
+
const dy = pt.y - this.startY;
|
|
124
|
+
if (dx * dx + dy * dy < DRAG_THRESHOLD * DRAG_THRESHOLD) return;
|
|
125
|
+
this.hasDragged = true;
|
|
126
|
+
this.segments.push({
|
|
127
|
+
x1: this.startX, y1: this.startY,
|
|
128
|
+
x2: pt.x, y2: pt.y,
|
|
129
|
+
time: performance.now()
|
|
130
|
+
});
|
|
131
|
+
} else {
|
|
132
|
+
this.segments.push({
|
|
133
|
+
x1: this.lastX, y1: this.lastY,
|
|
134
|
+
x2: pt.x, y2: pt.y,
|
|
135
|
+
time: performance.now()
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
this.lastX = pt.x;
|
|
139
|
+
this.lastY = pt.y;
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
_handleMouseUp() {
|
|
143
|
+
this.isPressed = false;
|
|
144
|
+
this.hasDragged = false;
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
_tick() {
|
|
148
|
+
if (!this.active) return;
|
|
149
|
+
const now = performance.now();
|
|
150
|
+
const initialLength = this.segments.length;
|
|
151
|
+
while (this.segments.length && now - this.segments[0].time > FADE_DURATION) {
|
|
152
|
+
this.segments.shift();
|
|
153
|
+
}
|
|
154
|
+
if (this.segments.length > 0 || initialLength > 0) {
|
|
155
|
+
this.layer.batchDraw();
|
|
156
|
+
}
|
|
157
|
+
this.rafId = requestAnimationFrame(this._onTick);
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
_renderSegments(ctx) {
|
|
161
|
+
const segs = this.segments;
|
|
162
|
+
if (segs.length === 0) return;
|
|
163
|
+
const now = performance.now();
|
|
164
|
+
const newestAge = (now - segs[segs.length - 1].time) / FADE_DURATION;
|
|
165
|
+
const opacity = Math.max(0, 1 - newestAge);
|
|
166
|
+
if (opacity <= 0) return;
|
|
167
|
+
|
|
168
|
+
ctx.save();
|
|
169
|
+
ctx.lineCap = 'round';
|
|
170
|
+
ctx.lineJoin = 'round';
|
|
171
|
+
|
|
172
|
+
ctx.beginPath();
|
|
173
|
+
ctx.moveTo(segs[0].x1, segs[0].y1);
|
|
174
|
+
for (let i = 0; i < segs.length; i++) {
|
|
175
|
+
ctx.lineTo(segs[i].x2, segs[i].y2);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
ctx.strokeStyle = `rgba(${GLOW_COLOR}, ${opacity * 0.3})`;
|
|
179
|
+
ctx.lineWidth = LASER_WIDTH * 2.5;
|
|
180
|
+
ctx.stroke();
|
|
181
|
+
|
|
182
|
+
ctx.strokeStyle = `rgba(${CORE_COLOR}, ${opacity})`;
|
|
183
|
+
ctx.lineWidth = LASER_WIDTH;
|
|
184
|
+
ctx.stroke();
|
|
185
|
+
|
|
186
|
+
ctx.restore();
|
|
187
|
+
}
|
|
188
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Vue from 'vue'
|
|
2
2
|
import Canvas from './canvas'
|
|
3
3
|
import Transformer from './transformer'
|
|
4
|
+
import Connection from './tools/helper/connection'
|
|
4
5
|
|
|
5
6
|
export default new Vue({
|
|
6
7
|
data() {
|
|
@@ -17,7 +18,7 @@ export default new Vue({
|
|
|
17
18
|
isTextNodesSelected: false,
|
|
18
19
|
isMassivelyAssigningProperties: false,
|
|
19
20
|
isToolLocked: false,
|
|
20
|
-
|
|
21
|
+
isAltHeld: false,
|
|
21
22
|
fontSizeOptions: {
|
|
22
23
|
15: 'S',
|
|
23
24
|
20: 'M',
|
|
@@ -105,14 +106,19 @@ export default new Vue({
|
|
|
105
106
|
return this.tool === 'text' || this.isTextNodesSelected;
|
|
106
107
|
},
|
|
107
108
|
isUsingDrawingTool() {
|
|
108
|
-
return
|
|
109
|
+
return this.tool !== 'selector' && this.tool !== 'laser';
|
|
110
|
+
},
|
|
111
|
+
isCanvasPenetrable() {
|
|
112
|
+
return this.tool === 'laser' ||
|
|
113
|
+
this.isAltHeld ||
|
|
114
|
+
(this.tool === 'selector' && this.isToolLocked);
|
|
109
115
|
}
|
|
110
116
|
},
|
|
111
117
|
watch: {
|
|
112
|
-
toolSelected() {
|
|
118
|
+
toolSelected(newTool) {
|
|
113
119
|
this.refreshNodesStatus()
|
|
114
|
-
if (
|
|
115
|
-
|
|
120
|
+
if (newTool !== 'line' && newTool !== 'arrow') {
|
|
121
|
+
Connection.hideSnapVisuals()
|
|
116
122
|
}
|
|
117
123
|
}
|
|
118
124
|
},
|
|
@@ -3,21 +3,30 @@ 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'
|
|
7
|
+
import Connection from './helper/connection'
|
|
6
8
|
|
|
7
9
|
export default {
|
|
8
10
|
temporalShape: null,
|
|
9
11
|
startX: 0,
|
|
10
12
|
startY: 0,
|
|
13
|
+
startSnap: null,
|
|
14
|
+
endSnap: null,
|
|
11
15
|
show({ offsetX, offsetY }) {
|
|
12
16
|
Canvas.isDrawing = true;
|
|
13
|
-
|
|
14
|
-
|
|
17
|
+
const startSnap = Connection.findSnapTarget({ x: offsetX, y: offsetY })
|
|
18
|
+
const startX = startSnap ? startSnap.worldX : offsetX
|
|
19
|
+
const startY = startSnap ? startSnap.worldY : offsetY
|
|
20
|
+
this.startX = startX
|
|
21
|
+
this.startY = startY
|
|
22
|
+
this.startSnap = startSnap
|
|
23
|
+
this.endSnap = null
|
|
15
24
|
const strokeWidth = Properties.strokeWidth
|
|
16
25
|
const pointerWidth = Math.pow(Properties.strokeWidth, 2) / 2
|
|
17
26
|
const pointerLength = Math.pow(Properties.strokeWidth, 2) / 2
|
|
18
27
|
const stroke = Properties.strokeColor
|
|
19
|
-
const newArrow = this.generate({
|
|
20
|
-
points: [
|
|
28
|
+
const newArrow = this.generate({
|
|
29
|
+
points: [startX, startY, startX, startY],
|
|
21
30
|
stroke,
|
|
22
31
|
strokeWidth,
|
|
23
32
|
pointerWidth,
|
|
@@ -25,25 +34,47 @@ export default {
|
|
|
25
34
|
})
|
|
26
35
|
this.temporalShape = newArrow
|
|
27
36
|
Canvas.layer.add(this.temporalShape)
|
|
37
|
+
Connection.hideSnapVisuals()
|
|
28
38
|
},
|
|
29
39
|
change({ offsetX, offsetY, shiftKey }) {
|
|
30
|
-
if (Canvas.isDrawing)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
if (!Canvas.isDrawing) return
|
|
41
|
+
const endPoint = AngleSnap.getSnappedEndPoint(
|
|
42
|
+
this.startX,
|
|
43
|
+
this.startY,
|
|
44
|
+
offsetX,
|
|
45
|
+
offsetY,
|
|
46
|
+
shiftKey
|
|
47
|
+
);
|
|
48
|
+
let endX = endPoint.x
|
|
49
|
+
let endY = endPoint.y
|
|
50
|
+
const endSnap = Connection.findSnapTarget({ x: endX, y: endY }, this.temporalShape, 'end')
|
|
51
|
+
if (endSnap) {
|
|
52
|
+
endX = endSnap.worldX
|
|
53
|
+
endY = endSnap.worldY
|
|
54
|
+
Connection.showSnapVisuals(endSnap)
|
|
55
|
+
} else {
|
|
56
|
+
Connection.hideSnapVisuals()
|
|
39
57
|
}
|
|
58
|
+
this.endSnap = endSnap
|
|
59
|
+
this.temporalShape.points([this.startX, this.startY, endX, endY]);
|
|
40
60
|
},
|
|
41
61
|
finish() {
|
|
62
|
+
Connection.hideSnapVisuals()
|
|
63
|
+
if (this.temporalShape) {
|
|
64
|
+
if (this.startSnap) {
|
|
65
|
+
Connection.attachLineEndpoint(this.temporalShape, 'start', this.startSnap)
|
|
66
|
+
}
|
|
67
|
+
if (this.endSnap) {
|
|
68
|
+
Connection.attachLineEndpoint(this.temporalShape, 'end', this.endSnap)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
42
71
|
if (!Properties.isToolLocked) {
|
|
43
72
|
Properties.tool = Properties.latestTool.tool
|
|
44
73
|
Properties.isToolLocked = Properties.latestTool.isToolLocked
|
|
45
74
|
}
|
|
46
75
|
this.temporalShape = null
|
|
76
|
+
this.startSnap = null
|
|
77
|
+
this.endSnap = null
|
|
47
78
|
},
|
|
48
79
|
bindEvents(newArrow) {
|
|
49
80
|
newArrow.off('transform')
|
|
@@ -58,8 +89,10 @@ export default {
|
|
|
58
89
|
})
|
|
59
90
|
newArrow.on('transformend', Histories.record)
|
|
60
91
|
newArrow.on('dragend', Histories.record)
|
|
92
|
+
AttachedText.bindShapeEvents(newArrow)
|
|
93
|
+
Connection.bindLineListeners(newArrow)
|
|
61
94
|
},
|
|
62
|
-
generate({ x, y, scaleX, scaleY, skewX, rotation, points, stroke, strokeWidth, pointerWidth, pointerLength }) {
|
|
95
|
+
generate({ x, y, scaleX, scaleY, skewX, rotation, points, stroke, strokeWidth, pointerWidth, pointerLength, attachedText, attachedFontSize, attachedFill, startAttachedTo, startAttachedLocalX, startAttachedLocalY, endAttachedTo, endAttachedLocalX, endAttachedLocalY }) {
|
|
63
96
|
const newArrow = new Konva.Arrow({
|
|
64
97
|
x,
|
|
65
98
|
y,
|
|
@@ -72,6 +105,15 @@ export default {
|
|
|
72
105
|
strokeWidth,
|
|
73
106
|
pointerWidth,
|
|
74
107
|
pointerLength,
|
|
108
|
+
attachedText,
|
|
109
|
+
attachedFontSize,
|
|
110
|
+
attachedFill,
|
|
111
|
+
startAttachedTo,
|
|
112
|
+
startAttachedLocalX,
|
|
113
|
+
startAttachedLocalY,
|
|
114
|
+
endAttachedTo,
|
|
115
|
+
endAttachedLocalX,
|
|
116
|
+
endAttachedLocalY,
|
|
75
117
|
name: 'node',
|
|
76
118
|
nodeType: 'arrow',
|
|
77
119
|
lineCap: 'round',
|