@stonecrop/node-editor 0.2.24 → 0.2.25
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/dist/index.js +12 -0
- package/dist/node-editor.d.ts +28 -0
- package/dist/node-editor.js +6370 -6037
- package/dist/node-editor.js.map +1 -1
- package/dist/node-editor.umd.cjs +3 -2
- package/dist/node-editor.umd.cjs.map +1 -1
- package/dist/node_editor/src/tsdoc-metadata.json +11 -0
- package/dist/node_editor.tsbuildinfo +1 -0
- package/dist/src/index.d.ts +12 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/types/index.d.ts +27 -0
- package/dist/src/types/index.d.ts.map +1 -0
- package/dist/style.css +1 -1
- package/dist/types/index.js +0 -0
- package/package.json +26 -19
- package/src/components/EditableEdge.vue +36 -50
- package/src/components/EditableNode.vue +24 -42
- package/src/components/NodeEditor.vue +87 -112
- package/src/components/StateEditor.vue +78 -67
- package/src/index.ts +7 -1
- package/src/shims-vue.d.ts +5 -0
- package/src/types/index.ts +24 -0
- package/src/histoire.setup.ts +0 -7
|
@@ -1,35 +1,33 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="node-editor-wrapper" :class="
|
|
2
|
+
<div class="node-editor-wrapper" :class="nodeContainerClass" @mouseover="hover = true" @mouseleave="hover = false">
|
|
3
3
|
<div class="chart-controls">
|
|
4
4
|
<div class="chart-controls-left">
|
|
5
5
|
<div><b>Selected Node:</b> {{ activeElementKey ? activeElementKey : 'none' }}</div>
|
|
6
6
|
</div>
|
|
7
7
|
<div class="chart-controls-right">
|
|
8
8
|
<div>
|
|
9
|
-
<button class="button-default" @click="addNode
|
|
9
|
+
<button class="button-default" @click="addNode">Add Node</button>
|
|
10
10
|
</div>
|
|
11
11
|
<div>
|
|
12
|
-
<button class="button-default" @click="fitView
|
|
12
|
+
<button class="button-default" @click="fitView">Center</button>
|
|
13
13
|
</div>
|
|
14
14
|
<div v-if="activeElementIndex > -1">
|
|
15
|
-
<button class="button-default" @click="shiftInput
|
|
15
|
+
<button class="button-default" @click="shiftInput">Shift Input Position</button>
|
|
16
16
|
</div>
|
|
17
17
|
<div v-if="activeElementIndex > -1">
|
|
18
|
-
<button class="button-default" @click="shiftOutput
|
|
18
|
+
<button class="button-default" @click="shiftOutput">Shift Output Position</button>
|
|
19
19
|
</div>
|
|
20
20
|
</div>
|
|
21
21
|
</div>
|
|
22
22
|
|
|
23
23
|
<VueFlow
|
|
24
|
-
|
|
24
|
+
v-if="vueFlowElements && vueFlowElements.length"
|
|
25
25
|
class="nowheel"
|
|
26
26
|
:prevent-scrolling="true"
|
|
27
27
|
:zoom-on-scroll="false"
|
|
28
28
|
:fit-view-on-init="true"
|
|
29
|
-
v-if="vueFlowElements && vueFlowElements.length"
|
|
30
29
|
v-model="vueFlowElements"
|
|
31
|
-
@
|
|
32
|
-
@edge-double-click="onEdgeDoubleClick($event)">
|
|
30
|
+
@wheel.prevent="onWheel">
|
|
33
31
|
<template #node-editable="props">
|
|
34
32
|
<EditableNode v-bind="props" @change="labelChanged($event, props.id)" />
|
|
35
33
|
</template>
|
|
@@ -39,79 +37,62 @@
|
|
|
39
37
|
</VueFlow>
|
|
40
38
|
</div>
|
|
41
39
|
</template>
|
|
42
|
-
<script lang="ts" setup>
|
|
43
|
-
import { VueFlow, useVueFlow } from '@vue-flow/core'
|
|
44
|
-
import '@vue-flow/core/dist/style.css'
|
|
45
|
-
import '@vue-flow/core/dist/theme-default.css'
|
|
46
|
-
import EditableNode from './EditableNode.vue'
|
|
47
|
-
import EditableEdge from './EditableEdge.vue'
|
|
48
|
-
import { ref, computed, defineEmits, onBeforeUnmount, onMounted, watch, reactive } from 'vue'
|
|
49
40
|
|
|
50
|
-
|
|
41
|
+
<script setup lang="ts">
|
|
42
|
+
import { type VueFlowStore, Position, VueFlow, useVueFlow, Node } from '@vue-flow/core'
|
|
43
|
+
import { type HTMLAttributes, ref, computed, defineEmits, onBeforeUnmount, onMounted } from 'vue'
|
|
44
|
+
|
|
45
|
+
import EditableEdge from '@/components/EditableEdge.vue'
|
|
46
|
+
import EditableNode from '@/components/EditableNode.vue'
|
|
47
|
+
import type { FlowElements } from '@/types'
|
|
51
48
|
|
|
52
49
|
const props = defineProps<{
|
|
53
|
-
modelValue:
|
|
54
|
-
nodeContainerClass
|
|
50
|
+
modelValue: FlowElements
|
|
51
|
+
nodeContainerClass?: HTMLAttributes['class']
|
|
55
52
|
}>()
|
|
56
|
-
|
|
57
|
-
// Emits
|
|
58
|
-
|
|
59
53
|
const emit = defineEmits(['update:modelValue'])
|
|
60
54
|
|
|
61
|
-
// Data
|
|
62
|
-
|
|
63
|
-
const containerClass = ref('')
|
|
64
|
-
const vueFlowInstance = ref({})
|
|
65
55
|
const hover = ref(false)
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
y: 0,
|
|
69
|
-
})
|
|
56
|
+
const vueFlowElements = ref<FlowElements>([])
|
|
57
|
+
const vueFlowInstance = ref<Partial<VueFlowStore>>()
|
|
70
58
|
|
|
71
59
|
const activeElementKey = ref('')
|
|
72
|
-
|
|
73
|
-
const vueFlowElements = ref([])
|
|
74
|
-
|
|
75
|
-
// Computed variables
|
|
76
|
-
|
|
77
60
|
const activeElementIndex = computed(() => {
|
|
78
|
-
|
|
79
|
-
if (
|
|
80
|
-
|
|
61
|
+
vueFlowElements.value.forEach((element, index) => {
|
|
62
|
+
if (element.id === activeElementKey.value) {
|
|
63
|
+
return index
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
|
|
81
67
|
return -1
|
|
82
68
|
})
|
|
83
69
|
|
|
84
70
|
const elements = computed({
|
|
85
71
|
get: () => {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
_elements[j].data.hasOutput = true
|
|
98
|
-
} else if (_elements[j].type == 'output') {
|
|
99
|
-
_elements[j].data.hasInput = true
|
|
100
|
-
_elements[j].data.hasOutput = false
|
|
72
|
+
const _elements = props.modelValue
|
|
73
|
+
|
|
74
|
+
// Add data to each element
|
|
75
|
+
for (const _element of _elements) {
|
|
76
|
+
_element.data = {}
|
|
77
|
+
if (_element.type === 'input') {
|
|
78
|
+
_element.data.hasInput = false
|
|
79
|
+
_element.data.hasOutput = true
|
|
80
|
+
} else if (_element.type === 'output') {
|
|
81
|
+
_element.data.hasInput = true
|
|
82
|
+
_element.data.hasOutput = false
|
|
101
83
|
} else {
|
|
102
|
-
|
|
103
|
-
|
|
84
|
+
_element.data.hasInput = true
|
|
85
|
+
_element.data.hasOutput = true
|
|
104
86
|
}
|
|
105
|
-
|
|
106
|
-
|
|
87
|
+
_element.class = 'vue-flow__node-default'
|
|
88
|
+
_element.type = 'editable'
|
|
107
89
|
}
|
|
108
90
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
_elements[j].events = {
|
|
91
|
+
// Add click event to each element
|
|
92
|
+
for (const _element of _elements) {
|
|
93
|
+
_element.events = {
|
|
113
94
|
click: () => {
|
|
114
|
-
activeElementKey.value =
|
|
95
|
+
activeElementKey.value = _element.id
|
|
115
96
|
},
|
|
116
97
|
}
|
|
117
98
|
}
|
|
@@ -123,20 +104,6 @@ const elements = computed({
|
|
|
123
104
|
},
|
|
124
105
|
})
|
|
125
106
|
|
|
126
|
-
//VueFlow
|
|
127
|
-
|
|
128
|
-
const { getNodes, onPaneReady } = useVueFlow({})
|
|
129
|
-
|
|
130
|
-
onPaneReady(i => {
|
|
131
|
-
vueFlowInstance.value = i
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
// Setup
|
|
135
|
-
|
|
136
|
-
vueFlowElements.value = elements.value
|
|
137
|
-
|
|
138
|
-
// Lifecycle Hooks
|
|
139
|
-
|
|
140
107
|
onMounted(() => {
|
|
141
108
|
document.removeEventListener('keypress', handleKeypress)
|
|
142
109
|
document.addEventListener('keypress', handleKeypress)
|
|
@@ -146,70 +113,74 @@ onBeforeUnmount(() => {
|
|
|
146
113
|
document.removeEventListener('keypress', handleKeypress)
|
|
147
114
|
})
|
|
148
115
|
|
|
149
|
-
|
|
116
|
+
const { onPaneReady } = useVueFlow()
|
|
117
|
+
onPaneReady(instance => {
|
|
118
|
+
vueFlowInstance.value = instance
|
|
119
|
+
})
|
|
150
120
|
|
|
151
|
-
|
|
121
|
+
vueFlowElements.value = elements.value
|
|
122
|
+
|
|
123
|
+
// Methods
|
|
124
|
+
const shiftTerminal = (currentTerminal: Position) => {
|
|
152
125
|
return {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
126
|
+
[Position.Top]: Position.Right,
|
|
127
|
+
[Position.Right]: Position.Bottom,
|
|
128
|
+
[Position.Bottom]: Position.Left,
|
|
129
|
+
[Position.Left]: Position.Top,
|
|
157
130
|
}[currentTerminal]
|
|
158
131
|
}
|
|
159
132
|
|
|
160
133
|
const shiftOutput = () => {
|
|
161
134
|
if (activeElementIndex.value > -1) {
|
|
162
|
-
vueFlowElements.value[activeElementIndex.value]
|
|
163
|
-
|
|
164
|
-
)
|
|
135
|
+
const activeNode = vueFlowElements.value[activeElementIndex.value] as Node
|
|
136
|
+
activeNode.sourcePosition = shiftTerminal(activeNode.sourcePosition)
|
|
165
137
|
}
|
|
166
138
|
}
|
|
167
139
|
|
|
168
140
|
const shiftInput = () => {
|
|
169
141
|
if (activeElementIndex.value > -1) {
|
|
170
|
-
vueFlowElements.value[activeElementIndex.value]
|
|
171
|
-
|
|
172
|
-
)
|
|
142
|
+
const activeNode = vueFlowElements.value[activeElementIndex.value] as Node
|
|
143
|
+
activeNode.targetPosition = shiftTerminal(activeNode.targetPosition)
|
|
173
144
|
}
|
|
174
145
|
}
|
|
175
146
|
|
|
176
|
-
const onWheel =
|
|
177
|
-
window.scrollBy(0,
|
|
147
|
+
const onWheel = (event: WheelEvent) => {
|
|
148
|
+
window.scrollBy(0, event.deltaY)
|
|
178
149
|
}
|
|
179
150
|
|
|
180
|
-
const handleKeypress =
|
|
181
|
-
if (hover.value &&
|
|
182
|
-
if (
|
|
183
|
-
vueFlowInstance.value.zoomIn()
|
|
151
|
+
const handleKeypress = (event: KeyboardEvent) => {
|
|
152
|
+
if (hover.value && event.ctrlKey == true) {
|
|
153
|
+
if (event.key == '+' || event.key == '=') {
|
|
154
|
+
void vueFlowInstance.value.zoomIn()
|
|
184
155
|
}
|
|
185
|
-
if (
|
|
186
|
-
vueFlowInstance.value.zoomOut()
|
|
156
|
+
if (event.key == '-') {
|
|
157
|
+
void vueFlowInstance.value.zoomOut()
|
|
187
158
|
}
|
|
188
159
|
}
|
|
189
160
|
}
|
|
190
161
|
|
|
191
|
-
const fitView = () => {
|
|
192
|
-
vueFlowInstance.value.fitView()
|
|
162
|
+
const fitView = async () => {
|
|
163
|
+
await vueFlowInstance.value.fitView()
|
|
193
164
|
}
|
|
194
165
|
|
|
195
166
|
const addNode = () => {
|
|
196
|
-
let newNodePosition = { x: Math.random() * 200, y: Math.random() * 200 }
|
|
197
167
|
let makeEdge = false
|
|
168
|
+
let newNodePosition = { x: Math.random() * 200, y: Math.random() * 200 }
|
|
198
169
|
if (activeElementIndex.value > -1) {
|
|
199
170
|
const activeNode = vueFlowElements.value[activeElementIndex.value]
|
|
200
171
|
if (activeNode.data.hasOutput) {
|
|
201
|
-
newNodePosition = { x: activeNode.position.x + 200, y: activeNode.position.y + 50 }
|
|
172
|
+
newNodePosition = { x: (activeNode as Node).position.x + 200, y: (activeNode as Node).position.y + 50 }
|
|
202
173
|
makeEdge = true
|
|
203
174
|
}
|
|
204
175
|
}
|
|
205
176
|
|
|
206
|
-
|
|
207
|
-
|
|
177
|
+
const id = vueFlowElements.value.length
|
|
178
|
+
const nodeId = `node-${id}`
|
|
208
179
|
vueFlowElements.value.push({
|
|
209
180
|
id: nodeId,
|
|
210
181
|
label: 'Node ' + id,
|
|
211
|
-
sourcePosition:
|
|
212
|
-
targetPosition:
|
|
182
|
+
sourcePosition: Position.Right,
|
|
183
|
+
targetPosition: Position.Left,
|
|
213
184
|
class: 'vue-flow__node-default',
|
|
214
185
|
type: 'editable',
|
|
215
186
|
data: {
|
|
@@ -243,14 +214,6 @@ const addNode = () => {
|
|
|
243
214
|
}
|
|
244
215
|
}
|
|
245
216
|
|
|
246
|
-
const onConnect = e => {
|
|
247
|
-
console.log('edge connect', e)
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
const onEdgeDoubleClick = e => {
|
|
251
|
-
console.log('edge double click', e)
|
|
252
|
-
}
|
|
253
|
-
|
|
254
217
|
const labelChanged = (e, id) => {
|
|
255
218
|
for (let j = 0; j < vueFlowElements.value.length; j++) {
|
|
256
219
|
if (vueFlowElements.value[j].id == id) {
|
|
@@ -260,6 +223,7 @@ const labelChanged = (e, id) => {
|
|
|
260
223
|
}
|
|
261
224
|
}
|
|
262
225
|
</script>
|
|
226
|
+
|
|
263
227
|
<style>
|
|
264
228
|
@import '@vue-flow/core/dist/style.css';
|
|
265
229
|
@import '@vue-flow/core/dist/theme-default.css';
|
|
@@ -272,9 +236,11 @@ const labelChanged = (e, id) => {
|
|
|
272
236
|
align-items: center;
|
|
273
237
|
padding-top: 0.2em;
|
|
274
238
|
}
|
|
239
|
+
|
|
275
240
|
.chart-controls-right div {
|
|
276
241
|
margin-left: 5px;
|
|
277
242
|
}
|
|
243
|
+
|
|
278
244
|
.chart-controls {
|
|
279
245
|
padding-left: 20px;
|
|
280
246
|
padding-right: 20px;
|
|
@@ -284,26 +250,32 @@ const labelChanged = (e, id) => {
|
|
|
284
250
|
flex-direction: row;
|
|
285
251
|
justify-content: space-between;
|
|
286
252
|
}
|
|
253
|
+
|
|
287
254
|
.chart-controls div {
|
|
288
255
|
margin-bottom: 5px;
|
|
289
256
|
}
|
|
257
|
+
|
|
290
258
|
.defaultContainerClass {
|
|
291
259
|
height: 90vh;
|
|
292
260
|
width: 100%;
|
|
293
261
|
border: 1px solid #ccc;
|
|
294
262
|
}
|
|
263
|
+
|
|
295
264
|
.default-input-node.vue-flow__node-input,
|
|
296
265
|
.default-output-node.vue-flow__node-output {
|
|
297
266
|
border-color: #000;
|
|
298
267
|
}
|
|
268
|
+
|
|
299
269
|
.default-input-node.vue-flow__node-input .vue-flow__handle,
|
|
300
270
|
.default-output-node.vue-flow__node-output .vue-flow__handle {
|
|
301
271
|
background-color: #000;
|
|
302
272
|
}
|
|
273
|
+
|
|
303
274
|
.default-input-node.vue-flow__node-input.selected,
|
|
304
275
|
.default-output-node.vue-flow__node-output.selected {
|
|
305
276
|
box-shadow: 0 0 0 0.5px #000;
|
|
306
277
|
}
|
|
278
|
+
|
|
307
279
|
button.button-default {
|
|
308
280
|
background-color: #ffffff;
|
|
309
281
|
padding: 1px 12px;
|
|
@@ -316,14 +288,17 @@ button.button-default {
|
|
|
316
288
|
button.button-default:hover {
|
|
317
289
|
background-color: #f2f2f2;
|
|
318
290
|
}
|
|
291
|
+
|
|
319
292
|
.vue-flow {
|
|
320
293
|
background-size: 40px 40px;
|
|
321
294
|
background-image: linear-gradient(to right, #ccc 1px, transparent 1px),
|
|
322
295
|
linear-gradient(to bottom, #ccc 1px, transparent 1px);
|
|
323
296
|
}
|
|
297
|
+
|
|
324
298
|
input.label-editor {
|
|
325
299
|
position: absolute;
|
|
326
300
|
}
|
|
301
|
+
|
|
327
302
|
.node-editor-wrapper {
|
|
328
303
|
position: relative;
|
|
329
304
|
}
|
|
@@ -3,120 +3,131 @@
|
|
|
3
3
|
<NodeEditor v-model="elements" :node-container-class="nodeContainerClass" />
|
|
4
4
|
</div>
|
|
5
5
|
</template>
|
|
6
|
-
<script lang="ts" setup>
|
|
7
|
-
import { VueFlow } from '@vue-flow/core'
|
|
8
|
-
import '@vue-flow/core/dist/style.css'
|
|
9
|
-
import '@vue-flow/core/dist/theme-default.css'
|
|
10
|
-
import NodeEditor from './NodeEditor.vue'
|
|
11
|
-
import { ref, computed } from 'vue'
|
|
12
6
|
|
|
13
|
-
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
import { type Node, Position } from '@vue-flow/core'
|
|
9
|
+
import { type HTMLAttributes, computed } from 'vue'
|
|
14
10
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
// Emits
|
|
11
|
+
import NodeEditor from '@/components/NodeEditor.vue'
|
|
12
|
+
import type { EditorStates, FlowElement, FlowElements, Layout } from '@/types'
|
|
18
13
|
|
|
14
|
+
const states = defineModel<EditorStates>()
|
|
15
|
+
const props = defineProps<{
|
|
16
|
+
layout: Layout
|
|
17
|
+
nodeContainerClass?: HTMLAttributes['class']
|
|
18
|
+
}>()
|
|
19
19
|
const emit = defineEmits(['update:modelValue'])
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const elements = computed({
|
|
21
|
+
const elements = computed<FlowElements>({
|
|
24
22
|
get: () => {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
let
|
|
30
|
-
for (
|
|
31
|
-
|
|
32
|
-
let el = {
|
|
23
|
+
const hasInputs = {}
|
|
24
|
+
const stateElements: FlowElements = []
|
|
25
|
+
const stateHash: Record<string, FlowElement> = {}
|
|
26
|
+
|
|
27
|
+
let index = 0
|
|
28
|
+
for (const [key, value] of Object.entries(states.value)) {
|
|
29
|
+
const el: Node = {
|
|
33
30
|
id: key,
|
|
34
31
|
label: key,
|
|
35
|
-
position: props.layout[key]
|
|
36
|
-
targetPosition:
|
|
37
|
-
|
|
38
|
-
sourcePosition:
|
|
39
|
-
props.layout[key] && props.layout[key].sourcePosition ? props.layout[key].sourcePosition : 'right',
|
|
32
|
+
position: props.layout[key]?.position || { x: 200 * index, y: 100 },
|
|
33
|
+
targetPosition: props.layout[key]?.targetPosition || Position.Left,
|
|
34
|
+
sourcePosition: props.layout[key]?.sourcePosition || Position.Right,
|
|
40
35
|
}
|
|
41
|
-
|
|
36
|
+
|
|
37
|
+
if (value.type === 'final') {
|
|
42
38
|
el.type = 'output'
|
|
43
39
|
el.class = 'default-output-node'
|
|
44
40
|
}
|
|
41
|
+
|
|
45
42
|
stateHash[key] = el
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
43
|
+
|
|
44
|
+
if (value.on) {
|
|
45
|
+
for (const [edgeKey, edgeValue] of Object.entries(value.on)) {
|
|
46
|
+
if (Array.isArray(edgeValue)) {
|
|
47
|
+
for (const edge of edgeValue) {
|
|
48
|
+
// TODO: handle typescript errors for both types of states
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
|
50
|
+
const edgeJson = edge.toJSON()
|
|
51
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
|
52
|
+
const target = edgeJson.target.toString()
|
|
53
|
+
stateElements.push({
|
|
54
|
+
id: `${key}-${edgeKey}`,
|
|
55
|
+
target: target,
|
|
56
|
+
source: key,
|
|
57
|
+
label: edgeKey,
|
|
58
|
+
animated: true,
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
62
|
+
hasInputs[target] = true
|
|
63
|
+
}
|
|
64
|
+
}
|
|
51
65
|
}
|
|
52
|
-
stateElements.push({
|
|
53
|
-
id: `${key}-${edges[edgeKey]}-${edgeKey}`,
|
|
54
|
-
target: target,
|
|
55
|
-
source: key,
|
|
56
|
-
label: edgeKey,
|
|
57
|
-
animated: true,
|
|
58
|
-
})
|
|
59
|
-
hasInputs[target] = true
|
|
60
66
|
}
|
|
61
|
-
|
|
67
|
+
|
|
68
|
+
index++
|
|
62
69
|
}
|
|
63
|
-
|
|
70
|
+
|
|
71
|
+
for (const [key, value] of Object.entries(stateHash)) {
|
|
64
72
|
if (!hasInputs[key]) {
|
|
65
|
-
|
|
66
|
-
|
|
73
|
+
value['type'] = 'input'
|
|
74
|
+
value['class'] = 'default-input-node'
|
|
67
75
|
}
|
|
68
|
-
stateElements.push(
|
|
76
|
+
stateElements.push(value)
|
|
69
77
|
}
|
|
78
|
+
|
|
70
79
|
return stateElements
|
|
71
80
|
},
|
|
72
81
|
set: newValue => {
|
|
73
82
|
// update modelValue when elements change
|
|
74
83
|
onElementsChange(newValue)
|
|
75
|
-
|
|
84
|
+
|
|
85
|
+
// TODO: emit('update:modelValue', props.modelValue)
|
|
76
86
|
},
|
|
77
87
|
})
|
|
78
88
|
|
|
79
|
-
|
|
89
|
+
const onElementsChange = (elements: FlowElements) => {
|
|
90
|
+
const edges: Record<string, Record<string, any>> = {}
|
|
91
|
+
const idToLabel: Record<string, string> = {}
|
|
92
|
+
const states: EditorStates = {}
|
|
80
93
|
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
for (let i = 0; i < elements.length; i++) {
|
|
86
|
-
let el = elements[i]
|
|
87
|
-
if (el.type == 'input') {
|
|
94
|
+
for (const el of elements) {
|
|
95
|
+
const label = el.label as string
|
|
96
|
+
|
|
97
|
+
if (el.type === 'input') {
|
|
88
98
|
// it's an input node
|
|
89
|
-
states[
|
|
99
|
+
states[label] = {
|
|
90
100
|
on: {},
|
|
91
101
|
}
|
|
92
|
-
} else if (el.type
|
|
102
|
+
} else if (el.type === 'output') {
|
|
93
103
|
// it's an output node
|
|
94
|
-
states[
|
|
104
|
+
states[label] = {
|
|
95
105
|
type: 'final',
|
|
96
106
|
}
|
|
97
|
-
} else if (el.source && el.target) {
|
|
107
|
+
} /* else if (el.source && el.target) {
|
|
98
108
|
// it's an edge
|
|
99
109
|
edges[el.source] = edges[el.source] || {}
|
|
100
|
-
edges[el.source][
|
|
110
|
+
edges[el.source][label] = {
|
|
101
111
|
target: el.target,
|
|
102
112
|
}
|
|
103
|
-
} else {
|
|
113
|
+
} */ else {
|
|
104
114
|
// it's a state
|
|
105
|
-
states[
|
|
115
|
+
states[label] = {
|
|
106
116
|
on: {},
|
|
107
117
|
}
|
|
108
118
|
}
|
|
109
|
-
|
|
119
|
+
|
|
120
|
+
idToLabel[el.id] = label
|
|
110
121
|
}
|
|
111
122
|
|
|
112
|
-
for (
|
|
123
|
+
for (const [edgeKey, edgeValue] of Object.entries(edges)) {
|
|
113
124
|
// add edges to states
|
|
114
|
-
|
|
115
|
-
for (
|
|
116
|
-
states[label].on[
|
|
125
|
+
const label = idToLabel[edgeKey]
|
|
126
|
+
for (const [key, value] of Object.entries(edgeValue)) {
|
|
127
|
+
states[label].on[key] = value
|
|
117
128
|
}
|
|
118
129
|
}
|
|
130
|
+
|
|
119
131
|
emit('update:modelValue', states)
|
|
120
132
|
}
|
|
121
133
|
</script>
|
|
122
|
-
<style scoped></style>
|
package/src/index.ts
CHANGED
|
@@ -2,8 +2,14 @@ import { App } from 'vue'
|
|
|
2
2
|
|
|
3
3
|
import NodeEditor from '@/components/NodeEditor.vue'
|
|
4
4
|
import StateEditor from '@/components/StateEditor.vue'
|
|
5
|
+
export type { EditorStates, FlowElement, FlowElements, Layout } from '@/types'
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Install all Node Editor components
|
|
9
|
+
* @param app - Vue app instance
|
|
10
|
+
* @public
|
|
11
|
+
*/
|
|
12
|
+
function install(app: App) {
|
|
7
13
|
app.component('NodeEditor', NodeEditor)
|
|
8
14
|
app.component('StateEditor', StateEditor)
|
|
9
15
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type Elements, type Element, type XYPosition, Position } from '@vue-flow/core'
|
|
2
|
+
import type { AnyStateMachine, AnyStateNodeDefinition, StatesConfig } from 'xstate'
|
|
3
|
+
|
|
4
|
+
export type EditorStates = {
|
|
5
|
+
[key: string]: Partial<AnyStateMachine | AnyStateNodeDefinition> | StatesConfig<any, any, any>
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type FlowElements = Elements<
|
|
9
|
+
{ hasInput?: boolean; hasOutput?: boolean },
|
|
10
|
+
{ hasInput?: boolean; hasOutput?: boolean }
|
|
11
|
+
>
|
|
12
|
+
|
|
13
|
+
export type FlowElement = Element<
|
|
14
|
+
{ hasInput?: boolean; hasOutput?: boolean },
|
|
15
|
+
{ hasInput?: boolean; hasOutput?: boolean }
|
|
16
|
+
>
|
|
17
|
+
|
|
18
|
+
export type Layout = {
|
|
19
|
+
[key: string]: {
|
|
20
|
+
position?: XYPosition
|
|
21
|
+
targetPosition?: Position
|
|
22
|
+
sourcePosition?: Position
|
|
23
|
+
}
|
|
24
|
+
}
|
package/src/histoire.setup.ts
DELETED