Skip to content

Commit 24f1afe

Browse files
Merge branch 'main' into fix-ogg-syntax-9256
2 parents 447295e + 31a4dce commit 24f1afe

22 files changed

Lines changed: 1035 additions & 147 deletions

src/components/LiteGraphCanvasSplitterOverlay.vue

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,15 +155,17 @@ const unifiedWidth = computed(() =>
155155
156156
const { focusMode } = storeToRefs(workspaceStore)
157157
158-
const { isSelectMode } = useAppMode()
158+
const { isSelectMode, isBuilderMode } = useAppMode()
159159
const { activeSidebarTabId, activeSidebarTab } = storeToRefs(sidebarTabStore)
160160
const { bottomPanelVisible } = storeToRefs(useBottomPanelStore())
161161
const { isOpen: rightSidePanelVisible } = storeToRefs(rightSidePanelStore)
162162
const showOffsideSplitter = computed(
163163
() => rightSidePanelVisible.value || isSelectMode.value
164164
)
165165
166-
const sidebarPanelVisible = computed(() => activeSidebarTab.value !== null)
166+
const sidebarPanelVisible = computed(
167+
() => activeSidebarTab.value !== null && !isBuilderMode.value
168+
)
167169
168170
const sidebarStateKey = computed(() => {
169171
return unifiedWidth.value

src/components/appMode/AppModeToolbar.vue

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ import { useCommandStore } from '@/stores/commandStore'
99
import { useWorkspaceStore } from '@/stores/workspaceStore'
1010
import { cn } from '@/utils/tailwindUtil'
1111
import { useAppMode } from '@/composables/useAppMode'
12+
import { useAppModeStore } from '@/stores/appModeStore'
1213
1314
const { t } = useI18n()
1415
const commandStore = useCommandStore()
1516
const workspaceStore = useWorkspaceStore()
16-
const { enableAppBuilder, setMode } = useAppMode()
17+
const { enableAppBuilder } = useAppMode()
18+
const { enterBuilder } = useAppModeStore()
1719
const tooltipOptions = { showDelay: 300, hideDelay: 300 }
1820
1921
const isAssetsActive = computed(
@@ -23,10 +25,6 @@ const isWorkflowsActive = computed(
2325
() => workspaceStore.sidebarTab.activeSidebarTab?.id === 'workflows'
2426
)
2527
26-
function enterBuilderMode() {
27-
setMode('builder:select')
28-
}
29-
3028
function openAssets() {
3129
void commandStore.execute('Workspace.ToggleSidebarTab.assets')
3230
}
@@ -75,7 +73,7 @@ function openTemplates() {
7573
size="unset"
7674
:aria-label="t('linearMode.appModeToolbar.appBuilder')"
7775
class="size-10 rounded-lg"
78-
@click="enterBuilderMode"
76+
@click="enterBuilder"
7977
>
8078
<i class="icon-[lucide--hammer] size-4" />
8179
</Button>

src/components/builder/AppBuilder.vue

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,20 +38,28 @@ const workflowStore = useWorkflowStore()
3838
const { t } = useI18n()
3939
const canvas: LGraphCanvas = canvasStore.getCanvas()
4040
41-
const { mode, isArrangeMode } = useAppMode()
41+
const { isSelectMode, isArrangeMode } = useAppMode()
4242
const hoveringSelectable = ref(false)
4343
4444
provide(HideLayoutFieldKey, true)
4545
4646
workflowStore.activeWorkflow?.changeTracker?.reset()
4747
48+
function resolveNode(nodeId: NodeId) {
49+
return (
50+
app.rootGraph.getNodeById(nodeId) ??
51+
[...app.rootGraph.subgraphs.values()]
52+
.flatMap((sg) => sg.nodes)
53+
.find((n) => n.id == nodeId)
54+
)
55+
}
56+
4857
// Prune stale entries whose node/widget no longer exists, so the
4958
// DraggableList model always matches the rendered items.
5059
watchEffect(() => {
51-
const valid = appModeStore.selectedInputs.filter(([nodeId, widgetName]) => {
52-
const node = app.rootGraph.getNodeById(nodeId)
53-
return node?.widgets?.some((w) => w.name === widgetName)
54-
})
60+
const valid = appModeStore.selectedInputs.filter(([nodeId, widgetName]) =>
61+
resolveNode(nodeId)?.widgets?.some((w) => w.name === widgetName)
62+
)
5563
if (valid.length < appModeStore.selectedInputs.length) {
5664
appModeStore.selectedInputs = valid
5765
}
@@ -60,7 +68,7 @@ watchEffect(() => {
6068
const arrangeInputs = computed(() =>
6169
appModeStore.selectedInputs
6270
.map(([nodeId, widgetName]) => {
63-
const node = app.rootGraph.getNodeById(nodeId)
71+
const node = resolveNode(nodeId)
6472
const widget = node?.widgets?.find((w) => w.name === widgetName)
6573
if (!node || !widget) return null
6674
return { nodeId, widgetName, node, widget }
@@ -70,7 +78,7 @@ const arrangeInputs = computed(() =>
7078
7179
const inputsWithState = computed(() =>
7280
appModeStore.selectedInputs.map(([nodeId, widgetName]) => {
73-
const node = app.rootGraph.getNodeById(nodeId)
81+
const node = resolveNode(nodeId)
7482
const widget = node?.widgets?.find((w) => w.name === widgetName)
7583
if (!node || !widget) return { nodeId, widgetName }
7684
@@ -168,14 +176,14 @@ function handleClick(e: MouseEvent) {
168176
if (!widget) {
169177
if (!node.constructor.nodeData?.output_node)
170178
return canvasInteractions.forwardEventToCanvas(e)
171-
const index = appModeStore.selectedOutputs.findIndex((id) => id === node.id)
179+
const index = appModeStore.selectedOutputs.findIndex((id) => id == node.id)
172180
if (index === -1) appModeStore.selectedOutputs.push(node.id)
173181
else appModeStore.selectedOutputs.splice(index, 1)
174182
return
175183
}
176184
177185
const index = appModeStore.selectedInputs.findIndex(
178-
([nodeId, widgetName]) => node.id === nodeId && widget.name === widgetName
186+
([nodeId, widgetName]) => node.id == nodeId && widget.name === widgetName
179187
)
180188
if (index === -1) appModeStore.selectedInputs.push([node.id, widget.name])
181189
else appModeStore.selectedInputs.splice(index, 1)
@@ -239,6 +247,7 @@ const renderedInputs = computed<[string, MaybeRef<BoundStyle> | undefined][]>(
239247
:disabled="!appModeStore.selectedInputs.length"
240248
class="border-border-subtle border-b"
241249
:tooltip="`${t('linearMode.builder.inputsDesc')}\n${t('linearMode.builder.inputsExample')}`"
250+
:tooltip-delay="100"
242251
>
243252
<template #label>
244253
<div class="flex gap-3">
@@ -274,7 +283,7 @@ const renderedInputs = computed<[string, MaybeRef<BoundStyle> | undefined][]>(
274283
() =>
275284
remove(
276285
appModeStore.selectedInputs,
277-
([id, name]) => nodeId === id && widgetName === name
286+
([id, name]) => nodeId == id && widgetName === name
278287
)
279288
"
280289
/>
@@ -286,6 +295,7 @@ const renderedInputs = computed<[string, MaybeRef<BoundStyle> | undefined][]>(
286295
enable-empty-state
287296
:disabled="!appModeStore.selectedOutputs.length"
288297
:tooltip="`${t('linearMode.builder.outputsDesc')}\n${t('linearMode.builder.outputsExample')}`"
298+
:tooltip-delay="100"
289299
>
290300
<template #label>
291301
<div class="flex gap-3">
@@ -319,12 +329,15 @@ const renderedInputs = computed<[string, MaybeRef<BoundStyle> | undefined][]>(
319329
"
320330
:title
321331
:sub-title="String(key)"
322-
:remove="() => remove(appModeStore.selectedOutputs, (k) => k === key)"
332+
:remove="() => remove(appModeStore.selectedOutputs, (k) => k == key)"
323333
/>
324334
</DraggableList>
325335
</PropertiesAccordionItem>
326336

327-
<Teleport v-if="mode === 'builder:select'" to="body">
337+
<Teleport
338+
v-if="isSelectMode && !settingStore.get('Comfy.VueNodes.Enabled')"
339+
to="body"
340+
>
328341
<div
329342
:class="
330343
cn(
@@ -358,13 +371,19 @@ const renderedInputs = computed<[string, MaybeRef<BoundStyle> | undefined][]>(
358371
<div class="absolute top-0 right-0 size-8">
359372
<div
360373
v-if="isSelected"
361-
class="absolute -top-1/2 -right-1/2 size-full p-2 bg-warning-background rounded-lg"
374+
class="absolute -top-1/2 -right-1/2 size-full p-2 bg-warning-background rounded-lg cursor-pointer pointer-events-auto"
375+
@click.stop="
376+
remove(appModeStore.selectedOutputs, (k) => k == key)
377+
"
378+
@pointerdown.stop
362379
>
363380
<i class="icon-[lucide--check] bg-text-foreground size-full" />
364381
</div>
365382
<div
366383
v-else
367-
class="absolute -top-1/2 -right-1/2 size-full ring-warning-background/50 ring-4 ring-inset bg-component-node-background rounded-lg"
384+
class="absolute -top-1/2 -right-1/2 size-full ring-warning-background/50 ring-4 ring-inset bg-component-node-background rounded-lg cursor-pointer pointer-events-auto"
385+
@click.stop="appModeStore.selectedOutputs.push(key)"
386+
@pointerdown.stop
368387
/>
369388
</div>
370389
</div>

src/components/builder/IoItem.vue

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,11 @@ const entries = computed(() => {
3333
</script>
3434
<template>
3535
<div class="p-2 my-2 rounded-lg flex items-center-safe">
36-
<span class="mr-auto" v-text="title" />
37-
<span class="text-muted-foreground mr-2 text-end" v-text="subTitle" />
36+
<span class="mr-auto truncate shrink-1" v-text="title" />
37+
<span
38+
class="text-muted-foreground mr-2 text-end truncate shrink-3"
39+
v-text="subTitle"
40+
/>
3841
<Popover :entries>
3942
<template #button>
4043
<Button variant="muted-textonly">

src/components/rightSidePanel/layout/PropertiesAccordionItem.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ const {
1111
enableEmptyState,
1212
tooltip,
1313
size = 'default',
14+
tooltipDelay = 1000,
1415
class: className
1516
} = defineProps<{
1617
disabled?: boolean
1718
label?: string
1819
enableEmptyState?: boolean
1920
tooltip?: string
2021
size?: 'default' | 'lg'
22+
tooltipDelay?: number
2123
class?: string
2224
}>()
2325
@@ -27,7 +29,7 @@ const isExpanded = computed(() => !isCollapse.value && !disabled)
2729
2830
const tooltipConfig = computed(() => {
2931
if (!tooltip) return undefined
30-
return { value: tooltip, showDelay: 1000 }
32+
return { value: tooltip, showDelay: tooltipDelay }
3133
})
3234
</script>
3335

src/composables/graph/useMoreOptionsMenu.ts

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import { computed, ref } from 'vue'
22
import type { Ref } from 'vue'
33

4-
import type { LGraphGroup, LGraphNode } from '@/lib/litegraph/src/litegraph'
4+
import { isPromotedWidgetView } from '@/core/graph/subgraph/promotedWidgetTypes'
5+
import type {
6+
LGraphGroup,
7+
LGraphNode,
8+
NodeId
9+
} from '@/lib/litegraph/src/litegraph'
510
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
611
import { getExtraOptionsForWidget } from '@/services/litegraphService'
712
import { isLGraphGroup } from '@/utils/litegraphUtil'
@@ -46,7 +51,7 @@ export enum BadgeVariant {
4651
// Global singleton for NodeOptions component reference
4752
let nodeOptionsInstance: null | NodeOptionsInstance = null
4853

49-
const hoveredWidgetName = ref<string>()
54+
const hoveredWidget = ref<[string, NodeId | undefined]>()
5055

5156
/**
5257
* Toggle the node options popover
@@ -63,14 +68,12 @@ export function toggleNodeOptions(event: Event) {
6368
* Use this for contextmenu events where we always want to show at the new position
6469
* @param event - The trigger event (must be MouseEvent for position)
6570
*/
66-
export function showNodeOptions(event: MouseEvent) {
67-
hoveredWidgetName.value = undefined
68-
const target = event.target
69-
if (target instanceof HTMLElement) {
70-
const widgetEl = target.closest('.lg-node-widget')
71-
if (widgetEl instanceof HTMLElement)
72-
hoveredWidgetName.value = widgetEl.dataset.widgetName
73-
}
71+
export function showNodeOptions(
72+
event: MouseEvent,
73+
widgetName?: string,
74+
nodeId?: NodeId
75+
) {
76+
hoveredWidget.value = widgetName ? [widgetName, nodeId] : undefined
7477
if (nodeOptionsInstance?.show) {
7578
nodeOptionsInstance.show(event)
7679
}
@@ -259,8 +262,16 @@ export function useMoreOptionsMenu() {
259262
options.push(...getImageMenuOptions(selectedNodes.value[0]))
260263
options.push({ type: 'divider' })
261264
}
262-
const rawName = hoveredWidgetName.value
263-
const widget = node?.widgets?.find((w) => w.name === rawName)
265+
const [widgetName, nodeId] = hoveredWidget.value ?? []
266+
const widget =
267+
nodeId !== undefined
268+
? node?.widgets?.find(
269+
(w) =>
270+
isPromotedWidgetView(w) &&
271+
w.sourceWidgetName === widgetName &&
272+
w.sourceNodeId === nodeId
273+
)
274+
: node?.widgets?.find((w) => w.name === widgetName)
264275
if (widget) {
265276
const widgetOptions = convertContextMenuToOptions(
266277
getExtraOptionsForWidget(node, widget)

src/locales/en/main.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3056,11 +3056,11 @@
30563056
"promptAddInputs": "Click on node parameters to add them here as inputs",
30573057
"noInputs": "No inputs added yet",
30583058
"inputsDesc": "Users will interact and adjust these to generate their outputs.",
3059-
"inputsExample": "Examples: “Load image”, “Text prompt”, “Steps”",
3059+
"inputsExample": "Examples: “Load\u00a0image”, “Text\u00a0prompt”, “Steps”",
30603060
"promptAddOutputs": "Click on output nodes to add them here. These will be the generated results.",
30613061
"noOutputs": "No output nodes added yet",
30623062
"outputsDesc": "Connect at least one output node so users can see results after running.",
3063-
"outputsExample": "Examples: “Save Image” or “Save Video"
3063+
"outputsExample": "Examples: “Save\u00a0Image” or “Save\u00a0Video"
30643064
},
30653065
"queue": {
30663066
"clickToClear": "Click to clear queue",
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<script setup lang="ts">
2+
import { remove } from 'es-toolkit'
3+
import { computed } from 'vue'
4+
5+
import type { NodeId } from '@/lib/litegraph/src/LGraphNode'
6+
import { useAppModeStore } from '@/stores/appModeStore'
7+
import { cn } from '@/utils/tailwindUtil'
8+
9+
const { id, name } = defineProps<{
10+
id: string
11+
isSelectMode: boolean
12+
name: string
13+
}>()
14+
15+
const appModeStore = useAppModeStore()
16+
const isPromoted = computed(() => appModeStore.selectedInputs.some(matchesThis))
17+
18+
function matchesThis([nodeId, widgetName]: [NodeId, string]) {
19+
return id == nodeId && name === widgetName
20+
}
21+
function togglePromotion() {
22+
if (isPromoted.value) remove(appModeStore.selectedInputs, matchesThis)
23+
else appModeStore.selectedInputs.push([id, name])
24+
}
25+
</script>
26+
<template>
27+
<div
28+
v-if="isSelectMode"
29+
class="col-span-2 flex flex-row pointer-events-auto cursor-pointer gap-1 relative"
30+
@pointerdown.capture.stop.prevent="togglePromotion"
31+
@click.capture.stop.prevent
32+
@pointerup.capture.stop.prevent
33+
@pointermove.capture.stop.prevent
34+
@contextmenu.capture.stop.prevent
35+
>
36+
<div
37+
:class="
38+
cn(
39+
'border-primary-background border rounded-sm size-4 self-center m-1',
40+
isPromoted && 'bg-primary-background flex items-center'
41+
)
42+
"
43+
>
44+
<i
45+
v-if="isPromoted"
46+
class="icon-[lucide--check] bg-primary-foreground place-center"
47+
/>
48+
</div>
49+
<div
50+
:class="
51+
cn(
52+
'grid grid-cols-2 items-stretch ring-primary-background rounded-lg pointer-events-none flex-1',
53+
isPromoted && 'ring-2'
54+
)
55+
"
56+
>
57+
<slot />
58+
</div>
59+
<div class="absolute size-full hover:bg-primary-background/10 rounded-lg" />
60+
</div>
61+
<slot v-else />
62+
</template>

0 commit comments

Comments
 (0)