claudes variant of ui
This commit is contained in:
+155
@@ -0,0 +1,155 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, markRaw, watch } from 'vue'
|
||||
import { VueFlow } from '@vue-flow/core'
|
||||
import type { Node, Edge, Connection } from '@vue-flow/core'
|
||||
import '@vue-flow/core/dist/style.css'
|
||||
import '@vue-flow/core/dist/theme-default.css'
|
||||
|
||||
import Toolbar from './components/Toolbar.vue'
|
||||
import NodePalette from './components/NodePalette.vue'
|
||||
import StudioNode from './components/nodes/StudioNode.vue'
|
||||
import { useStudioStore } from './stores/studio'
|
||||
import { buildGraph } from './graphBuilder'
|
||||
import { defaultParams } from './nodeTypes'
|
||||
import type { StudioNodeData } from './types'
|
||||
|
||||
const studio = useStudioStore()
|
||||
const nodeTypes = { studio: markRaw(StudioNode) }
|
||||
|
||||
const nodes = ref<Node[]>([])
|
||||
const edges = ref<Edge[]>([])
|
||||
|
||||
let nodeCounter = 0
|
||||
|
||||
// Sync server-push state into Vue Flow node data
|
||||
watch(
|
||||
() => studio.serverStatus,
|
||||
(status) => {
|
||||
for (const node of nodes.value) {
|
||||
const d = node.data as StudioNodeData
|
||||
const serverState = (status[node.id] as StudioNodeData['serverState']) ?? undefined
|
||||
if (d.serverState !== serverState) {
|
||||
node.data = { ...d, serverState }
|
||||
}
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
function addNode(nodeType: string) {
|
||||
nodeCounter++
|
||||
const id = `${nodeType}-${nodeCounter}`
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const newNode: Node = {
|
||||
id,
|
||||
type: 'studio',
|
||||
position: { x: 160 + (nodeCounter % 6) * 220, y: 80 + Math.floor(nodeCounter / 6) * 200 },
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
data: { nodeType, params: defaultParams(nodeType), serverState: undefined } as any,
|
||||
}
|
||||
// @ts-ignore TS2589: Vue Flow Node<T> generic is too deep for vue-tsc's instantiation limit
|
||||
nodes.value = [...nodes.value, newNode]
|
||||
}
|
||||
|
||||
function onConnect(connection: Connection) {
|
||||
const srcHandle = connection.sourceHandle ?? ''
|
||||
const tgtHandle = connection.targetHandle ?? ''
|
||||
if (srcHandle.startsWith('video') !== tgtHandle.startsWith('video')) return
|
||||
edges.value = [
|
||||
...edges.value,
|
||||
{ ...connection, id: `e-${Date.now()}`, animated: true } as Edge,
|
||||
]
|
||||
}
|
||||
|
||||
function run() {
|
||||
const graph = buildGraph(nodes.value, edges.value)
|
||||
studio.send({ type: 'load_graph', graph })
|
||||
}
|
||||
|
||||
function stop() {
|
||||
studio.send({ type: 'load_graph', graph: { nodes: [], edges: [] } })
|
||||
}
|
||||
|
||||
function clear() {
|
||||
nodes.value = []
|
||||
edges.value = []
|
||||
nodeCounter = 0
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="app-shell">
|
||||
<Toolbar @run="run" @stop="stop" @clear="clear" />
|
||||
<div class="canvas-area">
|
||||
<NodePalette @add-node="addNode" />
|
||||
<VueFlow
|
||||
v-model:nodes="nodes"
|
||||
v-model:edges="edges"
|
||||
:node-types="nodeTypes"
|
||||
class="flow-canvas"
|
||||
:min-zoom="0.3"
|
||||
:max-zoom="2"
|
||||
fit-view-on-init
|
||||
@connect="onConnect"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
html, body, #app {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #11111b;
|
||||
font-family: 'Inter', system-ui, sans-serif;
|
||||
color: #cdd6f4;
|
||||
}
|
||||
|
||||
.vue-flow {
|
||||
background: #11111b !important;
|
||||
}
|
||||
|
||||
.vue-flow__background {
|
||||
background: #11111b !important;
|
||||
}
|
||||
|
||||
.vue-flow__edge-path {
|
||||
stroke: #89b4fa !important;
|
||||
stroke-width: 2 !important;
|
||||
}
|
||||
|
||||
.vue-flow__edge.animated .vue-flow__edge-path {
|
||||
stroke-dasharray: 5;
|
||||
animation: dashdraw 0.5s linear infinite;
|
||||
}
|
||||
|
||||
.vue-flow__handle {
|
||||
border-radius: 50% !important;
|
||||
}
|
||||
|
||||
@keyframes dashdraw {
|
||||
to { stroke-dashoffset: -10; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
.app-shell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.canvas-area {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.flow-canvas {
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user