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
13 changes: 7 additions & 6 deletions src/components/sidebar/SideToolbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,15 @@
<SidebarBottomPanelToggleButton :is-small="isSmall" />
<SidebarShortcutsToggleButton :is-small="isSmall" />
<SidebarSettingsButton :is-small="isSmall" />
<ModeToggle v-if="showLinearToggle" />
<ModeToggle v-if="menuItemStore.hasSeenLinear || linearFeatureFlag" />
</div>
</div>
<HelpCenterPopups :is-small="isSmall" />
</nav>
</template>

<script setup lang="ts">
import { useResizeObserver, whenever } from '@vueuse/core'
import { useResizeObserver } from '@vueuse/core'
import { debounce } from 'es-toolkit/compat'
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'

Expand All @@ -68,6 +68,7 @@ import { useTelemetry } from '@/platform/telemetry'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useCommandStore } from '@/stores/commandStore'
import { useKeybindingStore } from '@/stores/keybindingStore'
import { useMenuItemStore } from '@/stores/menuItemStore'
import { useUserStore } from '@/stores/userStore'
import { useWorkspaceStore } from '@/stores/workspaceStore'
import type { SidebarTabExtension } from '@/types/extensionTypes'
Expand All @@ -83,14 +84,14 @@ const settingStore = useSettingStore()
const userStore = useUserStore()
const commandStore = useCommandStore()
const canvasStore = useCanvasStore()
const menuItemStore = useMenuItemStore()
const sideToolbarRef = ref<HTMLElement>()
const topToolbarRef = ref<HTMLElement>()
const bottomToolbarRef = ref<HTMLElement>()

const showLinearToggle = ref(useFeatureFlags().flags.linearToggleEnabled)
whenever(
() => canvasStore.linearMode,
() => (showLinearToggle.value = true)
const linearFeatureFlag = useFeatureFlags().featureFlag(
'linearToggleEnabled',
false
)

const isSmall = computed(
Expand Down
6 changes: 5 additions & 1 deletion src/lib/litegraph/src/types/widgets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ interface IWidgetKnobOptions extends IWidgetOptions<number[]> {
gradient_stops?: string
}

export interface IWidgetAssetOptions extends IWidgetOptions {
openModal: () => void
}

/**
* A widget for a node.
* All types are based on IBaseWidget - additions can be made there or directly on individual types.
Expand Down Expand Up @@ -249,7 +253,7 @@ export interface ITextareaWidget extends IBaseWidget<string, 'textarea'> {
export interface IAssetWidget extends IBaseWidget<
string,
'asset',
IWidgetOptions<string[]>
IWidgetAssetOptions
> {
type: 'asset'
value: string
Expand Down
2 changes: 1 addition & 1 deletion src/lib/litegraph/src/widgets/AssetWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,6 @@ export class AssetWidget

override onClick() {
//Open Modal
this.callback?.(this.value)
this.options.openModal()
}
}
14 changes: 11 additions & 3 deletions src/renderer/extensions/linearMode/DropZone.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ defineProps<{
onDragOver?: (e: DragEvent) => boolean
onDragDrop?: (e: DragEvent) => Promise<boolean> | boolean
dropIndicator?: {
label?: string
iconClass?: string
imageUrl?: string
label?: string
onClick?: (e: MouseEvent) => void
}
}>()
Expand Down Expand Up @@ -44,8 +45,15 @@ const canAcceptDrop = ref(false)
"
@click.prevent="dropIndicator?.onClick?.($event)"
>
<span v-if="dropIndicator.label" v-text="dropIndicator.label" />
<i v-if="dropIndicator.iconClass" :class="dropIndicator.iconClass" />
<img
v-if="dropIndicator?.imageUrl"
class="h-23"
:src="dropIndicator?.imageUrl"
/>
<template v-else>
<span v-if="dropIndicator.label" v-text="dropIndicator.label" />
<i v-if="dropIndicator.iconClass" :class="dropIndicator.iconClass" />
</template>
</div>
</div>
<slot v-else />
Expand Down
32 changes: 22 additions & 10 deletions src/renderer/extensions/linearMode/LinearControls.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import DropZone from '@/renderer/extensions/linearMode/DropZone.vue'
import NodeWidgets from '@/renderer/extensions/vueNodes/components/NodeWidgets.vue'
import { applyLightThemeColor } from '@/renderer/extensions/vueNodes/utils/nodeStyleUtils'
import WidgetInputNumberInput from '@/renderer/extensions/vueNodes/widgets/components/WidgetInputNumber.vue'
import { api } from '@/scripts/api'
import { app } from '@/scripts/app'
import { useCommandStore } from '@/stores/commandStore'
import { useExecutionStore } from '@/stores/executionStore'
Expand Down Expand Up @@ -50,15 +51,26 @@ useEventListener(
() => (graphNodes.value = app.rootGraph.nodes)
)

function getDropIndicator(node: LGraphNode) {
if (node.type !== 'LoadImage') return undefined

const filename = node.widgets?.[0]?.value
const resultItem = { type: 'input', filename: `${filename}` }

return {
iconClass: 'icon-[lucide--image]',
imageUrl: filename
? api.apiURL(
`/view?${new URLSearchParams(resultItem)}${app.getPreviewFormatParam()}`
)
: undefined,
label: t('linearMode.dragAndDropImage'),
onClick: () => node.widgets?.[1]?.callback?.(undefined)
}
}

function nodeToNodeData(node: LGraphNode) {
const dropIndicator =
node.type !== 'LoadImage'
? undefined
: {
iconClass: 'icon-[lucide--image]',
label: t('linearMode.dragAndDropImage'),
onClick: () => node.widgets?.[1]?.callback?.(undefined)
}
const dropIndicator = getDropIndicator(node)
const nodeData = extractVueNodeData(node)
for (const widget of nodeData.widgets ?? []) widget.slotMetadata = undefined

Expand Down Expand Up @@ -107,7 +119,7 @@ async function runButtonClick(e: Event) {
: 'Comfy.QueuePrompt'

useTelemetry()?.trackUiButtonClicked({
button_id: 'queue_run_linear'
button_id: props.mobile ? 'queue_run_linear_mobile' : 'queue_run_linear'
})
if (batchCount.value > 1) {
useTelemetry()?.trackUiButtonClicked({
Expand Down Expand Up @@ -196,7 +208,7 @@ defineExpose({ runButtonClick })
<NodeWidgets
:node-data
:style="{ background: applyLightThemeColor(nodeData.bgcolor) }"
class="py-3 gap-y-3 **:[.col-span-2]:grid-cols-1 not-has-[textarea]:flex-0 rounded-lg"
class="py-3 gap-y-3 **:[.col-span-2]:grid-cols-1 not-has-[textarea]:flex-0 rounded-lg max-w-100"
/>
</template>
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/renderer/extensions/linearMode/LinearPreview.vue
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ async function rerun(e: Event) {
<VideoPreview
v-else-if="getMediaType(selectedOutput) === 'video'"
:src="selectedOutput!.url"
class="object-contain flex-1 contain-size"
class="object-contain flex-1 md:contain-size"
/>
<audio
v-else-if="getMediaType(selectedOutput) === 'audio'"
Expand All @@ -178,7 +178,7 @@ async function rerun(e: Event) {
/>
<img
v-else
class="pointer-events-none object-contain flex-1 max-h-full brightness-50 opacity-10"
class="pointer-events-none object-contain flex-1 max-h-full md:contain-size brightness-50 opacity-10"
src="/assets/images/comfy-logo-mono.svg"
/>
</template>
2 changes: 1 addition & 1 deletion src/renderer/extensions/linearMode/OutputHistory.vue
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ useEventListener(document.body, 'keydown', (e: KeyboardEvent) => {
<ModeToggle />
</div>
<div class="border-border-subtle md:border-r" />
<WorkflowsSidebarTab v-if="displayWorkflows" class="min-w-50" />
<WorkflowsSidebarTab v-if="displayWorkflows" class="min-w-50 grow-1" />
<article
v-else
ref="outputsRef"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,8 @@ describe('useComboWidget', () => {
'asset',
'ckpt_name',
'model1.safetensors',
expect.any(Function)
expect.any(Function),
expect.any(Object)
)
expect(mockSettingStoreGet).toHaveBeenCalledWith('Comfy.Assets.UseAssetAPI')
expect(vi.mocked(assetService.isAssetBrowserEligible)).toHaveBeenCalledWith(
Expand Down Expand Up @@ -250,7 +251,8 @@ describe('useComboWidget', () => {
'asset',
'ckpt_name',
'fallback.safetensors',
expect.any(Function)
expect.any(Function),
expect.any(Object)
)
expect(mockSettingStoreGet).toHaveBeenCalledWith('Comfy.Assets.UseAssetAPI')
expect(widget).toBe(mockWidget)
Expand Down Expand Up @@ -280,7 +282,8 @@ describe('useComboWidget', () => {
'asset',
'ckpt_name',
'Select model', // Should fallback to this instead of undefined
expect.any(Function)
expect.any(Function),
expect.any(Object)
)
expect(mockSettingStoreGet).toHaveBeenCalledWith('Comfy.Assets.UseAssetAPI')
expect(widget).toBe(mockWidget)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import MultiSelectWidget from '@/components/graph/widgets/MultiSelectWidget.vue'
import { t } from '@/i18n'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { isAssetWidget, isComboWidget } from '@/lib/litegraph/src/litegraph'
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
import type {
IBaseWidget,
IWidgetAssetOptions
} from '@/lib/litegraph/src/types/widgets'
import { useAssetBrowserDialog } from '@/platform/assets/composables/useAssetBrowserDialog'
import {
assetFilenameSchema,
Expand Down Expand Up @@ -91,55 +94,59 @@ const createAssetBrowserWidget = (
const displayLabel = currentValue ?? t('widgets.selectModel')
const assetBrowserDialog = useAssetBrowserDialog()

async function openModal(this: IBaseWidget) {
if (!isAssetWidget(widget)) {
throw new Error(`Expected asset widget but received ${widget.type}`)
}
await assetBrowserDialog.show({
nodeType: node.comfyClass || '',
inputName: inputSpec.name,
currentValue: widget.value,
onAssetSelected: (asset) => {
const validatedAsset = assetItemSchema.safeParse(asset)

if (!validatedAsset.success) {
console.error(
'Invalid asset item:',
validatedAsset.error.errors,
'Received:',
asset
)
return
}

const filename = validatedAsset.data.user_metadata?.filename
const validatedFilename = assetFilenameSchema.safeParse(filename)

if (!validatedFilename.success) {
console.error(
'Invalid asset filename:',
validatedFilename.error.errors,
'for asset:',
validatedAsset.data.id
)
return
}

const oldValue = widget.value
this.value = validatedFilename.data
node.onWidgetChanged?.(
widget.name,
validatedFilename.data,
oldValue,
widget
)
}
})
}
const options: IWidgetAssetOptions = { openModal }

const widget = node.addWidget(
'asset',
inputSpec.name,
displayLabel,
async function (this: IBaseWidget) {
if (!isAssetWidget(widget)) {
throw new Error(`Expected asset widget but received ${widget.type}`)
}
await assetBrowserDialog.show({
nodeType: node.comfyClass || '',
inputName: inputSpec.name,
currentValue: widget.value,
onAssetSelected: (asset) => {
const validatedAsset = assetItemSchema.safeParse(asset)

if (!validatedAsset.success) {
console.error(
'Invalid asset item:',
validatedAsset.error.errors,
'Received:',
asset
)
return
}

const filename = validatedAsset.data.user_metadata?.filename
const validatedFilename = assetFilenameSchema.safeParse(filename)

if (!validatedFilename.success) {
console.error(
'Invalid asset filename:',
validatedFilename.error.errors,
'for asset:',
validatedAsset.data.id
)
return
}

const oldValue = widget.value
this.value = validatedFilename.data
node.onWidgetChanged?.(
widget.name,
validatedFilename.data,
oldValue,
widget
)
}
})
}
() => undefined,
options
)

return widget
Expand Down
13 changes: 12 additions & 1 deletion src/stores/menuItemStore.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
import { whenever } from '@vueuse/core'
import { defineStore } from 'pinia'
import type { MenuItem } from 'primevue/menuitem'
import { ref } from 'vue'

import { CORE_MENU_COMMANDS } from '@/constants/coreMenuCommands'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import type { ComfyExtension } from '@/types/comfy'

import { useCommandStore } from './commandStore'

export const useMenuItemStore = defineStore('menuItem', () => {
const canvasStore = useCanvasStore()
const commandStore = useCommandStore()
const menuItems = ref<MenuItem[]>([])
const menuItemHasActiveStateChildren = ref<Record<string, boolean>>({})
const hasSeenLinear = ref(false)

whenever(
() => canvasStore.linearMode,
() => (hasSeenLinear.value = true),
{ immediate: true, once: true }
)

const registerMenuGroup = (path: string[], items: MenuItem[]) => {
let currentLevel = menuItems.value
Expand Down Expand Up @@ -103,6 +113,7 @@ export const useMenuItemStore = defineStore('menuItem', () => {
registerCommands,
loadExtensionMenuCommands,
registerCoreMenuCommands,
menuItemHasActiveStateChildren
menuItemHasActiveStateChildren,
hasSeenLinear
}
})
4 changes: 2 additions & 2 deletions src/views/LinearView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,8 @@ const linearWorkflowRef = useTemplateRef('linearWorkflowRef')
:selected-item
:selected-output
/>
<div ref="topLeftRef" class="absolute z-20 top-4 left-4" />
<div ref="topRightRef" class="absolute z-20 top-4 right-4" />
<div ref="topLeftRef" class="absolute z-21 top-4 left-4" />
<div ref="topRightRef" class="absolute z-21 top-4 right-4" />
<div ref="bottomLeftRef" class="absolute z-20 bottom-4 left-4" />
<div ref="bottomRightRef" class="absolute z-20 bottom-24 right-4" />
<div
Expand Down