jellies-draw 0.2.5 → 0.3.1
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 +28 -15
- package/src/components/functions/canvas.js +43 -5
- package/src/components/functions/clipboard.js +27 -0
- package/src/components/functions/laser.js +188 -0
- package/src/components/functions/properties.js +11 -5
- package/src/components/functions/tools/arrow.js +51 -14
- package/src/components/functions/tools/ellipse.js +5 -1
- package/src/components/functions/tools/helper/attachedText.js +62 -6
- 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 +51 -14
- package/src/components/functions/tools/rectangle.js +5 -1
- package/src/components/functions/transformer.js +42 -9
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') {
|
|
@@ -131,7 +131,6 @@ export default {
|
|
|
131
131
|
}
|
|
132
132
|
this.isCurrentToolLocked = this.toolsCanBeLocked.includes(action) && this.currentTool === action
|
|
133
133
|
this.currentTool = action
|
|
134
|
-
this.isCanvasPenetrable = this.currentTool === 'selector' && this.isCurrentToolLocked
|
|
135
134
|
this.releaseInstantTool()
|
|
136
135
|
},
|
|
137
136
|
releaseInstantTool() {
|
|
@@ -148,12 +147,26 @@ export default {
|
|
|
148
147
|
event.target.value = ''
|
|
149
148
|
},
|
|
150
149
|
handleShortCutKey(event) {
|
|
151
|
-
if (this.hasShortCuts
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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')
|
|
156
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])
|
|
157
170
|
}
|
|
158
171
|
}
|
|
159
172
|
}
|
|
@@ -4,7 +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';
|
|
7
8
|
import AttachedText from './tools/helper/attachedText';
|
|
9
|
+
import Connection from './tools/helper/connection';
|
|
8
10
|
export default {
|
|
9
11
|
layer: null,
|
|
10
12
|
stage: null,
|
|
@@ -30,6 +32,15 @@ export default {
|
|
|
30
32
|
});
|
|
31
33
|
this.layer = new Konva.Layer();
|
|
32
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
|
+
);
|
|
33
44
|
AttachedText.attachLayerListeners(this.layer);
|
|
34
45
|
document.addEventListener('keydown', this.keydownHandler);
|
|
35
46
|
document.addEventListener('keyup', this.keyupHandler);
|
|
@@ -43,6 +54,10 @@ export default {
|
|
|
43
54
|
window.addEventListener('pointerup', this.handleGlobalPointerUp, { once: true });
|
|
44
55
|
});
|
|
45
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);
|
|
46
61
|
},
|
|
47
62
|
generateEventHandlers() {
|
|
48
63
|
this.resizeHandler = this._handleResize.bind(this);
|
|
@@ -74,6 +89,15 @@ export default {
|
|
|
74
89
|
this.stage.off('click tap', this.clickHandler);
|
|
75
90
|
document.removeEventListener('keydown', this.keydownHandler);
|
|
76
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();
|
|
77
101
|
this.stage.clear()
|
|
78
102
|
this.stage = null;
|
|
79
103
|
window.removeEventListener('resize', this.resizeHandler);
|
|
@@ -102,12 +126,12 @@ export default {
|
|
|
102
126
|
this._handlePropertiesShortCuts(event);
|
|
103
127
|
}
|
|
104
128
|
} else if (event.altKey) {
|
|
105
|
-
Properties.
|
|
129
|
+
Properties.isAltHeld = true;
|
|
106
130
|
}
|
|
107
131
|
},
|
|
108
132
|
_handleKeyup(event) {
|
|
109
133
|
if (event.key === 'Alt') {
|
|
110
|
-
Properties.
|
|
134
|
+
Properties.isAltHeld = false;
|
|
111
135
|
if (Properties.tool === 'selector') {
|
|
112
136
|
Properties.isToolLocked = false;
|
|
113
137
|
Properties.latestTool = {
|
|
@@ -197,11 +221,14 @@ export default {
|
|
|
197
221
|
},
|
|
198
222
|
_handleMouseMove(e) {
|
|
199
223
|
if (Properties.isUsingDrawingTool || Properties.isUsingText) {
|
|
224
|
+
if (Properties.tool === 'line' || Properties.tool === 'arrow') {
|
|
225
|
+
this._handleSnapHoverPreview();
|
|
226
|
+
}
|
|
200
227
|
return;
|
|
201
228
|
}
|
|
202
229
|
|
|
203
230
|
const target = e.target;
|
|
204
|
-
|
|
231
|
+
|
|
205
232
|
if (target === this.stage) {
|
|
206
233
|
this.stage.container().style.cursor = 'default';
|
|
207
234
|
return;
|
|
@@ -210,9 +237,9 @@ export default {
|
|
|
210
237
|
if (target.hasName('node')) {
|
|
211
238
|
const mousePos = this.stage.getPointerPosition();
|
|
212
239
|
const canInteract = this._canInteractWithNode(target, mousePos);
|
|
213
|
-
|
|
240
|
+
|
|
214
241
|
this.stage.container().style.cursor = canInteract ? 'move' : 'default';
|
|
215
|
-
|
|
242
|
+
|
|
216
243
|
if (!canInteract) {
|
|
217
244
|
target.draggable(false);
|
|
218
245
|
} else if (!Properties.isUsingDrawingTool) {
|
|
@@ -220,6 +247,17 @@ export default {
|
|
|
220
247
|
}
|
|
221
248
|
}
|
|
222
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
|
+
},
|
|
223
261
|
_canInteractWithNode(node, mousePos) {
|
|
224
262
|
const selectedNodes = Transformer._nodes();
|
|
225
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,6 +40,17 @@ 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();
|
|
29
56
|
if (className === 'Text') {
|
|
@@ -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
|
},
|
|
@@ -4,21 +4,29 @@ import Canvas from '../canvas'
|
|
|
4
4
|
import Histories from '../histories'
|
|
5
5
|
import AngleSnap from './helper/angleSnap'
|
|
6
6
|
import AttachedText from './helper/attachedText'
|
|
7
|
+
import Connection from './helper/connection'
|
|
7
8
|
|
|
8
9
|
export default {
|
|
9
10
|
temporalShape: null,
|
|
10
11
|
startX: 0,
|
|
11
12
|
startY: 0,
|
|
13
|
+
startSnap: null,
|
|
14
|
+
endSnap: null,
|
|
12
15
|
show({ offsetX, offsetY }) {
|
|
13
16
|
Canvas.isDrawing = true;
|
|
14
|
-
|
|
15
|
-
|
|
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
|
|
16
24
|
const strokeWidth = Properties.strokeWidth
|
|
17
25
|
const pointerWidth = Math.pow(Properties.strokeWidth, 2) / 2
|
|
18
26
|
const pointerLength = Math.pow(Properties.strokeWidth, 2) / 2
|
|
19
27
|
const stroke = Properties.strokeColor
|
|
20
|
-
const newArrow = this.generate({
|
|
21
|
-
points: [
|
|
28
|
+
const newArrow = this.generate({
|
|
29
|
+
points: [startX, startY, startX, startY],
|
|
22
30
|
stroke,
|
|
23
31
|
strokeWidth,
|
|
24
32
|
pointerWidth,
|
|
@@ -26,25 +34,47 @@ export default {
|
|
|
26
34
|
})
|
|
27
35
|
this.temporalShape = newArrow
|
|
28
36
|
Canvas.layer.add(this.temporalShape)
|
|
37
|
+
Connection.hideSnapVisuals()
|
|
29
38
|
},
|
|
30
39
|
change({ offsetX, offsetY, shiftKey }) {
|
|
31
|
-
if (Canvas.isDrawing)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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()
|
|
40
57
|
}
|
|
58
|
+
this.endSnap = endSnap
|
|
59
|
+
this.temporalShape.points([this.startX, this.startY, endX, endY]);
|
|
41
60
|
},
|
|
42
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
|
+
}
|
|
43
71
|
if (!Properties.isToolLocked) {
|
|
44
72
|
Properties.tool = Properties.latestTool.tool
|
|
45
73
|
Properties.isToolLocked = Properties.latestTool.isToolLocked
|
|
46
74
|
}
|
|
47
75
|
this.temporalShape = null
|
|
76
|
+
this.startSnap = null
|
|
77
|
+
this.endSnap = null
|
|
48
78
|
},
|
|
49
79
|
bindEvents(newArrow) {
|
|
50
80
|
newArrow.off('transform')
|
|
@@ -60,8 +90,9 @@ export default {
|
|
|
60
90
|
newArrow.on('transformend', Histories.record)
|
|
61
91
|
newArrow.on('dragend', Histories.record)
|
|
62
92
|
AttachedText.bindShapeEvents(newArrow)
|
|
93
|
+
Connection.bindLineListeners(newArrow)
|
|
63
94
|
},
|
|
64
|
-
generate({ x, y, scaleX, scaleY, skewX, rotation, points, stroke, strokeWidth, pointerWidth, pointerLength, attachedText, attachedFontSize, attachedFill }) {
|
|
95
|
+
generate({ x, y, scaleX, scaleY, skewX, rotation, points, stroke, strokeWidth, pointerWidth, pointerLength, attachedText, attachedFontSize, attachedFill, startAttachedTo, startAttachedLocalX, startAttachedLocalY, endAttachedTo, endAttachedLocalX, endAttachedLocalY }) {
|
|
65
96
|
const newArrow = new Konva.Arrow({
|
|
66
97
|
x,
|
|
67
98
|
y,
|
|
@@ -77,6 +108,12 @@ export default {
|
|
|
77
108
|
attachedText,
|
|
78
109
|
attachedFontSize,
|
|
79
110
|
attachedFill,
|
|
111
|
+
startAttachedTo,
|
|
112
|
+
startAttachedLocalX,
|
|
113
|
+
startAttachedLocalY,
|
|
114
|
+
endAttachedTo,
|
|
115
|
+
endAttachedLocalX,
|
|
116
|
+
endAttachedLocalY,
|
|
80
117
|
name: 'node',
|
|
81
118
|
nodeType: 'arrow',
|
|
82
119
|
lineCap: 'round',
|
|
@@ -4,6 +4,7 @@ import Canvas from '../canvas'
|
|
|
4
4
|
import Histories from '../histories'
|
|
5
5
|
import ShapeConstraint from './helper/shapeConstraint'
|
|
6
6
|
import AttachedText from './helper/attachedText'
|
|
7
|
+
import Connection from './helper/connection'
|
|
7
8
|
|
|
8
9
|
export default {
|
|
9
10
|
temporalShape: null,
|
|
@@ -60,7 +61,7 @@ export default {
|
|
|
60
61
|
}
|
|
61
62
|
}, 200)
|
|
62
63
|
},
|
|
63
|
-
generate({ x, y, radiusX, radiusY, scaleX, scaleY, skewX, rotation, fill, stroke, strokeWidth, attachedText, attachedFontSize, attachedFill }) {
|
|
64
|
+
generate({ x, y, radiusX, radiusY, scaleX, scaleY, skewX, rotation, fill, stroke, strokeWidth, attachedText, attachedFontSize, attachedFill, nodeId }) {
|
|
64
65
|
const newEllipse = new Konva.Ellipse({
|
|
65
66
|
x,
|
|
66
67
|
y,
|
|
@@ -76,10 +77,12 @@ export default {
|
|
|
76
77
|
attachedText,
|
|
77
78
|
attachedFontSize,
|
|
78
79
|
attachedFill,
|
|
80
|
+
nodeId,
|
|
79
81
|
name: 'node',
|
|
80
82
|
nodeType: 'ellipse',
|
|
81
83
|
strokeScaleEnabled: false
|
|
82
84
|
})
|
|
85
|
+
Connection.ensureNodeId(newEllipse)
|
|
83
86
|
this.bindEvents(newEllipse)
|
|
84
87
|
return newEllipse
|
|
85
88
|
},
|
|
@@ -89,5 +92,6 @@ export default {
|
|
|
89
92
|
newEllipse.on('transformend', Histories.record)
|
|
90
93
|
newEllipse.on('dragend', Histories.record)
|
|
91
94
|
AttachedText.bindShapeEvents(newEllipse)
|
|
95
|
+
Connection.bindShapeListeners(newEllipse)
|
|
92
96
|
}
|
|
93
97
|
}
|