Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/composables/graph/useGraphNodeManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,10 +269,13 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager {
const updatedWidgets = currentData.widgets.map((w) =>
w.name === widgetName ? { ...w, value: validateWidgetValue(value) } : w
)
vueNodeData.set(nodeId, {
// Create a completely new object to ensure Vue reactivity triggers
const updatedData = {
...currentData,
widgets: updatedWidgets
})
}

vueNodeData.set(nodeId, updatedData)
} catch (error) {
// Ignore widget update errors to prevent cascade failures
}
Expand Down
44 changes: 25 additions & 19 deletions src/composables/graph/useWidgetValue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@
* Composable for managing widget value synchronization between Vue and LiteGraph
* Provides consistent pattern for immediate UI updates and LiteGraph callbacks
*/
import { ref, watch } from 'vue'
import { computed, toValue, ref, watch } from 'vue'
import type { Ref } from 'vue'

import type { SimplifiedWidget, WidgetValue } from '@/types/simplifiedWidget'
import type { MaybeRefOrGetter } from '@vueuse/core'

interface UseWidgetValueOptions<T extends WidgetValue = WidgetValue, U = T> {
/** The widget configuration from LiteGraph */
widget: SimplifiedWidget<T>
/** The current value from parent component */
modelValue: T
/** The current value from parent component (can be a value or a getter function) */
modelValue: MaybeRefOrGetter<T>
/** Default value if modelValue is null/undefined */
defaultValue: T
/** Emit function from component setup */
Expand Down Expand Up @@ -46,8 +47,21 @@ export function useWidgetValue<T extends WidgetValue = WidgetValue, U = T>({
emit,
transform
}: UseWidgetValueOptions<T, U>): UseWidgetValueReturn<T, U> {
// Local value for immediate UI updates
const localValue = ref<T>(modelValue ?? defaultValue)
// Ref for immediate UI feedback before value flows back through modelValue
const newProcessedValue = ref<T | null>(null)

// Computed that prefers the immediately processed value, then falls back to modelValue
const localValue = computed<T>(
() => newProcessedValue.value ?? toValue(modelValue) ?? defaultValue
)

// Clear newProcessedValue when modelValue updates (allowing external changes to flow through)
watch(
() => toValue(modelValue),
() => {
newProcessedValue.value = null
}
)

// Handle user changes
const onChange = (newValue: U) => {
Expand All @@ -71,21 +85,13 @@ export function useWidgetValue<T extends WidgetValue = WidgetValue, U = T>({
}
}

// 1. Update local state for immediate UI feedback
localValue.value = processedValue
// Set for immediate UI feedback
newProcessedValue.value = processedValue

// 2. Emit to parent component
// Emit to parent component
emit('update:modelValue', processedValue)
}

// Watch for external updates from LiteGraph
watch(
() => modelValue,
(newValue) => {
localValue.value = newValue ?? defaultValue
}
)

return {
localValue: localValue as Ref<T>,
onChange
Expand All @@ -97,7 +103,7 @@ export function useWidgetValue<T extends WidgetValue = WidgetValue, U = T>({
*/
export function useStringWidgetValue(
widget: SimplifiedWidget<string>,
modelValue: string,
modelValue: string | (() => string),
emit: (event: 'update:modelValue', value: string) => void
) {
return useWidgetValue({
Expand All @@ -114,7 +120,7 @@ export function useStringWidgetValue(
*/
export function useNumberWidgetValue(
widget: SimplifiedWidget<number>,
modelValue: number,
modelValue: number | (() => number),
emit: (event: 'update:modelValue', value: number) => void
) {
return useWidgetValue({
Expand All @@ -137,7 +143,7 @@ export function useNumberWidgetValue(
*/
export function useBooleanWidgetValue(
widget: SimplifiedWidget<boolean>,
modelValue: boolean,
modelValue: boolean | (() => boolean),
emit: (event: 'update:modelValue', value: boolean) => void
) {
return useWidgetValue({
Expand Down
3 changes: 3 additions & 0 deletions src/extensions/core/uploadAudio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ async function uploadFile(
getResourceURL(...splitFilePath(path))
)
audioWidget.value = path

// Manually trigger the callback to update VueNodes
audioWidget.callback?.(path)
}
} else {
useToastStore().addAlert(resp.status + ' - ' + resp.statusText)
Expand Down
47 changes: 45 additions & 2 deletions src/renderer/extensions/vueNodes/components/LGraphNode.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
bypassed,
'before:rounded-2xl before:pointer-events-none before:absolute before:inset-0':
muted,
'will-change-transform': isDragging
'will-change-transform': isDragging,
'ring-4 ring-primary-500 bg-primary-500/10': isDraggingOver
},

shouldHandleNodePointerEvents
Expand All @@ -43,6 +44,9 @@
v-bind="pointerHandlers"
@wheel="handleWheel"
@contextmenu="handleContextMenu"
@dragover.prevent="handleDragOver"
@dragleave="handleDragLeave"
@drop="handleDrop"
>
<div class="flex flex-col justify-center items-center relative">
<template v-if="isCollapsed">
Expand Down Expand Up @@ -132,7 +136,7 @@
</template>

<script setup lang="ts">
import { whenever } from '@vueuse/core'
import { useMouseInElement, whenever } from '@vueuse/core'
import { storeToRefs } from 'pinia'
import { computed, inject, onErrorCaptured, onMounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
Expand Down Expand Up @@ -479,4 +483,43 @@ const nodeMedia = computed(() => {
})

const nodeContainerRef = ref<HTMLDivElement>()

// Track mouse position relative to node container for drag and drop
const { isOutside } = useMouseInElement(nodeContainerRef)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does useMouseInElement work? What are the performance tradeoffs?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No much perf gains,
The composable helps us know when the dragged file is within the node or not
@DrJKL suggests we should adopt the composable for checks like these

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking of using Pragmatic Drag and Drop instead of re-implementing it in this one component.


// Drag and drop support
const isDraggingOver = ref(false)

const handleDragOver = (event: DragEvent) => {
const node = lgraphNode.value
if (!node || !node.onDragOver) {
isDraggingOver.value = false
return
}

// Call the litegraph node's onDragOver callback to check if files are valid
const canDrop = node.onDragOver(event)
isDraggingOver.value = canDrop
}

const handleDragLeave = () => {
if (isOutside.value) {
isDraggingOver.value = false
}
}

const handleDrop = async (event: DragEvent) => {
event.preventDefault()
event.stopPropagation()

isDraggingOver.value = false

const node = lgraphNode.value
if (!node || !node.onDragDrop) {
return
}

// Forward the drop event to the litegraph node's onDragDrop callback
await node.onDragDrop(event)
}
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const emit = defineEmits<{

const { localValue, onChange } = useWidgetValue({
widget: props.widget,
modelValue: props.modelValue,
modelValue: () => props.modelValue,
defaultValue: props.widget.options?.values?.[0] || '',
emit
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,13 @@ export const useImageUploadWidget = () => {
folder,
onUploadComplete: (output) => {
output.forEach((path) => addToComboValues(fileComboWidget, path))

// Create a NEW array to ensure Vue reactivity detects the change
const newValue = allow_batch ? [...output] : output[0]

// @ts-expect-error litegraph combo value type does not support arrays yet
fileComboWidget.value = output
fileComboWidget.callback?.(output)
fileComboWidget.value = newValue
fileComboWidget.callback?.(newValue)
}
})

Expand Down