109 lines
2.9 KiB
Vue
109 lines
2.9 KiB
Vue
<template>
|
|
<div class="h-screen w-full relative">
|
|
<VueFlow
|
|
v-model="elements"
|
|
:default-zoom="0.8"
|
|
:min-zoom="0.2"
|
|
:max-zoom="2"
|
|
fit-view-on-init
|
|
@node-double-click="onNodeDoubleClick"
|
|
>
|
|
<Background pattern-color="#334155" :gap="20" />
|
|
<Controls />
|
|
<MiniMap pannable zoomable class="bg-slate-800 border-slate-700" />
|
|
|
|
<template #node-text="{ data, id }">
|
|
<MarkdownNode
|
|
:id="id"
|
|
:data="data"
|
|
@update-state="updateNodeState"
|
|
/>
|
|
</template>
|
|
|
|
<template #node-file="{ data, id }">
|
|
<MarkdownNode
|
|
:id="id"
|
|
:data="data"
|
|
:is-file="true"
|
|
@update-state="updateNodeState"
|
|
/>
|
|
</template>
|
|
</VueFlow>
|
|
|
|
<div class="absolute top-4 left-4 z-10 bg-slate-800/90 backdrop-blur border border-slate-700 rounded-lg p-3">
|
|
<h2 class="text-sm font-semibold text-slate-200">{{ canvasName }}</h2>
|
|
<p class="text-xs text-slate-400 mt-1">Double click to edit state</p>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { VueFlow } from '@vue-flow/core'
|
|
import { Background } from '@vue-flow/background'
|
|
import { Controls } from '@vue-flow/controls'
|
|
import { MiniMap } from '@vue-flow/minimap'
|
|
import '@vue-flow/core/dist/style.css'
|
|
import '@vue-flow/core/dist/theme-default.css'
|
|
|
|
const props = defineProps({
|
|
canvasId: { type: String, default: 'demo' }
|
|
})
|
|
|
|
const { loadCanvas, updateNodeState: apiUpdateNodeState } = useCanvas()
|
|
const canvasName = computed(() => props.canvasId)
|
|
const elements = ref([])
|
|
|
|
const transformToFlowElements = (canvasData) => {
|
|
const nodes = (canvasData.nodes || []).map(node => ({
|
|
id: node.id,
|
|
type: node.type === 'file' ? 'file' : 'text',
|
|
position: { x: node.x, y: node.y },
|
|
data: {
|
|
...node,
|
|
width: node.width,
|
|
height: node.height
|
|
},
|
|
style: {
|
|
width: `${node.width}px`,
|
|
height: `${node.height}px`,
|
|
background: node.style?.background || '#1e293b',
|
|
border: `1px solid ${node.style?.border || '#334155'}`,
|
|
borderRadius: '8px'
|
|
}
|
|
}))
|
|
|
|
const edges = (canvasData.edges || []).map(edge => ({
|
|
id: edge.id,
|
|
source: edge.fromNode,
|
|
target: edge.toNode,
|
|
sourceHandle: edge.fromSide,
|
|
targetHandle: edge.toSide,
|
|
animated: true,
|
|
style: { stroke: '#475569', strokeWidth: 2 }
|
|
}))
|
|
|
|
return [...nodes, ...edges]
|
|
}
|
|
|
|
const loadData = async () => {
|
|
const data = await loadCanvas(props.canvasId)
|
|
elements.value = transformToFlowElements(data)
|
|
}
|
|
|
|
const updateNodeState = async ({ nodeId, state }) => {
|
|
await apiUpdateNodeState(nodeId, state)
|
|
await loadData()
|
|
}
|
|
|
|
const onNodeDoubleClick = (event) => {
|
|
const node = event.node
|
|
const currentState = node.data?.state || 'pending'
|
|
const nextState = currentState === 'pending' ? 'in_progress' : currentState === 'in_progress' ? 'completed' : 'pending'
|
|
updateNodeState({ nodeId: node.id, state: nextState })
|
|
}
|
|
|
|
onMounted(() => {
|
|
loadData()
|
|
})
|
|
</script>
|