minor fixes & mix node sample

This commit is contained in:
itten
2026-07-09 11:38:11 +03:00
parent 307bb0cd69
commit 0ea48bc9a2
5 changed files with 43 additions and 4 deletions
+1
View File
@@ -86,6 +86,7 @@ function onConnect(connection: Connection) {
const srcHandle = connection.sourceHandle ?? '' const srcHandle = connection.sourceHandle ?? ''
const tgtHandle = connection.targetHandle ?? '' const tgtHandle = connection.targetHandle ?? ''
if (srcHandle.startsWith('video') !== tgtHandle.startsWith('video')) return if (srcHandle.startsWith('video') !== tgtHandle.startsWith('video')) return
if (!srcHandle.includes('-out') || !tgtHandle.includes('-in')) return
const kind = srcHandle.startsWith('video') ? 'video' : 'audio' const kind = srcHandle.startsWith('video') ? 'video' : 'audio'
edges.value = [ edges.value = [
...edges.value, ...edges.value,
+2
View File
@@ -12,6 +12,7 @@ interface PaletteGroup {
const groups: PaletteGroup[] = [ const groups: PaletteGroup[] = [
{ label: 'SOURCES', types: ['testpattern', 'ndiin', 'decklinkin', 'videoin'] }, { label: 'SOURCES', types: ['testpattern', 'ndiin', 'decklinkin', 'videoin'] },
{ label: 'PROCESS', types: ['mix'] },
{ label: 'SINKS', types: ['ndiout', 'decklinkout', 'fakesink'] }, { label: 'SINKS', types: ['ndiout', 'decklinkout', 'fakesink'] },
] ]
@@ -23,6 +24,7 @@ const iconMap: Record<string, string> = {
ndiout: '◬', ndiout: '◬',
decklinkout: '◧', decklinkout: '◧',
fakesink: '◯', fakesink: '◯',
mix: '⊕',
} }
function onDragStart(event: DragEvent, nodeType: string) { function onDragStart(event: DragEvent, nodeType: string) {
+7 -3
View File
@@ -12,7 +12,10 @@ const typeDef = computed(() => NODE_TYPES[props.data.nodeType] ?? null)
const inputs = computed(() => typeDef.value?.ports.filter(p => p.direction === 'in') ?? []) const inputs = computed(() => typeDef.value?.ports.filter(p => p.direction === 'in') ?? [])
const outputs = computed(() => typeDef.value?.ports.filter(p => p.direction === 'out') ?? []) const outputs = computed(() => typeDef.value?.ports.filter(p => p.direction === 'out') ?? [])
const isSource = computed(() => outputs.value.length > 0) function portLabel(portId: string): string {
const parts = portId.split('-')
return parts.slice(0, -1).join(' ').toUpperCase()
}
const iconMap: Record<string, string> = { const iconMap: Record<string, string> = {
testpattern: '▦', testpattern: '▦',
@@ -22,6 +25,7 @@ const iconMap: Record<string, string> = {
ndiout: '◬', ndiout: '◬',
decklinkout: '◧', decklinkout: '◧',
fakesink: '◯', fakesink: '◯',
mix: '⊕',
} }
const nodeIcon = computed(() => iconMap[props.data.nodeType] ?? '◯') const nodeIcon = computed(() => iconMap[props.data.nodeType] ?? '◯')
@@ -91,12 +95,12 @@ function onDeleteNode() {
<div v-if="inputs.length || outputs.length" class="node-ports"> <div v-if="inputs.length || outputs.length" class="node-ports">
<div class="ports-side ports-left"> <div class="ports-side ports-left">
<div v-for="port in inputs" :key="port.id" class="port-row"> <div v-for="port in inputs" :key="port.id" class="port-row">
<span :class="['port-name', `port-${port.kind}`]">{{ port.kind }}</span> <span :class="['port-name', `port-${port.kind}`]">{{ portLabel(port.id) }}</span>
</div> </div>
</div> </div>
<div class="ports-side ports-right"> <div class="ports-side ports-right">
<div v-for="port in outputs" :key="port.id" class="port-row port-row-right"> <div v-for="port in outputs" :key="port.id" class="port-row port-row-right">
<span :class="['port-name', `port-${port.kind}`]">{{ port.kind }}</span> <span :class="['port-name', `port-${port.kind}`]">{{ portLabel(port.id) }}</span>
</div> </div>
</div> </div>
</div> </div>
+2 -1
View File
@@ -25,8 +25,9 @@ function parseRes(res: string): { width: number; height: number } {
// Converts a Vue Flow handle id to the NODE_CONFIG port key used in graph JSON // Converts a Vue Flow handle id to the NODE_CONFIG port key used in graph JSON
function handleToPort(handle: string): string { function handleToPort(handle: string): string {
return handle.replace(/-(?:in|out)$/, '_flow_id') return handle.replace(/-(?:in|out)(?:-\d+)?$/, '_flow_id')
// 'video-out' → 'video_flow_id', 'audio-in' → 'audio_flow_id' // 'video-out' → 'video_flow_id', 'audio-in' → 'audio_flow_id'
// 'video-in-1' → 'video_flow_id', 'audio-in-2' → 'audio_flow_id'
} }
export function buildGraph(vfNodes: Node[], vfEdges: Edge[]): GraphJson { export function buildGraph(vfNodes: Node[], vfEdges: Edge[]): GraphJson {
+31
View File
@@ -31,6 +31,20 @@ export const PATTERN_OPTIONS = [
{ label: 'White solid', value: 'white' }, { label: 'White solid', value: 'white' },
] ]
export const MIX_MODE_OPTIONS = [
{ label: 'Fade', value: 'fade' },
{ label: 'Dissolve', value: 'dissolve' },
{ label: 'Wipe', value: 'wipe' },
{ label: 'A/B', value: 'ab' },
]
export const TRANSITION_OPTIONS = [
{ label: 'Cut', value: 'cut' },
{ label: 'Cross', value: 'cross' },
{ label: 'Slide', value: 'slide' },
{ label: 'Push', value: 'push' },
]
// Format params shown on source nodes; values go into edge format (not node params) // Format params shown on source nodes; values go into edge format (not node params)
const FORMAT_PARAMS: ParamDef[] = [ const FORMAT_PARAMS: ParamDef[] = [
{ key: 'res', label: 'Resolution', type: 'select', options: RESOLUTION_OPTIONS, default: '1920x1080', isFormat: true }, { key: 'res', label: 'Resolution', type: 'select', options: RESOLUTION_OPTIONS, default: '1920x1080', isFormat: true },
@@ -114,6 +128,23 @@ export const NODE_TYPES: Record<string, NodeTypeDef> = {
{ key: 'file', label: 'File', type: 'input', default: '0.ts' } { key: 'file', label: 'File', type: 'input', default: '0.ts' }
], ],
}, },
mix: {
type: 'mix',
label: 'Mix',
ports: [
{ id: 'video-in-1', kind: 'video', direction: 'in' },
{ id: 'video-in-2', kind: 'video', direction: 'in' },
{ id: 'audio-in-1', kind: 'audio', direction: 'in' },
{ id: 'audio-in-2', kind: 'audio', direction: 'in' },
{ id: 'video-out', kind: 'video', direction: 'out' },
{ id: 'audio-out', kind: 'audio', direction: 'out' },
],
params: [
{ key: 'mode', label: 'Mode', type: 'select', options: MIX_MODE_OPTIONS, default: 'fade' },
{ key: 'transition', label: 'Transition', type: 'select', options: TRANSITION_OPTIONS, default: 'cut' },
{ key: 'duration', label: 'Duration', type: 'number', default: 500, min: 0, max: 10000 },
],
},
} }
// Default params for a new node of the given type // Default params for a new node of the given type