@live-change/flow-frontend 0.3.15

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.
@@ -0,0 +1,37 @@
1
+ <template>
2
+ <div @mousedown="handleMouseDown"
3
+ @touchstart="handleTouchStart"
4
+ style="cursor: move">
5
+ <slot />
6
+ </div>
7
+ </template>
8
+
9
+ <script setup>
10
+
11
+ import { defineProps, defineEmits, toRefs, inject } from "vue"
12
+
13
+ const props = defineProps({
14
+ node: {
15
+ type: Object,
16
+ required: true
17
+ }
18
+ })
19
+
20
+ const { node } = toRefs(props)
21
+
22
+ const flow = inject("flow")
23
+
24
+ function handleMouseDown(event) {
25
+ event.preventDefault()
26
+ flow.startDragNode(node.value, event)
27
+ }
28
+
29
+ function handleTouchStart(event) {
30
+
31
+ }
32
+
33
+ </script>
34
+
35
+ <style scoped>
36
+
37
+ </style>
@@ -0,0 +1,71 @@
1
+ <template>
2
+ <div @mousedown="handleMouseDown"
3
+ @touchstart="handleTouchStart"
4
+ ref="element"
5
+ style="cursor: crosshair">
6
+ <slot />
7
+ </div>
8
+ </template>
9
+
10
+ <script setup>
11
+
12
+ import { defineProps, defineEmits, toRefs, inject, watch, onUnmounted, ref } from "vue"
13
+
14
+ const props = defineProps({
15
+ node: {
16
+ type: Object,
17
+ required: true
18
+ },
19
+ portId: {
20
+ type: String,
21
+ required: true
22
+ },
23
+ direction: {
24
+ type: Object,
25
+ default: () => ({ x: 0, y: 0 }),
26
+ properties: {
27
+ x: {
28
+ type: Number,
29
+ required: true
30
+ },
31
+ y: {
32
+ type: Number,
33
+ required: true
34
+ }
35
+ }
36
+ },
37
+ newEdgeOptions: {
38
+ type: Object,
39
+ default: () => ({})
40
+ }
41
+ })
42
+
43
+ const { node, portId, direction, newEdgeOptions } = toRefs(props)
44
+
45
+ const flow = inject("flow")
46
+
47
+ const element = ref(null)
48
+
49
+ watch(() => element.value, (element) => {
50
+ flow.setPortElement(node.value, portId.value, element, props.direction)
51
+ }, { immediate: true })
52
+
53
+ onUnmounted(() => {
54
+ flow.setPortElement(node.value, portId.value, null)
55
+ })
56
+
57
+ function handleMouseDown(event) {
58
+ event.preventDefault()
59
+ console.log("start draw edge", node.value.id, portId.value)
60
+ flow.startDrawEdge(node.value, portId.value, event, newEdgeOptions)
61
+ }
62
+
63
+ function handleTouchStart(event) {
64
+
65
+ }
66
+
67
+ </script>
68
+
69
+ <style scoped>
70
+
71
+ </style>
@@ -0,0 +1,417 @@
1
+ import { ref, unref, reactive, computed, inject, provide, watch } from "vue"
2
+
3
+ export function useFlow(options) {
4
+ const existingFlow = inject("flow")
5
+ if(existingFlow && options) throw new Error("Flow already exists")
6
+ if(existingFlow) return existingFlow
7
+
8
+ const nodes = options?.nodes ?? ref([])
9
+ const edges = options?.edges ?? ref([])
10
+
11
+ const dragState = ref(null)
12
+ const container = ref(null)
13
+ const viewport = ref(null)
14
+
15
+ const nodeViews = reactive(new Map())
16
+
17
+ const size = reactive({
18
+ width: options.width ?? 1024,
19
+ height: options.height ?? 1024,
20
+ })
21
+
22
+ const position = reactive({ x: 0, y: 0 })
23
+ const scale = ref(1)
24
+
25
+ function updatePositionAndScale(x, y, s) {
26
+ const viewportRect = viewport.value.getBoundingClientRect()
27
+ const vWidth = viewportRect.width
28
+ const vHeight = viewportRect.height
29
+ if(s < vWidth / size.width) s = vWidth / size.width
30
+ if(s < vHeight / size.height) s = vHeight / size.height
31
+ if(options.maxZoom && s > options.maxZoom) s = options.maxZoom
32
+ if(options.minZoom && s < options.minZoom) s = options.minZoom
33
+
34
+ if(x > 0) x = 0
35
+ if(y > 0) y = 0
36
+ if(x + size.width * s < vWidth) x = vWidth - size.width * s
37
+ if(y + size.height * s < vHeight) y = vHeight - size.height * s
38
+ position.x = x
39
+ position.y = y
40
+ scale.value = s
41
+ }
42
+
43
+ function zoom(event, by) {
44
+ const viewportRect = viewport.value.getBoundingClientRect()
45
+ const vWidth = viewportRect.width
46
+ const vHeight = viewportRect.height
47
+
48
+ const zoomEventX = event.clientX - viewportRect.x
49
+ const zoomEventY = event.clientY - viewportRect.y
50
+
51
+ const zoomCenterX = zoomEventX / scale.value - position.x / scale.value
52
+ const zoomCenterY = zoomEventY / scale.value - position.y / scale.value
53
+
54
+ let s = scale.value * by
55
+
56
+ if(s < vWidth / size.width) s = vWidth / size.width
57
+ if(s < vHeight / size.height) s = vHeight / size.height
58
+ if(options.maxZoom && s > options.maxZoom) s = options.maxZoom
59
+ if(options.minZoom && s < options.minZoom) s = options.minZoom
60
+
61
+ const newBy = s / scale.value
62
+
63
+ const newZoomCenterX = zoomCenterX * newBy
64
+ const newZoomCenterY = zoomCenterY * newBy
65
+
66
+ const dx = (newZoomCenterX - zoomCenterX) * scale.value
67
+ const dy = (newZoomCenterY - zoomCenterY) * scale.value
68
+
69
+ updatePositionAndScale(position.x - dx, position.y - dy, s)
70
+ }
71
+
72
+ function getEventPosition(event) {
73
+ const containerRect = container.value.getBoundingClientRect()
74
+ return {
75
+ x: (event.clientX - containerRect.left) / scale.value,
76
+ y: (event.clientY - containerRect.top) / scale.value
77
+ }
78
+ }
79
+
80
+ function getElementPosition(element, at = [0,0]) {
81
+ const elementRect = element.getBoundingClientRect()
82
+ const containerRect = container.value.getBoundingClientRect()
83
+ return {
84
+ x: (elementRect.left + elementRect.width * at[0] - containerRect.left) / scale.value,
85
+ y: (elementRect.top + elementRect.height * at[1] - containerRect.top) / scale.value,
86
+ }
87
+ }
88
+
89
+ function getElementRect(element) {
90
+ const containerRect = container.value.getBoundingClientRect()
91
+ const elementRect = element.getBoundingClientRect()
92
+ return {
93
+ x: (elementRect.left - containerRect.left)/scale.value,
94
+ y: (elementRect.top - containerRect.top)/scale.value,
95
+ width: elementRect.width / scale.value,
96
+ height: elementRect.height / scale.value,
97
+ }
98
+ }
99
+
100
+ function findNode(nodeId) {
101
+ if(options.findNode) return options.findNode(nodeId)
102
+ if(options.findNodeIndex) return unref(nodes)[options.findNodeIndex(nodeId)]
103
+ return unref(nodes).value.find(n => n.id == node)
104
+ }
105
+ function findNodeIndex(nodeId) {
106
+ nodeId = getNodeId(nodeId)
107
+ if(options.findNodeIndex) return options.findNodeIndex(nodeId)
108
+ return unref(nodes).value.findIndex(n => n.id == node)
109
+ }
110
+ function getNodeId(nodeId) {
111
+ if(typeof nodeId == "string") return nodeId
112
+ if(options.getNodeId) return options.getNodeId(nodeId)
113
+ return nodeId.id
114
+ }
115
+ function getNode(node) {
116
+ if(typeof node == "string") return findNode(node)
117
+ return node
118
+ }
119
+
120
+ function findEdge(edgeId) {
121
+ if(options.findEdge) return options.findEdge(edgeId)
122
+ if(options.findEdgeIndex) return unref(edges)[options.findEdgeIndex(edgeId)]
123
+ return unref(edges).value.find(e => e.id == edgeId)
124
+ }
125
+ function findEdgeIndex(edgeId) {
126
+ edgeId = getEdgeId(edgeId)
127
+ if(options.findEdgeIndex) return options.findEdgeIndex(edgeId)
128
+ return unref(edges).value.findIndex(e => e.id == edgeId)
129
+ }
130
+ function getEdgeId(edgeId) {
131
+ if(typeof edgeId == "string") return edgeId
132
+ if(options.getEdgeId) return options.getEdgeId(edgeId)
133
+ return edgeId.id
134
+ }
135
+ function getEdge(edge) {
136
+ if(typeof edge == "string") return findEdge(edge)
137
+ return edge
138
+ }
139
+
140
+ function setNodeElement(nodeId, element) {
141
+ nodeId = getNodeId(nodeId)
142
+ //console.log("setNodeElement", nodeId, element)
143
+ if(!element) {
144
+ nodeViews.delete(nodeId)
145
+ return
146
+ }
147
+ const nodeView = nodeViews.get(nodeId)
148
+ if(!nodeView) {
149
+ nodeViews.set(nodeId, {
150
+ node: nodeId,
151
+ element,
152
+ rect: getElementRect(element),
153
+ ports: reactive(new Map()),
154
+ })
155
+ return
156
+ }
157
+ nodeView.element = element
158
+ }
159
+
160
+ function setPortElement(nodeId, portId, element, direction) {
161
+ nodeId = getNodeId(nodeId)
162
+ if (typeof portId != "string") portId = portId.id ?? `${nodeId}/${portId.name}`
163
+ console.log("setPortElement", nodeId, portId, element)
164
+ const nodeView = nodeViews.get(nodeId)
165
+ if(!nodeView) {
166
+ return
167
+ }
168
+ if(!element) {
169
+ nodeView.ports.delete(portId)
170
+ return
171
+ }
172
+ nodeView.ports.set(portId, {
173
+ port: portId,
174
+ element,
175
+ rect: getElementRect(element),
176
+ direction,
177
+ })
178
+ }
179
+
180
+ function invalidatePortView(portView) {
181
+ portView.rect = getElementRect(portView.element)
182
+ }
183
+
184
+ function invalidateNodeView(nodeView) {
185
+ if(!nodeView.element) {
186
+ if(typeof nodeView == "string") {
187
+ nodeView = nodeViews.get(nodeView)
188
+ } else {
189
+ nodeView = nodeViews.get(getNodeId(nodeView))
190
+ }
191
+ }
192
+ nodeView.rect = getElementRect(nodeView.element)
193
+ for(const portView of nodeView.ports.values()) {
194
+ invalidatePortView(portView)
195
+ }
196
+ }
197
+
198
+ function getPortView(nodeId, portId) {
199
+ nodeId = getNodeId(nodeId)
200
+ if (typeof portId != "string") portId = portId.id ?? `${nodeId}/${portId.name}`
201
+ const nodeView = nodeViews.get(nodeId)
202
+ if(!nodeView) return null
203
+ return nodeView.ports.get(portId)
204
+ }
205
+
206
+ function getNearestPortView(position, maxDistance = 50, filter = (nodeId, portId) => true) {
207
+ const maxNodeDistance = maxDistance * 2
208
+ const maxDistanceSqr = maxDistance ** 2
209
+ let nearestPortView = null
210
+ let nearestNodeView = null
211
+ let nearestDistanceSqr = Infinity
212
+ for(const nodeView of nodeViews.values()) {
213
+ // check if position is near node
214
+ const rect = nodeView.rect
215
+ //console.log("getNearestPortView", nodeView.node, nodeView.rect)
216
+ if(position.x < rect.x - maxNodeDistance) continue
217
+ if(position.x > rect.x + rect.width + maxNodeDistance) continue
218
+ if(position.y < rect.y - maxNodeDistance) continue
219
+ if(position.y > rect.y + rect.height + maxNodeDistance) continue
220
+ //console.log('dist ok')
221
+ if(filter(nodeView.node, null) === false) continue
222
+ //console.log('node ok')
223
+ // check all ports
224
+ for(const portView of nodeView.ports.values()) {
225
+ const portPosition = {
226
+ x: portView.rect.x + portView.rect.width / 2,
227
+ y: portView.rect.y + portView.rect.height / 2,
228
+ }
229
+ //console.log("port", portView.port, portPosition)
230
+ const distanceSqr = (position.x - portPosition.x) ** 2 + (position.y - portPosition.y) ** 2
231
+ if(distanceSqr > maxDistanceSqr) continue
232
+ //console.log("port dist ok")
233
+ if(filter(nodeView.node, portView.port) === false) continue
234
+ if(distanceSqr < nearestDistanceSqr) {
235
+ nearestPortView = portView
236
+ nearestNodeView = nodeView
237
+ nearestDistanceSqr = distanceSqr
238
+ }
239
+ }
240
+ }
241
+ return {
242
+ portView: nearestPortView,
243
+ nodeView: nearestNodeView,
244
+ distance: Math.sqrt(nearestDistanceSqr),
245
+ }
246
+ }
247
+
248
+ watch(() => container.value, () => {
249
+ for(const nodeView of nodeViews.values()) {
250
+ invalidateNodeView(nodeView)
251
+ }
252
+ }, { immediate: true })
253
+
254
+ function startDragNode(node, event) {
255
+ node = getNode(node)
256
+ dragState.value = {
257
+ type: "dragNode",
258
+ node,
259
+ initialPosition: {
260
+ x: node.position.x,
261
+ y: node.position.y
262
+ },
263
+ startPosition: getEventPosition(event)
264
+ }
265
+ }
266
+
267
+ function startDrawEdge(nodeId, portId, event, newEdgeOptions) {
268
+ console.log("startDrawEdge", nodeId, portId, event)
269
+ nodeId = getNodeId(nodeId)
270
+ if (typeof portId != "string") portId = portId.id ?? `${node.id}/${portId.name}`
271
+ dragState.value = {
272
+ type: "drawEdge",
273
+ node: nodeId,
274
+ port: portId,
275
+ edge: newEdgeOptions
276
+ }
277
+ }
278
+
279
+ function deleteEdge(edgeId) {
280
+ edgeId = getEdgeId(edgeId)
281
+ const edgeIndex = findEdgeIndex(edgeId)
282
+ if(edgeIndex < 0) return
283
+ if(options.deleteEdge) {
284
+ options.deleteEdge(unref(edges)[edgeIndex])
285
+ } else {
286
+ edges.splice(edgeIndex, 1)
287
+ }
288
+ }
289
+
290
+ function deleteNode(nodeId) {
291
+ nodeId = getNodeId(nodeId)
292
+ const nodeIndex = findNodeIndex(nodeId)
293
+ if(nodeIndex < 0) return
294
+ const edgesToDelete = unref(edges).filter(e => e.src.node == nodeId || e.dest.node == nodeId)
295
+ for(const edge of edgesToDelete) {
296
+ deleteEdge(edge)
297
+ }
298
+ if(options.deleteNode) {
299
+ options.deleteNode(unref(nodes)[nodeIndex])
300
+ } else {
301
+ nodes.splice(nodeIndex, 1)
302
+ }
303
+ }
304
+
305
+ function startDragEdgeEnd(edge, end, event) {
306
+ edge = getEdge(edge)
307
+ deleteEdge(edge)
308
+ const otherEnd = {
309
+ 'dest': 'src',
310
+ 'src': 'dest',
311
+ }[end]
312
+ dragState.value = {
313
+ type: "drawEdge",
314
+ node: edge[otherEnd].node,
315
+ port: edge[otherEnd].port,
316
+ edge
317
+ }
318
+ }
319
+
320
+
321
+ function updateNode(nodeId, value) {
322
+ nodeId = getNodeId(nodeId)
323
+ const node = findNode(nodeId)
324
+ //console.log("updateNode", nodeId, node, nodes, JSON.stringify(node, null, 2))
325
+ if(options.updateNode) {
326
+ if (typeof value == "function") {
327
+ options.updateNode(node, value)
328
+ } else {
329
+ options.updateNode(node, (node) => {
330
+ for (const key in value) {
331
+ if (key != "id") node[key] = value[key]
332
+ }
333
+ return node
334
+ })
335
+ }
336
+ } else {
337
+ if (typeof value == "function") {
338
+ value(node)
339
+ } else {
340
+ for (const key in value) {
341
+ if (key != "id") node[key] = value[key]
342
+ }
343
+ }
344
+ }
345
+ //console.log("updatedNode", nodeId, node, nodes, JSON.stringify(node, null, 2))
346
+ }
347
+
348
+ let nextEdgeId = 0
349
+ function generateEdgeId() {
350
+ if(options.uidGenerator) return options.uidGenerator()
351
+ return `e${(nextEdgeId++).toFixed().padStart(4, "0")}`
352
+ }
353
+
354
+ let nextNodeId = 0
355
+ function generateNodeId() {
356
+ if(options.uidGenerator) return options.uidGenerator()
357
+ return `n${(nextNodeId++).toFixed().padStart(4, "0")}`
358
+ }
359
+
360
+ let nextPortId = 0
361
+ function generatePortId() {
362
+ if(options.uidGenerator) return options.uidGenerator()
363
+ return `p${(nextPortId++).toFixed().padStart(4, "0")}`
364
+ }
365
+
366
+ const flow = {
367
+ nodes,
368
+ edges,
369
+
370
+ dragState,
371
+ container,
372
+ viewport,
373
+
374
+ nodeViews,
375
+
376
+ position, size, scale,
377
+
378
+ options,
379
+
380
+ findNodeIndex,
381
+ findNode,
382
+ getNodeId,
383
+ getNode,
384
+
385
+ updateNode,
386
+ deleteNode,
387
+ deleteEdge,
388
+
389
+ startDragNode,
390
+ startDrawEdge,
391
+ startDragEdgeEnd,
392
+ updatePositionAndScale,
393
+ zoom,
394
+
395
+ getPortView,
396
+ getNearestPortView,
397
+
398
+ invalidateNodeView,
399
+ invalidatePortView,
400
+
401
+ generateEdgeId,
402
+ generateNodeId,
403
+ generatePortId,
404
+
405
+ setNodeElement,
406
+ setPortElement,
407
+
408
+ getEventPosition,
409
+ getElementPosition,
410
+ getElementRect,
411
+ }
412
+ provide("flow", flow)
413
+
414
+ globalThis.flow = flow
415
+
416
+ return flow
417
+ }
@@ -0,0 +1,6 @@
1
+ import { clientEntry } from '@live-change/frontend-base/client-entry.js'
2
+ import App from './App.vue'
3
+ import { createRouter } from './router'
4
+
5
+
6
+ clientEntry(App, createRouter)
@@ -0,0 +1,6 @@
1
+ import { serverEntry } from '@live-change/frontend-base/server-entry.js'
2
+ import App from './App.vue'
3
+ import { createRouter } from './router'
4
+
5
+ const render = serverEntry(App, createRouter)
6
+ export { render }
@@ -0,0 +1,37 @@
1
+ import {
2
+ createMemoryHistory,
3
+ createRouter as _createRouter,
4
+ createWebHistory
5
+ } from 'vue-router'
6
+
7
+ import { dbAdminRoutes } from "@live-change/db-admin"
8
+
9
+ export function wysiwygRoutes(config = {}) {
10
+ const { prefix = '/', route = (r) => r } = config
11
+ return [
12
+
13
+ /*route({
14
+ name: 'wysiwyg:editorWithPreview', path: prefix + '', meta: { },
15
+ component: () => import("./EditorWithPreview.vue"),
16
+ props: {
17
+ }
18
+ }),*/
19
+
20
+ ...dbAdminRoutes({ prefix: '/_db', route: r => ({ ...r, meta: { ...r.meta, raw: true }}) })
21
+
22
+ ]
23
+ }
24
+
25
+ export async function sitemap(route, api) {
26
+
27
+ }
28
+
29
+
30
+ export function createRouter(app, config) {
31
+ const router = _createRouter({
32
+ history: import.meta.env.SSR ? createMemoryHistory() : createWebHistory(),
33
+ routes: wysiwygRoutes(config)
34
+ })
35
+ return router
36
+ }
37
+
@@ -0,0 +1,16 @@
1
+ import { defineConfig } from 'vite'
2
+
3
+ import baseViteConfig from '@live-change/frontend-base/vite-config.js'
4
+
5
+ export default defineConfig(async ({ command, mode }) => {
6
+ const baseConfig = (await baseViteConfig({ command, mode }))
7
+ return {
8
+ ...baseConfig,
9
+
10
+ resolve: {
11
+ alias: [
12
+ ...baseConfig.resolve.alias,
13
+ ]
14
+ }
15
+ }
16
+ })
package/index.js ADDED
@@ -0,0 +1,11 @@
1
+ import { useFlow } from './front/src/components/index.js'
2
+
3
+ import Edge from './front/src/components/Edge.vue'
4
+ import Node from './front/src/components/Node.vue'
5
+ import Flow from './front/src/components/Flow.vue'
6
+ import NodeHandle from './front/src/components/NodeHandle.vue'
7
+ import NodePort from './front/src/components/NodePort.vue'
8
+ import EdgeEndHandle from './front/src/components/EdgeEndHandle.vue'
9
+ import EdgeBezierCurve from './front/src/components/EdgeBezierCurve.vue'
10
+
11
+ export { useFlow, Edge, Node, Flow, NodeHandle, NodePort, EdgeEndHandle, EdgeBezierCurve }
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@live-change/flow-frontend",
3
+ "version": "0.3.15",
4
+ "scripts": {
5
+ "memDev": "lcli memDev --enableSessions --initScript ./init.js --dbAccess",
6
+ "localDevInit": "rm tmp.db; lcli localDev --enableSessions --initScript ./init.js",
7
+ "localDev": "lcli localDev --enableSessions",
8
+ "dev": "lcli dev --enableSessions",
9
+ "ssrDev": "lcli ssrDev --enableSessions",
10
+ "serveAllMem": "cross-env NODE_ENV=production lcli ssrServer --withApi --withServices --updateServices --enableSessions --withDb --dbBackend mem --createDb",
11
+ "serveAll": "cross-env NODE_ENV=production lcli ssrServer --withApi --withServices --updateServices --enableSessions",
12
+ "serve": "cross-env NODE_ENV=production lcli ssrServer --enableSessions",
13
+ "apiServer": "lcli apiServer --enableSessions",
14
+ "devApiServer": "lcli devApiServer --enableSessions",
15
+ "memApiServer": "lcli memApiServer --enableSessions",
16
+ "build": "cd front; yarn build:client && yarn build:server",
17
+ "build:client": "cd front; vite build --ssrManifest --outDir dist/client",
18
+ "build:server": "cd front; vite build --ssr src/entry-server.js --outDir dist/server",
19
+ "generate": "vite build --ssrManifest --outDir dist/static && yarn build:server && node prerender",
20
+ "debug": "node --inspect-brk server"
21
+ },
22
+ "dependencies": {
23
+ "@live-change/access-control-service": "0.3.36",
24
+ "@live-change/cli": "0.7.34",
25
+ "@live-change/dao": "0.5.22",
26
+ "@live-change/dao-vue3": "0.5.22",
27
+ "@live-change/dao-websocket": "0.5.22",
28
+ "@live-change/db-admin": "0.6.23",
29
+ "@live-change/framework": "0.7.34",
30
+ "@live-change/frontend-base": "0.3.15",
31
+ "@live-change/password-authentication-service": "0.3.36",
32
+ "@live-change/secret-code-service": "0.3.36",
33
+ "@live-change/secret-link-service": "0.3.36",
34
+ "@live-change/session-service": "0.3.36",
35
+ "@live-change/user-frontend": "0.3.15",
36
+ "@live-change/user-service": "0.3.36",
37
+ "@live-change/vue3-components": "0.2.31",
38
+ "@live-change/vue3-ssr": "0.2.31",
39
+ "@vueuse/core": "^10.4.1",
40
+ "codeceptjs-assert": "^0.0.5",
41
+ "compression": "^1.7.4",
42
+ "cross-env": "^7.0.3",
43
+ "get-port-sync": "1.0.1",
44
+ "primeflex": "^3.3.1",
45
+ "primeicons": "^6.0.1",
46
+ "primevue": "^3.40.1",
47
+ "rollup-plugin-node-builtins": "^2.1.2",
48
+ "rollup-plugin-visualizer": "5.9.2",
49
+ "serialize-javascript": "^6.0.1",
50
+ "serve-static": "^1.15.0",
51
+ "vue-router": "^4.2.4",
52
+ "vue3-scroll-border": "0.1.5"
53
+ },
54
+ "devDependencies": {
55
+ "@live-change/codeceptjs-helper": "0.7.34",
56
+ "@wdio/selenium-standalone-service": "^8.15.0",
57
+ "codeceptjs": "^3.5.4",
58
+ "generate-password": "1.7.0",
59
+ "playwright": "^1.37.1",
60
+ "random-profile-generator": "^2.3.0",
61
+ "txtgen": "^3.0.6",
62
+ "webdriverio": "^8.16.6"
63
+ },
64
+ "author": "",
65
+ "license": "BSD-3-Clause",
66
+ "description": "",
67
+ "gitHead": "61ea1e9554f3bf8cb8f9debffdabd8a88855c3b2"
68
+ }
package/server/init.js ADDED
@@ -0,0 +1,8 @@
1
+ const App = require('@live-change/framework')
2
+ const app = App.app()
3
+
4
+ module.exports = async function(services) {
5
+
6
+
7
+
8
+ }