jellies-draw 0.1.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/.eslintrc.js +89 -0
- package/README.md +24 -0
- package/babel.config.js +5 -0
- package/package.json +65 -0
- package/public/favicon.ico +0 -0
- package/public/index.html +17 -0
- package/src/App.vue +93 -0
- package/src/assets/jellies-draw-tool.ttf +0 -0
- package/src/components/DrawingCanvas.vue +66 -0
- package/src/components/PropertyPickers.vue +218 -0
- package/src/components/ToolButton.vue +125 -0
- package/src/components/ToolButtons.vue +113 -0
- package/src/components/functions/canvas.js +136 -0
- package/src/components/functions/clipboard.js +32 -0
- package/src/components/functions/histories.js +53 -0
- package/src/components/functions/properties.js +126 -0
- package/src/components/functions/tools/arrow.js +70 -0
- package/src/components/functions/tools/clear.js +8 -0
- package/src/components/functions/tools/ellipse.js +73 -0
- package/src/components/functions/tools/eraser.js +51 -0
- package/src/components/functions/tools/image.js +74 -0
- package/src/components/functions/tools/index.js +23 -0
- package/src/components/functions/tools/line.js +56 -0
- package/src/components/functions/tools/pen.js +61 -0
- package/src/components/functions/tools/rectangle.js +71 -0
- package/src/components/functions/tools/selector.js +47 -0
- package/src/components/functions/tools/text.js +178 -0
- package/src/components/functions/transformer.js +214 -0
- package/src/index.js +9 -0
- package/src/main.js +8 -0
- package/webpack.config.js +39 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="buttons">
|
|
3
|
+
<tool-button
|
|
4
|
+
v-for="tool, index in tools"
|
|
5
|
+
:key="tool"
|
|
6
|
+
:action="tool"
|
|
7
|
+
:short-cut="(index + 1) % 10"
|
|
8
|
+
:is-active="currentTool === tool"
|
|
9
|
+
@action-applied="applyAction"
|
|
10
|
+
/>
|
|
11
|
+
<input
|
|
12
|
+
ref="fileSelector"
|
|
13
|
+
class="file-selector"
|
|
14
|
+
type="file"
|
|
15
|
+
@change="uploadImage"
|
|
16
|
+
/>
|
|
17
|
+
</div>
|
|
18
|
+
</template>
|
|
19
|
+
<script>
|
|
20
|
+
import ToolButton from './ToolButton.vue'
|
|
21
|
+
import Properties from './functions/properties'
|
|
22
|
+
import Tools from './functions/tools'
|
|
23
|
+
export default {
|
|
24
|
+
name: 'ToolButtons',
|
|
25
|
+
components: {
|
|
26
|
+
ToolButton
|
|
27
|
+
},
|
|
28
|
+
props: {
|
|
29
|
+
hasShortCuts: {
|
|
30
|
+
type: Boolean,
|
|
31
|
+
default: false
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
mounted() {
|
|
35
|
+
document.addEventListener('keydown', this.handleShortCutKey)
|
|
36
|
+
},
|
|
37
|
+
beforeDestroy() {
|
|
38
|
+
document.removeEventListener('keydown', this.handleShortCutKey)
|
|
39
|
+
},
|
|
40
|
+
data() {
|
|
41
|
+
return {
|
|
42
|
+
tools: [
|
|
43
|
+
'selector',
|
|
44
|
+
'rectangle',
|
|
45
|
+
'ellipse',
|
|
46
|
+
'arrow',
|
|
47
|
+
'line',
|
|
48
|
+
'pen',
|
|
49
|
+
'text',
|
|
50
|
+
'image',
|
|
51
|
+
'eraser',
|
|
52
|
+
'clear'
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
computed: {
|
|
57
|
+
currentTool: {
|
|
58
|
+
get() {
|
|
59
|
+
return Properties.tool;
|
|
60
|
+
},
|
|
61
|
+
set(newTool) {
|
|
62
|
+
Properties.tool = newTool;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
watch: {
|
|
67
|
+
currentTool(newTool) {
|
|
68
|
+
this.currentTool = newTool;
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
methods: {
|
|
72
|
+
applyAction(action) {
|
|
73
|
+
if (action === 'image') {
|
|
74
|
+
this.$refs.fileSelector.click()
|
|
75
|
+
}
|
|
76
|
+
if (action === 'clear') {
|
|
77
|
+
Tools.clear.clear()
|
|
78
|
+
}
|
|
79
|
+
this.currentTool = action
|
|
80
|
+
this.releaseInstantTool()
|
|
81
|
+
},
|
|
82
|
+
releaseInstantTool() {
|
|
83
|
+
if (this.currentTool === 'image' || this.currentTool === 'clear') {
|
|
84
|
+
setTimeout(() => {
|
|
85
|
+
this.currentTool = 'selector'
|
|
86
|
+
}, 300)
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
uploadImage(event) {
|
|
90
|
+
const file = event.target.files[0]
|
|
91
|
+
Tools.image.add(file)
|
|
92
|
+
event.target.value = ''
|
|
93
|
+
},
|
|
94
|
+
handleShortCutKey(event) {
|
|
95
|
+
if (this.hasShortCuts && this.currentTool !== 'text') {
|
|
96
|
+
const key = event.key
|
|
97
|
+
const index = parseInt(key, 10)
|
|
98
|
+
if (index >= 0 && index < this.tools.length) {
|
|
99
|
+
this.applyAction(this.tools[(index + 9) % 10])
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
</script>
|
|
106
|
+
<style scoped>
|
|
107
|
+
.buttons {
|
|
108
|
+
display: flex;
|
|
109
|
+
}
|
|
110
|
+
.file-selector {
|
|
111
|
+
display: none;
|
|
112
|
+
}
|
|
113
|
+
</style>
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import Konva from 'konva';
|
|
2
|
+
import Transformer from './transformer';
|
|
3
|
+
import Clipboard from './clipboard';
|
|
4
|
+
import Properties from './properties';
|
|
5
|
+
import Tools from './tools';
|
|
6
|
+
import Histories from './histories';
|
|
7
|
+
export default {
|
|
8
|
+
layer: null,
|
|
9
|
+
stage: null,
|
|
10
|
+
container: null,
|
|
11
|
+
resizeHandler: null,
|
|
12
|
+
keydownHandler: null,
|
|
13
|
+
pointerDownHandler: null,
|
|
14
|
+
pointerMoveHandler: null,
|
|
15
|
+
pointerUpHandler: null,
|
|
16
|
+
clickHandler: null,
|
|
17
|
+
isEditingText: false,
|
|
18
|
+
isDrawing: false,
|
|
19
|
+
isRecording: false,
|
|
20
|
+
initialize(canvas, container) {
|
|
21
|
+
this.container = container
|
|
22
|
+
this.generateEventHandlers()
|
|
23
|
+
window.addEventListener('resize', this.resizeHandler);
|
|
24
|
+
this.stage = new Konva.Stage({
|
|
25
|
+
container: canvas,
|
|
26
|
+
width: container.offsetWidth,
|
|
27
|
+
height: container.offsetHeight
|
|
28
|
+
});
|
|
29
|
+
this.layer = new Konva.Layer();
|
|
30
|
+
this.stage.add(this.layer);
|
|
31
|
+
document.addEventListener('keydown', this.keydownHandler);
|
|
32
|
+
Transformer.initialize();
|
|
33
|
+
Histories.record();
|
|
34
|
+
this.stage.on('pointerdown', this.pointerDownHandler);
|
|
35
|
+
this.stage.on('pointermove', this.pointerMoveHandler);
|
|
36
|
+
this.stage.on('pointerup', this.pointerUpHandler);
|
|
37
|
+
this.stage.on('click tap', this.clickHandler);
|
|
38
|
+
},
|
|
39
|
+
generateEventHandlers() {
|
|
40
|
+
this.resizeHandler = this._handleResize.bind(this);
|
|
41
|
+
this.keydownHandler = this._handleKeydown.bind(this);
|
|
42
|
+
this.pointerDownHandler = this.handlePointerDown.bind(this);
|
|
43
|
+
this.pointerMoveHandler = this.handlePointerMove.bind(this);
|
|
44
|
+
this.pointerUpHandler = this.handlePointerUp.bind(this);
|
|
45
|
+
this.clickHandler = this.handleClick.bind(this);
|
|
46
|
+
},
|
|
47
|
+
clear() {
|
|
48
|
+
this.layer.destroyChildren();
|
|
49
|
+
Transformer.initialize();
|
|
50
|
+
},
|
|
51
|
+
destroy() {
|
|
52
|
+
this.stage.off('pointerdown', this.pointerDownHandler);
|
|
53
|
+
this.stage.off('pointermove', this.pointerMoveHandler);
|
|
54
|
+
this.stage.off('pointerup', this.pointerUpHandler);
|
|
55
|
+
this.stage.off('click tap', this.clickHandler);
|
|
56
|
+
document.removeEventListener('keydown', this.keydownHandler);
|
|
57
|
+
this.stage.clear()
|
|
58
|
+
this.stage = null;
|
|
59
|
+
window.removeEventListener('resize', this.resizeHandler);
|
|
60
|
+
},
|
|
61
|
+
_handleResize() {
|
|
62
|
+
if (this.stage) {
|
|
63
|
+
this.stage.width(this.container.offsetWidth);
|
|
64
|
+
this.stage.height(this.container.offsetHeight);
|
|
65
|
+
this.stage.batchDraw();
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
_handleKeydown(event) {
|
|
69
|
+
if (event.key === 'Escape') {
|
|
70
|
+
Transformer.deselectAllNodes();
|
|
71
|
+
} else if (event.key === 'Backspace') {
|
|
72
|
+
Transformer.removeSelectedNodes();
|
|
73
|
+
} else if (event.ctrlKey || event.metaKey) {
|
|
74
|
+
if (!this.isEditingText) {
|
|
75
|
+
this._handleKeyShortCuts(event);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
_handleKeyShortCuts(event) {
|
|
80
|
+
event.preventDefault();
|
|
81
|
+
if (event.key === 'c') {
|
|
82
|
+
Clipboard.copy();
|
|
83
|
+
} else if (event.key === 'x') {
|
|
84
|
+
Clipboard.cut();
|
|
85
|
+
} else if (event.key === 'v') {
|
|
86
|
+
Clipboard.paste();
|
|
87
|
+
} else if (event.key === 'a') {
|
|
88
|
+
Transformer.selectAllNodes();
|
|
89
|
+
} else if (event.key === 'z' || event.key === 'Z') {
|
|
90
|
+
if (event.shiftKey) {
|
|
91
|
+
Histories.redo()
|
|
92
|
+
} else {
|
|
93
|
+
Histories.undo()
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
handlePointerDown(event) {
|
|
98
|
+
if (Properties.tool === 'selector') {
|
|
99
|
+
if (event.target !== this.stage) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
Tools[Properties.tool].show(event.evt)
|
|
104
|
+
},
|
|
105
|
+
handlePointerMove(event) {
|
|
106
|
+
if (Properties.tool !== 'clear') {
|
|
107
|
+
Tools[Properties.tool].change(event.evt)
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
handlePointerUp(event) {
|
|
111
|
+
Tools[Properties.tool].finish()
|
|
112
|
+
if (this.isDrawing || this.isEditingText) {
|
|
113
|
+
Histories.record()
|
|
114
|
+
}
|
|
115
|
+
this.isDrawing = false
|
|
116
|
+
this.isEditingText = false
|
|
117
|
+
},
|
|
118
|
+
handleClick(event) {
|
|
119
|
+
const metaPressed = event.evt.shiftKey || event.evt.ctrlKey || event.evt.metaKey;
|
|
120
|
+
const targetNode = event.target;
|
|
121
|
+
if (Properties.isUsingDrawingTool) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (Tools.selector.isSelecting()) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (!targetNode.hasName('node')) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (targetNode === this.stage) {
|
|
131
|
+
Transformer.deselectAllNodes();
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
Transformer.selectNode(targetNode, metaPressed);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import Canvas from './canvas';
|
|
2
|
+
import Transformer from './transformer';
|
|
3
|
+
import Tools from './tools';
|
|
4
|
+
export default {
|
|
5
|
+
copiedNodes: [],
|
|
6
|
+
copy() {
|
|
7
|
+
this.copiedNodes = Transformer.copySelectedNodes();
|
|
8
|
+
},
|
|
9
|
+
cut() {
|
|
10
|
+
this.copy();
|
|
11
|
+
Transformer.removeSelectedNodes();
|
|
12
|
+
},
|
|
13
|
+
paste() {
|
|
14
|
+
if (this.copiedNodes) {
|
|
15
|
+
this.copiedNodes.forEach((node) => {
|
|
16
|
+
node.x(node.x() + 10);
|
|
17
|
+
node.y(node.y() + 10);
|
|
18
|
+
this._addEventsToCopiedNode(node);
|
|
19
|
+
Canvas.layer.add(node);
|
|
20
|
+
});
|
|
21
|
+
Transformer.selectNodes(this.copiedNodes);
|
|
22
|
+
this.copiedNodes = Transformer.copySelectedNodes();
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
_addEventsToCopiedNode(node) {
|
|
26
|
+
if (node.getClassName() === 'Text') {
|
|
27
|
+
Tools.text.bindEvents(node);
|
|
28
|
+
} else if (node.getClassName() === 'Arrow') {
|
|
29
|
+
Tools.arrow.bindEvents(node);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import Vue from 'vue'
|
|
2
|
+
import Konva from 'konva'
|
|
3
|
+
import Canvas from './canvas'
|
|
4
|
+
import Transformer from './transformer'
|
|
5
|
+
import Tools from './tools'
|
|
6
|
+
import Properties from './properties'
|
|
7
|
+
export default new Vue({
|
|
8
|
+
data() {
|
|
9
|
+
return {
|
|
10
|
+
histories: [],
|
|
11
|
+
historyIndex: -1
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
methods: {
|
|
15
|
+
record() {
|
|
16
|
+
if (Canvas.isRecording) {
|
|
17
|
+
return
|
|
18
|
+
}
|
|
19
|
+
Canvas.isRecording = true
|
|
20
|
+
this.histories.splice(this.historyIndex + 1)
|
|
21
|
+
this.histories.push(Canvas.layer.toJSON())
|
|
22
|
+
this.historyIndex++
|
|
23
|
+
this.$nextTick(() => {
|
|
24
|
+
Canvas.isRecording = false
|
|
25
|
+
})
|
|
26
|
+
},
|
|
27
|
+
undo() {
|
|
28
|
+
if (this.historyIndex > 0) {
|
|
29
|
+
this.historyIndex--
|
|
30
|
+
this.refreshNodes()
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
redo() {
|
|
34
|
+
if (this.historyIndex < this.histories.length - 1) {
|
|
35
|
+
this.historyIndex++
|
|
36
|
+
this.refreshNodes()
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
refreshNodes() {
|
|
40
|
+
Canvas.layer.destroyChildren();
|
|
41
|
+
const latestNodes = Konva.Node.create(this.histories[this.historyIndex]).find('.node')
|
|
42
|
+
latestNodes.forEach(({ attrs }) => {
|
|
43
|
+
if (attrs.nodeType === 'image') {
|
|
44
|
+
Tools[attrs.nodeType].generate(attrs)
|
|
45
|
+
} else {
|
|
46
|
+
Canvas.layer.add(Tools[attrs.nodeType].generate(attrs))
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
Transformer.initialize()
|
|
50
|
+
Properties.refreshNodesStatus()
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
})
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import Vue from 'vue'
|
|
2
|
+
import Canvas from './canvas'
|
|
3
|
+
import Transformer from './transformer'
|
|
4
|
+
|
|
5
|
+
export default new Vue({
|
|
6
|
+
data() {
|
|
7
|
+
return {
|
|
8
|
+
fillColorPicked: 'transparent',
|
|
9
|
+
strokeColorPicked: '#000000',
|
|
10
|
+
fontSizeSelected: 20,
|
|
11
|
+
strokeWidthSelected: 4,
|
|
12
|
+
toolSelected: 'selector',
|
|
13
|
+
isTextNodesSelected: false,
|
|
14
|
+
isMassivelyAssigningProperties: false
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
computed: {
|
|
18
|
+
fillColor: {
|
|
19
|
+
get() {
|
|
20
|
+
return this.fillColorPicked;
|
|
21
|
+
},
|
|
22
|
+
set(newFillColor) {
|
|
23
|
+
this.fillColorPicked = newFillColor;
|
|
24
|
+
if (!this.isMassivelyAssigningProperties) {
|
|
25
|
+
Transformer.setPropertiesOfSelectedNodes({
|
|
26
|
+
fill: newFillColor
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
strokeColor: {
|
|
32
|
+
get() {
|
|
33
|
+
return this.strokeColorPicked;
|
|
34
|
+
},
|
|
35
|
+
set(newStrokeColor) {
|
|
36
|
+
this.strokeColorPicked = newStrokeColor;
|
|
37
|
+
if (!this.isMassivelyAssigningProperties) {
|
|
38
|
+
Transformer.setPropertiesOfSelectedNodes({
|
|
39
|
+
stroke: newStrokeColor
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
fontSize: {
|
|
45
|
+
get() {
|
|
46
|
+
return this.fontSizeSelected;
|
|
47
|
+
},
|
|
48
|
+
set(newFontSize) {
|
|
49
|
+
this.fontSizeSelected = newFontSize;
|
|
50
|
+
if (!this.isMassivelyAssigningProperties) {
|
|
51
|
+
Transformer.setPropertiesOfSelectedNodes({
|
|
52
|
+
fontSize: parseInt(newFontSize)
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
strokeWidth: {
|
|
58
|
+
get() {
|
|
59
|
+
return this.strokeWidthSelected;
|
|
60
|
+
},
|
|
61
|
+
set(newStrokeWidth) {
|
|
62
|
+
this.strokeWidthSelected = newStrokeWidth;
|
|
63
|
+
if (!this.isMassivelyAssigningProperties) {
|
|
64
|
+
Transformer.setPropertiesOfSelectedNodes({
|
|
65
|
+
strokeWidth: parseInt(newStrokeWidth)
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
tool: {
|
|
71
|
+
get() {
|
|
72
|
+
return this.toolSelected;
|
|
73
|
+
},
|
|
74
|
+
set(newTool) {
|
|
75
|
+
this.toolSelected = newTool;
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
isUsingText() {
|
|
79
|
+
return this.tool === 'text' || this.isTextNodesSelected;
|
|
80
|
+
},
|
|
81
|
+
isUsingDrawingTool() {
|
|
82
|
+
return (this.tool !== 'selector');
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
watch: {
|
|
86
|
+
toolSelected() {
|
|
87
|
+
this.refreshNodesStatus()
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
methods: {
|
|
91
|
+
bindMoveCursor(shape) {
|
|
92
|
+
if (this.isUsingDrawingTool) {
|
|
93
|
+
shape.off('mouseenter', this.handleSwitchToMoveCursor);
|
|
94
|
+
shape.off('mouseleave', this.handleClearMoveCursor);
|
|
95
|
+
} else {
|
|
96
|
+
shape.on('mouseenter', this.handleSwitchToMoveCursor);
|
|
97
|
+
shape.on('mouseleave', this.handleClearMoveCursor);
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
handleSwitchToMoveCursor(){
|
|
101
|
+
Canvas.stage.container().style.cursor = 'move';
|
|
102
|
+
},
|
|
103
|
+
handleClearMoveCursor(){
|
|
104
|
+
Canvas.stage.container().style.cursor = null;
|
|
105
|
+
},
|
|
106
|
+
setProperties(properties) {
|
|
107
|
+
this.isMassivelyAssigningProperties = true;
|
|
108
|
+
Object.keys(properties).forEach(property => {
|
|
109
|
+
this[property] = properties[property];
|
|
110
|
+
});
|
|
111
|
+
this.isMassivelyAssigningProperties = false;
|
|
112
|
+
},
|
|
113
|
+
refreshNodesStatus() {
|
|
114
|
+
if (Canvas.layer) {
|
|
115
|
+
Canvas.layer.children.forEach(shape => {
|
|
116
|
+
shape.draggable(!this.isUsingDrawingTool);
|
|
117
|
+
this.bindMoveCursor(shape);
|
|
118
|
+
if (this.isUsingDrawingTool || this.isUsingText) {
|
|
119
|
+
Transformer.deselectAllNodes();
|
|
120
|
+
this.handleClearMoveCursor();
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
})
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import Konva from 'konva'
|
|
2
|
+
import Properties from '../properties'
|
|
3
|
+
import Canvas from '../canvas'
|
|
4
|
+
import Histories from '../histories'
|
|
5
|
+
export default {
|
|
6
|
+
temporalShape: null,
|
|
7
|
+
startX: 0,
|
|
8
|
+
startY: 0,
|
|
9
|
+
show({ offsetX, offsetY }) {
|
|
10
|
+
Canvas.isDrawing = true;
|
|
11
|
+
this.startX = offsetX
|
|
12
|
+
this.startY = offsetY
|
|
13
|
+
const strokeWidth = Properties.strokeWidth
|
|
14
|
+
const pointerWidth = Math.pow(Properties.strokeWidth, 2) / 2
|
|
15
|
+
const pointerLength = Math.pow(Properties.strokeWidth, 2) / 2
|
|
16
|
+
const stroke = Properties.strokeColor
|
|
17
|
+
const newArrow = this.generate({
|
|
18
|
+
points: [offsetX, offsetY, offsetX, offsetY],
|
|
19
|
+
stroke,
|
|
20
|
+
strokeWidth,
|
|
21
|
+
pointerWidth,
|
|
22
|
+
pointerLength
|
|
23
|
+
})
|
|
24
|
+
this.temporalShape = newArrow
|
|
25
|
+
Canvas.layer.add(this.temporalShape)
|
|
26
|
+
},
|
|
27
|
+
change({ offsetX, offsetY }) {
|
|
28
|
+
if (Canvas.isDrawing) {
|
|
29
|
+
this.temporalShape.points([this.startX, this.startY, offsetX, offsetY]);
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
finish() {
|
|
33
|
+
Properties.tool = 'selector'
|
|
34
|
+
this.temporalShape = null
|
|
35
|
+
},
|
|
36
|
+
bindEvents(newArrow) {
|
|
37
|
+
newArrow.off('transform')
|
|
38
|
+
newArrow.off('transformend')
|
|
39
|
+
newArrow.off('dragend')
|
|
40
|
+
newArrow.on('transform', () => {
|
|
41
|
+
const pointerWidth = Math.pow(newArrow.strokeWidth(), 2) / 2
|
|
42
|
+
const pointerLength = Math.pow(newArrow.strokeWidth(), 2) / 2
|
|
43
|
+
const transformScale = newArrow.getAbsoluteScale().x
|
|
44
|
+
newArrow.pointerWidth(pointerWidth / transformScale);
|
|
45
|
+
newArrow.pointerLength(pointerLength / transformScale);
|
|
46
|
+
})
|
|
47
|
+
newArrow.on('transformend', Histories.record)
|
|
48
|
+
newArrow.on('dragend', Histories.record)
|
|
49
|
+
},
|
|
50
|
+
generate({ x, y, scaleX, scaleY, points, stroke, strokeWidth, pointerWidth, pointerLength }) {
|
|
51
|
+
const newArrow = new Konva.Arrow({
|
|
52
|
+
x,
|
|
53
|
+
y,
|
|
54
|
+
scaleX,
|
|
55
|
+
scaleY,
|
|
56
|
+
points,
|
|
57
|
+
stroke,
|
|
58
|
+
strokeWidth,
|
|
59
|
+
pointerWidth,
|
|
60
|
+
pointerLength,
|
|
61
|
+
name: 'node',
|
|
62
|
+
nodeType: 'arrow',
|
|
63
|
+
lineCap: 'round',
|
|
64
|
+
fillEnabled: false,
|
|
65
|
+
strokeScaleEnabled: false
|
|
66
|
+
})
|
|
67
|
+
this.bindEvents(newArrow)
|
|
68
|
+
return newArrow
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import Konva from 'konva'
|
|
2
|
+
import Properties from '../properties'
|
|
3
|
+
import Canvas from '../canvas'
|
|
4
|
+
import Histories from '../histories'
|
|
5
|
+
export default {
|
|
6
|
+
temporalShape: null,
|
|
7
|
+
startX: 0,
|
|
8
|
+
startY: 0,
|
|
9
|
+
show({ offsetX, offsetY }) {
|
|
10
|
+
Canvas.isDrawing = true;
|
|
11
|
+
this.startX = offsetX
|
|
12
|
+
this.startY = offsetY
|
|
13
|
+
const fill = Properties.fillColor
|
|
14
|
+
const stroke = Properties.strokeColor
|
|
15
|
+
const strokeWidth = Properties.strokeWidth
|
|
16
|
+
const newEllipse = this.generate({
|
|
17
|
+
x: offsetX,
|
|
18
|
+
y: offsetY,
|
|
19
|
+
radiusX: 1,
|
|
20
|
+
radiusY: 1,
|
|
21
|
+
fill,
|
|
22
|
+
stroke,
|
|
23
|
+
strokeWidth
|
|
24
|
+
})
|
|
25
|
+
this._checkEllipseAndChangeToVertex(newEllipse)
|
|
26
|
+
this.temporalShape = newEllipse
|
|
27
|
+
Canvas.layer.add(this.temporalShape)
|
|
28
|
+
},
|
|
29
|
+
change({ offsetX, offsetY }) {
|
|
30
|
+
if (Canvas.isDrawing) {
|
|
31
|
+
this.temporalShape.x((offsetX + this.startX) / 2);
|
|
32
|
+
this.temporalShape.y((offsetY + this.startY) / 2);
|
|
33
|
+
this.temporalShape.radiusX(Math.abs(offsetX - this.startX) / 2);
|
|
34
|
+
this.temporalShape.radiusY(Math.abs(offsetY - this.startY) / 2);
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
finish() {
|
|
38
|
+
Properties.tool = 'selector'
|
|
39
|
+
this.temporalShape = null
|
|
40
|
+
},
|
|
41
|
+
_checkEllipseAndChangeToVertex(newEllipse) {
|
|
42
|
+
setTimeout(() => {
|
|
43
|
+
if (!this.temporalShape && newEllipse.radiusX() < 5 && newEllipse.radiusY() < 5) {
|
|
44
|
+
newEllipse.radiusX(15)
|
|
45
|
+
newEllipse.radiusY(15)
|
|
46
|
+
}
|
|
47
|
+
}, 200)
|
|
48
|
+
},
|
|
49
|
+
generate({ x, y, radiusX, radiusY, scaleX, scaleY, fill, stroke, strokeWidth }) {
|
|
50
|
+
const newEllipse = new Konva.Ellipse({
|
|
51
|
+
x,
|
|
52
|
+
y,
|
|
53
|
+
radiusX,
|
|
54
|
+
radiusY,
|
|
55
|
+
scaleX,
|
|
56
|
+
scaleY,
|
|
57
|
+
fill,
|
|
58
|
+
stroke,
|
|
59
|
+
strokeWidth,
|
|
60
|
+
name: 'node',
|
|
61
|
+
nodeType: 'ellipse',
|
|
62
|
+
strokeScaleEnabled: false
|
|
63
|
+
})
|
|
64
|
+
this.bindEvents(newEllipse)
|
|
65
|
+
return newEllipse
|
|
66
|
+
},
|
|
67
|
+
bindEvents(newEllipse) {
|
|
68
|
+
newEllipse.off('transformend')
|
|
69
|
+
newEllipse.off('dragend')
|
|
70
|
+
newEllipse.on('transformend', Histories.record)
|
|
71
|
+
newEllipse.on('dragend', Histories.record)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import Konva from 'konva'
|
|
2
|
+
import Properties from '../properties'
|
|
3
|
+
import Canvas from '../canvas'
|
|
4
|
+
export default {
|
|
5
|
+
temporalCurveTrajectory: null,
|
|
6
|
+
temporalShape: null,
|
|
7
|
+
startX: 0,
|
|
8
|
+
startY: 0,
|
|
9
|
+
show({ offsetX, offsetY }) {
|
|
10
|
+
Canvas.isDrawing = true;
|
|
11
|
+
this.startX = offsetX
|
|
12
|
+
this.startY = offsetY
|
|
13
|
+
this.temporalCurveTrajectory = null
|
|
14
|
+
const strokeWidth = Properties.strokeWidth
|
|
15
|
+
const newCurve = new Konva.Line({
|
|
16
|
+
points: [offsetX, offsetY, offsetX, offsetY],
|
|
17
|
+
stroke: 'transparent',
|
|
18
|
+
strokeWidth,
|
|
19
|
+
name: 'node',
|
|
20
|
+
bezier: true,
|
|
21
|
+
lineCap: 'round',
|
|
22
|
+
strokeScaleEnabled: false
|
|
23
|
+
});
|
|
24
|
+
this.temporalShape = newCurve
|
|
25
|
+
Canvas.layer.add(this.temporalShape);
|
|
26
|
+
},
|
|
27
|
+
change({ offsetX, offsetY }) {
|
|
28
|
+
if (Canvas.isDrawing) {
|
|
29
|
+
if (!this.temporalCurveTrajectory) {
|
|
30
|
+
this.temporalCurveTrajectory = [this.startX, this.startY]
|
|
31
|
+
}
|
|
32
|
+
this.temporalCurveTrajectory.push(offsetX, offsetY)
|
|
33
|
+
this.temporalShape.points(this.temporalCurveTrajectory);
|
|
34
|
+
Canvas.stage.find('.node')
|
|
35
|
+
.filter(node => this._hasIntersectionOnEraserCurve(node))
|
|
36
|
+
.forEach(node => node.opacity(0.1));
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
finish() {
|
|
40
|
+
Canvas.stage.find('.node')
|
|
41
|
+
.filter(node => this._hasIntersectionOnEraserCurve(node))
|
|
42
|
+
.forEach(node => node.remove())
|
|
43
|
+
this.temporalShape = null
|
|
44
|
+
},
|
|
45
|
+
_hasIntersectionOnEraserCurve(node) {
|
|
46
|
+
return Konva.Util.haveIntersection(
|
|
47
|
+
this.temporalShape.getClientRect(),
|
|
48
|
+
node.getClientRect()
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|