Update rh-test (as of 2025-10-11) (#6044)

## Summary

Tested these changes and confirmed that:
1. Feedback button shows.
2. You can run workflows and switch out models.
3. You can use the mask editor. (thank you @ric-yu for helping me
verify).

## Changes

A lot, please see commits.

Gets us up to date with `main` as of 10-11-2025.

---------

Co-authored-by: Simula_r <18093452+simula-r@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: snomiao <snomiao@gmail.com>
Co-authored-by: Christian Byrne <cbyrne@comfy.org>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: DrJKL <DrJKL@users.noreply.github.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: Marwan Ahmed <155799754+marawan206@users.noreply.github.com>
Co-authored-by: DrJKL <DrJKL0424@gmail.com>
Co-authored-by: Rizumu Ayaka <rizumu@ayaka.moe>
Co-authored-by: Comfy Org PR Bot <snomiao+comfy-pr@gmail.com>
Co-authored-by: AustinMroz <4284322+AustinMroz@users.noreply.github.com>
Co-authored-by: Austin Mroz <austin@comfy.org>
Co-authored-by: Johnpaul Chiwetelu <49923152+Myestery@users.noreply.github.com>
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: filtered <176114999+webfiltered@users.noreply.github.com>
Co-authored-by: Benjamin Lu <benceruleanlu@proton.me>
Co-authored-by: Jin Yi <jin12cc@gmail.com>
Co-authored-by: Robin Huang <robin.j.huang@gmail.com>
This commit is contained in:
Arjan Singh
2025-10-14 15:59:26 -07:00
committed by GitHub
parent ab312ce3d7
commit 0239a83da2
519 changed files with 22711 additions and 11532 deletions

View File

@@ -5,7 +5,15 @@
:class="{ 'is-dragging': isDragging, 'is-docked': isDocked }"
>
<div ref="panelRef" class="actionbar-content flex items-center select-none">
<span ref="dragHandleRef" class="drag-handle cursor-move mr-2" />
<span
ref="dragHandleRef"
:class="
cn(
'drag-handle cursor-grab w-3 h-max mr-2',
isDragging && 'cursor-grabbing'
)
"
/>
<ComfyQueueButton />
</div>
</Panel>
@@ -26,6 +34,7 @@ import type { Ref } from 'vue'
import { computed, inject, nextTick, onMounted, ref, watch } from 'vue'
import { useSettingStore } from '@/platform/settings/settingStore'
import { cn } from '@/utils/tailwindUtil'
import ComfyQueueButton from './ComfyQueueButton.vue'
@@ -257,8 +266,4 @@ watch([isDragging, isOverlappingWithTopMenu], ([dragging, overlapping]) => {
:deep(.p-panel-header) {
display: none;
}
.drag-handle {
@apply w-3 h-max;
}
</style>

View File

@@ -16,10 +16,16 @@
@click="queuePrompt"
>
<template #icon>
<i-lucide:list-start v-if="workspaceStore.shiftDown" />
<i-lucide:play v-else-if="queueMode === 'disabled'" />
<i-lucide:fast-forward v-else-if="queueMode === 'instant'" />
<i-lucide:step-forward v-else-if="queueMode === 'change'" />
<i v-if="workspaceStore.shiftDown" class="icon-[lucide--list-start]" />
<i v-else-if="queueMode === 'disabled'" class="icon-[lucide--play]" />
<i
v-else-if="queueMode === 'instant'"
class="icon-[lucide--fast-forward]"
/>
<i
v-else-if="queueMode === 'change'"
class="icon-[lucide--step-forward]"
/>
</template>
<template #item="{ item }">
<Button

View File

@@ -1,17 +1,17 @@
<template>
<div class="flex flex-col h-full">
<div class="flex h-full flex-col">
<Tabs
:key="$i18n.locale"
v-model:value="bottomPanelStore.activeBottomPanelTabId"
>
<TabList pt:tab-list="border-none">
<div class="w-full flex justify-between">
<div class="flex w-full justify-between">
<div class="tabs-container">
<Tab
v-for="tab in bottomPanelStore.bottomPanelTabs"
:key="tab.id"
:value="tab.id"
class="p-3 border-none"
class="border-none p-3"
>
<span class="font-bold">
{{ getTabDisplayTitle(tab) }}
@@ -41,7 +41,7 @@
</TabList>
</Tabs>
<!-- h-0 to force the div to grow -->
<div class="grow h-0">
<div class="h-0 grow">
<ExtensionSlot
v-if="
bottomPanelStore.bottomPanelVisible &&

View File

@@ -1,6 +1,6 @@
<template>
<div class="h-full flex flex-col p-4">
<div class="flex-1 min-h-0 overflow-auto">
<div class="flex h-full flex-col p-4">
<div class="min-h-0 flex-1 overflow-auto">
<ShortcutsList
:commands="essentialsCommands"
:subcategories="essentialsSubcategories"

View File

@@ -1,13 +1,13 @@
<template>
<div class="shortcuts-list flex justify-center">
<div class="grid gap-4 md:gap-24 h-full grid-cols-1 md:grid-cols-3 w-[90%]">
<div class="grid h-full w-[90%] grid-cols-1 gap-4 md:grid-cols-3 md:gap-24">
<div
v-for="(subcategoryCommands, subcategory) in filteredSubcategories"
:key="subcategory"
class="flex flex-col"
>
<h3
class="subcategory-title text-xs font-bold uppercase tracking-wide text-surface-600 dark-theme:text-surface-400 mb-4"
class="subcategory-title mb-4 text-xs font-bold tracking-wide text-surface-600 uppercase dark-theme:text-surface-400"
>
{{ getSubcategoryTitle(subcategory) }}
</h3>
@@ -16,7 +16,7 @@
<div
v-for="command in subcategoryCommands"
:key="command.id"
class="shortcut-item flex justify-between items-center py-2 rounded hover:bg-surface-100 dark-theme:hover:bg-surface-700 transition-colors duration-200"
class="shortcut-item flex items-center justify-between rounded py-2 transition-colors duration-200 hover:bg-surface-100 dark-theme:hover:bg-surface-700"
>
<div class="shortcut-info grow pr-4">
<div class="shortcut-name text-sm font-medium">
@@ -32,7 +32,7 @@
<span
v-for="key in command.keybinding!.combo.getKeySequences()"
:key="key"
class="key-badge px-2 py-1 text-xs font-mono bg-surface-200 dark-theme:bg-surface-600 rounded border min-w-6 text-center"
class="key-badge min-w-6 rounded border bg-surface-200 px-2 py-1 text-center font-mono text-xs dark-theme:bg-surface-600"
>
{{ formatKey(key) }}
</span>

View File

@@ -1,6 +1,6 @@
<template>
<div class="h-full flex flex-col p-4">
<div class="flex-1 min-h-0 overflow-auto">
<div class="flex h-full flex-col p-4">
<div class="min-h-0 flex-1 overflow-auto">
<ShortcutsList
:commands="viewControlsCommands"
:subcategories="viewControlsSubcategories"

View File

@@ -1,10 +1,10 @@
<template>
<div
ref="rootEl"
class="relative overflow-hidden h-full w-full bg-neutral-900"
class="relative h-full w-full overflow-hidden bg-neutral-900"
>
<div class="p-terminal rounded-none h-full w-full p-2">
<div ref="terminalEl" class="h-full terminal-host" />
<div class="p-terminal h-full w-full rounded-none p-2">
<div ref="terminalEl" class="terminal-host h-full" />
</div>
<Button
v-tooltip.left="{

View File

@@ -1,11 +1,11 @@
<template>
<div class="bg-black h-full w-full">
<div class="h-full w-full bg-black">
<p v-if="errorMessage" class="p-4 text-center">
{{ errorMessage }}
</p>
<ProgressSpinner
v-else-if="loading"
class="relative inset-0 flex justify-center items-center h-full z-10"
class="relative inset-0 z-10 flex h-full items-center justify-center"
/>
<BaseTerminal v-show="!loading" @created="terminalCreated" />
</div>

View File

@@ -6,7 +6,7 @@
showDelay: 512
}"
href="#"
class="cursor-pointer p-breadcrumb-item-link"
class="p-breadcrumb-item-link cursor-pointer"
:class="{
'flex items-center gap-1': isActive,
'p-breadcrumb-item-link-menu-visible': menu?.overlayVisible,
@@ -37,7 +37,7 @@
v-if="isEditing"
ref="itemInputRef"
v-model="itemLabel"
class="fixed z-10000 text-[.8rem] px-2 py-2"
class="fixed z-10000 px-2 py-2 text-[.8rem]"
@blur="inputBlur(true)"
@click.stop
@keydown.enter="inputBlur(true)"

View File

@@ -12,6 +12,7 @@ const iconGroupClasses = cn(
'outline-hidden border-none p-0 rounded-lg',
'bg-white dark-theme:bg-zinc-700',
'text-neutral-950 dark-theme:text-white',
'transition-all duration-200',
'cursor-pointer'
)
</script>

View File

@@ -1,7 +1,8 @@
<template>
<div class="relative inline-flex items-center">
<IconButton @click="toggle">
<i-lucide:more-vertical class="text-sm" />
<IconButton :size="size" :type="type" @click="toggle">
<i v-if="!isVertical" class="icon-[lucide--ellipsis] text-sm" />
<i v-else class="icon-[lucide--more-vertical] text-sm" />
</IconButton>
<Popover
@@ -13,8 +14,10 @@
:close-on-escape="true"
unstyled
:pt="pt"
@show="$emit('menuOpened')"
@hide="$emit('menuClosed')"
>
<div class="flex flex-col gap-2 p-2 min-w-40">
<div class="flex min-w-40 flex-col gap-2 p-2">
<slot :close="hide" />
</div>
</Popover>
@@ -25,12 +28,28 @@
import Popover from 'primevue/popover'
import { computed, ref } from 'vue'
import type { BaseButtonProps } from '@/types/buttonTypes'
import { cn } from '@/utils/tailwindUtil'
import IconButton from './IconButton.vue'
interface MoreButtonProps extends BaseButtonProps {
isVertical?: boolean
}
const popover = ref<InstanceType<typeof Popover>>()
const {
size = 'md',
type = 'secondary',
isVertical = false
} = defineProps<MoreButtonProps>()
defineEmits<{
menuOpened: []
menuClosed: []
}>()
const toggle = (event: Event) => {
popover.value?.toggle(event)
}
@@ -45,7 +64,7 @@ const pt = computed(() => ({
},
content: {
class: cn(
'mt-2 rounded-lg',
'mt-1 rounded-lg',
'bg-white dark-theme:bg-zinc-800',
'text-neutral dark-theme:text-white',
'shadow-lg',

View File

@@ -11,7 +11,15 @@ import CardTop from './CardTop.vue'
interface CardStoryArgs {
// CardContainer props
containerRatio: 'square' | 'portrait' | 'tallPortrait'
containerSize: 'mini' | 'compact' | 'regular' | 'portrait' | 'tall'
variant: 'default' | 'ghost' | 'outline'
rounded: 'none' | 'sm' | 'lg' | 'xl'
customAspectRatio?: string
hasBorder: boolean
hasBackground: boolean
hasShadow: boolean
hasCursor: boolean
customClass: string
maxWidth: number
minWidth: number
@@ -44,10 +52,44 @@ interface CardStoryArgs {
const meta: Meta<CardStoryArgs> = {
title: 'Components/Card/Card',
argTypes: {
containerRatio: {
containerSize: {
control: 'select',
options: ['square', 'portrait', 'tallPortrait'],
description: 'Card container aspect ratio'
options: ['mini', 'compact', 'regular', 'portrait', 'tall'],
description: 'Card container size preset'
},
variant: {
control: 'select',
options: ['default', 'ghost', 'outline'],
description: 'Card visual variant'
},
rounded: {
control: 'select',
options: ['none', 'sm', 'lg', 'xl'],
description: 'Border radius size'
},
customAspectRatio: {
control: 'text',
description: 'Custom aspect ratio (e.g., "16/9")'
},
hasBorder: {
control: 'boolean',
description: 'Add border styling'
},
hasBackground: {
control: 'boolean',
description: 'Add background styling'
},
hasShadow: {
control: 'boolean',
description: 'Add shadow styling'
},
hasCursor: {
control: 'boolean',
description: 'Add cursor pointer'
},
customClass: {
control: 'text',
description: 'Additional custom CSS classes'
},
topRatio: {
control: 'select',
@@ -149,8 +191,15 @@ const createCardTemplate = (args: CardStoryArgs) => ({
template: `
<div class="min-h-screen">
<CardContainer
:ratio="args.containerRatio"
class="max-w-[320px] mx-auto"
:size="args.containerSize"
:variant="args.variant"
:rounded="args.rounded"
:custom-aspect-ratio="args.customAspectRatio"
:has-border="args.hasBorder"
:has-background="args.hasBackground"
:has-shadow="args.hasShadow"
:has-cursor="args.hasCursor"
:class="args.customClass || 'max-w-[320px] mx-auto'"
>
<template #top>
<CardTop :ratio="args.topRatio">
@@ -205,7 +254,7 @@ const createCardTemplate = (args: CardStoryArgs) => ({
</template>
<template #bottom>
<CardBottom class="p-3 bg-neutral-100">
<CardBottom>
<CardTitle v-if="args.showTitle">{{ args.title }}</CardTitle>
<CardDescription v-if="args.showDescription">{{ args.description }}</CardDescription>
</CardBottom>
@@ -218,7 +267,15 @@ const createCardTemplate = (args: CardStoryArgs) => ({
export const Default: Story = {
render: (args: CardStoryArgs) => createCardTemplate(args),
args: {
containerRatio: 'portrait',
containerSize: 'portrait',
variant: 'default',
rounded: 'lg',
customAspectRatio: '',
hasBorder: true,
hasBackground: true,
hasShadow: true,
hasCursor: true,
customClass: '',
topRatio: 'square',
showTopLeft: false,
showTopRight: true,
@@ -243,7 +300,15 @@ export const Default: Story = {
export const SquareCard: Story = {
render: (args: CardStoryArgs) => createCardTemplate(args),
args: {
containerRatio: 'square',
containerSize: 'regular',
variant: 'default',
rounded: 'lg',
customAspectRatio: '',
hasBorder: true,
hasBackground: true,
hasShadow: true,
hasCursor: true,
customClass: '',
topRatio: 'landscape',
showTopLeft: false,
showTopRight: true,
@@ -268,7 +333,15 @@ export const SquareCard: Story = {
export const TallPortraitCard: Story = {
render: (args: CardStoryArgs) => createCardTemplate(args),
args: {
containerRatio: 'tallPortrait',
containerSize: 'tall',
variant: 'default',
rounded: 'lg',
customAspectRatio: '',
hasBorder: true,
hasBackground: true,
hasShadow: true,
hasCursor: true,
customClass: '',
topRatio: 'square',
showTopLeft: true,
showTopRight: true,
@@ -293,7 +366,15 @@ export const TallPortraitCard: Story = {
export const ImageCard: Story = {
render: (args: CardStoryArgs) => createCardTemplate(args),
args: {
containerRatio: 'portrait',
containerSize: 'portrait',
variant: 'default',
rounded: 'lg',
customAspectRatio: '',
hasBorder: true,
hasBackground: true,
hasShadow: true,
hasCursor: true,
customClass: '',
topRatio: 'square',
showTopLeft: false,
showTopRight: true,
@@ -314,10 +395,50 @@ export const ImageCard: Story = {
}
}
export const MiniCard: Story = {
render: (args: CardStoryArgs) => createCardTemplate(args),
args: {
containerSize: 'mini',
variant: 'default',
rounded: 'lg',
customAspectRatio: '',
hasBorder: true,
hasBackground: true,
hasShadow: true,
hasCursor: true,
customClass: '',
topRatio: 'square',
showTopLeft: false,
showTopRight: false,
showBottomLeft: false,
showBottomRight: true,
showTitle: true,
showDescription: false,
title: 'Mini Asset',
description: '',
backgroundColor: '#06b6d4',
showImage: false,
imageUrl: '',
tags: ['Asset'],
showFileSize: true,
fileSize: '124 KB',
showFileType: false,
fileType: ''
}
}
export const MinimalCard: Story = {
render: (args: CardStoryArgs) => createCardTemplate(args),
args: {
containerRatio: 'square',
containerSize: 'regular',
variant: 'default',
rounded: 'lg',
customAspectRatio: '',
hasBorder: true,
hasBackground: true,
hasShadow: true,
hasCursor: true,
customClass: '',
topRatio: 'landscape',
showTopLeft: false,
showTopRight: false,
@@ -338,10 +459,209 @@ export const MinimalCard: Story = {
}
}
export const GhostVariant: Story = {
render: (args: CardStoryArgs) => createCardTemplate(args),
args: {
containerSize: 'compact',
variant: 'ghost',
rounded: 'lg',
customAspectRatio: '',
hasBorder: true,
hasBackground: true,
hasShadow: true,
hasCursor: true,
customClass: '',
topRatio: 'square',
showTopLeft: false,
showTopRight: false,
showBottomLeft: false,
showBottomRight: true,
showTitle: true,
showDescription: true,
title: 'Workflow Template',
description: 'Ghost variant for workflow templates',
backgroundColor: '#10b981',
showImage: false,
imageUrl: '',
tags: ['Template'],
showFileSize: false,
fileSize: '',
showFileType: false,
fileType: ''
}
}
export const OutlineVariant: Story = {
render: (args: CardStoryArgs) => createCardTemplate(args),
args: {
containerSize: 'regular',
variant: 'outline',
rounded: 'lg',
customAspectRatio: '',
hasBorder: true,
hasBackground: true,
hasShadow: true,
hasCursor: true,
customClass: '',
topRatio: 'landscape',
showTopLeft: false,
showTopRight: true,
showBottomLeft: false,
showBottomRight: false,
showTitle: true,
showDescription: true,
title: 'Outline Card',
description: 'Card with outline variant styling',
backgroundColor: '#f59e0b',
showImage: false,
imageUrl: '',
tags: [],
showFileSize: false,
fileSize: '',
showFileType: false,
fileType: ''
}
}
export const CustomAspectRatio: Story = {
render: (args: CardStoryArgs) => createCardTemplate(args),
args: {
containerSize: 'regular',
variant: 'default',
customAspectRatio: '16/9',
hasBorder: true,
hasBackground: true,
hasShadow: true,
hasCursor: true,
customClass: '',
topRatio: 'landscape',
showTopLeft: false,
showTopRight: false,
showBottomLeft: false,
showBottomRight: true,
showTitle: true,
showDescription: false,
title: 'Wide Format Card',
description: '',
backgroundColor: '#8b5cf6',
showImage: false,
imageUrl: '',
tags: ['Wide'],
showFileSize: false,
fileSize: '',
showFileType: false,
fileType: ''
}
}
export const RoundedNone: Story = {
render: (args: CardStoryArgs) => createCardTemplate(args),
args: {
containerSize: 'regular',
variant: 'default',
rounded: 'none',
customAspectRatio: '',
hasBorder: true,
hasBackground: true,
hasShadow: true,
hasCursor: true,
customClass: '',
topRatio: 'square',
showTopLeft: false,
showTopRight: false,
showBottomLeft: false,
showBottomRight: false,
showTitle: true,
showDescription: true,
title: 'Sharp Corners',
description: 'Card with no border radius',
backgroundColor: '#dc2626',
showImage: false,
imageUrl: '',
tags: [],
showFileSize: false,
fileSize: '',
showFileType: false,
fileType: ''
}
}
export const RoundedXL: Story = {
render: (args: CardStoryArgs) => createCardTemplate(args),
args: {
containerSize: 'regular',
variant: 'default',
rounded: 'xl',
customAspectRatio: '',
hasBorder: true,
hasBackground: true,
hasShadow: true,
hasCursor: true,
customClass: '',
topRatio: 'square',
showTopLeft: false,
showTopRight: false,
showBottomLeft: false,
showBottomRight: false,
showTitle: true,
showDescription: true,
title: 'Extra Rounded',
description: 'Card with extra large border radius',
backgroundColor: '#059669',
showImage: false,
imageUrl: '',
tags: [],
showFileSize: false,
fileSize: '',
showFileType: false,
fileType: ''
}
}
export const NoStylesCard: Story = {
render: (args: CardStoryArgs) => createCardTemplate(args),
args: {
containerSize: 'regular',
variant: 'default',
rounded: 'lg',
customAspectRatio: '',
hasBorder: false,
hasBackground: false,
hasShadow: false,
hasCursor: true,
customClass: 'bg-gradient-to-br from-blue-500 to-purple-600',
topRatio: 'square',
showTopLeft: false,
showTopRight: false,
showBottomLeft: false,
showBottomRight: false,
showTitle: true,
showDescription: true,
title: 'Custom Styled Card',
description: 'Card with all default styles removed and custom gradient',
backgroundColor: 'transparent',
showImage: false,
imageUrl: '',
tags: [],
showFileSize: false,
fileSize: '',
showFileType: false,
fileType: ''
}
}
export const FullFeaturedCard: Story = {
render: (args: CardStoryArgs) => createCardTemplate(args),
args: {
containerRatio: 'tallPortrait',
containerSize: 'tall',
variant: 'default',
rounded: 'lg',
customAspectRatio: '',
hasBorder: true,
hasBackground: true,
hasShadow: true,
hasCursor: true,
customClass: '',
topRatio: 'square',
showTopLeft: true,
showTopRight: true,

View File

@@ -8,26 +8,78 @@
<script setup lang="ts">
import { computed } from 'vue'
const { ratio = 'square', type } = defineProps<{
ratio?: 'smallSquare' | 'square' | 'portrait' | 'tallPortrait'
type?: string
import { cn } from '@/utils/tailwindUtil'
const {
size = 'regular',
variant = 'default',
rounded = 'md',
customAspectRatio,
hasBorder = true,
hasBackground = true,
hasShadow = true,
hasCursor = true,
class: customClass = ''
} = defineProps<{
size?: 'mini' | 'compact' | 'regular' | 'portrait' | 'tall'
variant?: 'default' | 'ghost' | 'outline'
rounded?: 'none' | 'md' | 'lg' | 'xl'
customAspectRatio?: string
hasBorder?: boolean
hasBackground?: boolean
hasShadow?: boolean
hasCursor?: boolean
class?: string
}>()
// Base structure classes
const structureClasses = 'flex flex-col overflow-hidden'
// Rounded corners
const roundedClasses = {
none: 'rounded-none',
md: 'rounded',
lg: 'rounded-lg',
xl: 'rounded-xl'
} as const
const containerClasses = computed(() => {
const baseClasses =
'cursor-pointer flex flex-col bg-white dark-theme:bg-zinc-800 rounded-lg shadow-sm border border-zinc-200 dark-theme:border-zinc-700 overflow-hidden'
if (type === 'workflow-template-card') {
return `cursor-pointer p-2 flex flex-col hover:bg-white dark-theme:hover:bg-zinc-800 rounded-lg transition-background duration-200 ease-in-out`
// Variant styles
const variantClasses = {
default: cn(
hasBackground && 'bg-white dark-theme:bg-zinc-800',
hasBorder && 'border border-zinc-200 dark-theme:border-zinc-700',
hasShadow && 'shadow-sm',
hasCursor && 'cursor-pointer'
),
ghost: cn(
hasCursor && 'cursor-pointer',
'p-2 transition-colors duration-200'
),
outline: cn(
hasBorder && 'border-2 border-zinc-300 dark-theme:border-zinc-600',
hasCursor && 'cursor-pointer',
'hover:border-zinc-400 dark-theme:hover:border-zinc-500 transition-colors'
)
}
const ratioClasses = {
smallSquare: 'aspect-240/311',
square: 'aspect-256/308',
portrait: 'aspect-256/325',
tallPortrait: 'aspect-256/353'
}
// Size/aspect ratio
const aspectRatio = customAspectRatio
? `aspect-[${customAspectRatio}]`
: {
mini: 'aspect-100/120',
compact: 'aspect-240/311',
regular: 'aspect-256/308',
portrait: 'aspect-256/325',
tall: 'aspect-256/353'
}[size]
return `${baseClasses} ${ratioClasses[ratio]}`
return cn(
structureClasses,
roundedClasses[rounded],
variantClasses[variant],
aspectRatio,
customClass
)
})
</script>

View File

@@ -1,5 +1,5 @@
<template>
<div class="text-zinc-500 dark-theme:text-zinc-400 text-xs line-clamp-2 h-7">
<div class="line-clamp-2 h-7 text-xs text-zinc-500 dark-theme:text-zinc-400">
<slot></slot>
</div>
</template>

View File

@@ -1,32 +1,28 @@
<template>
<div :class="topStyle">
<slot class="absolute top-0 left-0 w-full h-full"></slot>
<slot class="absolute top-0 left-0 h-full w-full"></slot>
<div
v-if="slots['top-left']"
class="absolute top-2 left-2 flex gap-2 flex-wrap justify-start"
>
<div v-if="slots['top-left']" :class="slotClasses['top-left']">
<slot name="top-left"></slot>
</div>
<div
v-if="slots['top-right']"
class="absolute top-2 right-2 flex gap-2 flex-wrap justify-end"
>
<div v-if="slots['top-right']" :class="slotClasses['top-right']">
<slot name="top-right"></slot>
</div>
<div
v-if="slots['bottom-left']"
class="absolute bottom-2 left-2 flex gap-2 flex-wrap justify-start"
>
<div v-if="slots['center-left']" :class="slotClasses['center-left']">
<slot name="center-left"></slot>
</div>
<div v-if="slots['center-right']" :class="slotClasses['center-right']">
<slot name="center-right"></slot>
</div>
<div v-if="slots['bottom-left']" :class="slotClasses['bottom-left']">
<slot name="bottom-left"></slot>
</div>
<div
v-if="slots['bottom-right']"
class="absolute bottom-2 right-2 flex gap-2 flex-wrap justify-end"
>
<div v-if="slots['bottom-right']" :class="slotClasses['bottom-right']">
<slot name="bottom-right"></slot>
</div>
</div>
@@ -35,10 +31,26 @@
<script setup lang="ts">
import { computed, useSlots } from 'vue'
import { cn } from '@/utils/tailwindUtil'
const slots = useSlots()
const { ratio = 'square' } = defineProps<{
const {
ratio = 'square',
topLeftClass,
topRightClass,
centerLeftClass,
centerRightClass,
bottomLeftClass,
bottomRightClass
} = defineProps<{
ratio?: 'square' | 'landscape'
topLeftClass?: string
topRightClass?: string
centerLeftClass?: string
centerRightClass?: string
bottomLeftClass?: string
bottomRightClass?: string
}>()
const topStyle = computed(() => {
@@ -51,4 +63,26 @@ const topStyle = computed(() => {
return `${baseClasses} ${ratioClasses[ratio]}`
})
// Get default classes for each slot position
const defaultSlotClasses = {
'top-left': 'absolute top-2 left-2 flex flex-wrap justify-start gap-2',
'top-right': 'absolute top-2 right-2 flex flex-wrap justify-end gap-2',
'center-left':
'absolute top-1/2 left-2 flex -translate-y-1/2 flex-wrap justify-start gap-2',
'center-right':
'absolute top-1/2 right-2 flex -translate-y-1/2 flex-wrap justify-end gap-2',
'bottom-left': 'absolute bottom-2 left-2 flex flex-wrap justify-start gap-2',
'bottom-right': 'absolute right-2 bottom-2 flex flex-wrap justify-end gap-2'
}
// Compute all slot classes once and cache them
const slotClasses = computed(() => ({
'top-left': cn(defaultSlotClasses['top-left'], topLeftClass),
'top-right': cn(defaultSlotClasses['top-right'], topRightClass),
'center-left': cn(defaultSlotClasses['center-left'], centerLeftClass),
'center-right': cn(defaultSlotClasses['center-right'], centerRightClass),
'bottom-left': cn(defaultSlotClasses['bottom-left'], bottomLeftClass),
'bottom-right': cn(defaultSlotClasses['bottom-right'], bottomRightClass)
}))
</script>

View File

@@ -1,13 +1,28 @@
<template>
<div
class="inline-flex justify-center items-center gap-1 shrink-0 py-1 px-2 text-xs bg-[#D9D9D966]/40 rounded font-bold text-white/90"
>
<slot name="icon" class="text-xs text-white/90"></slot>
<div :class="chipClasses">
<slot name="icon"></slot>
<span>{{ label }}</span>
</div>
</template>
<script setup lang="ts">
const { label } = defineProps<{
import { computed } from 'vue'
import { cn } from '@/utils/tailwindUtil'
const { label, variant = 'dark' } = defineProps<{
label: string
variant?: 'dark' | 'light'
}>()
const baseClasses =
'inline-flex shrink-0 items-center justify-center gap-1 rounded px-2 py-1 text-xs font-bold'
const variantStyles = {
dark: 'bg-zinc-500/40 text-white/90',
light: 'backdrop-blur-[2px] bg-white/50 text-zinc-900 dark-theme:text-white'
}
const chipClasses = computed(() => {
return cn(baseClasses, variantStyles[variant])
})
</script>

View File

@@ -11,7 +11,7 @@
icon="pi pi-exclamation-triangle"
size="small"
variant="outlined"
class="h-min my-2 px-1 max-w-xs"
class="my-2 h-min max-w-xs px-1"
:title="props.error"
:pt="{
text: { class: 'overflow-hidden text-ellipsis' }

View File

@@ -1,16 +1,16 @@
<template>
<div class="image-upload-wrapper">
<div class="flex gap-2 items-center">
<div class="flex items-center gap-2">
<div
class="preview-box border rounded p-2 w-16 h-16 flex items-center justify-center"
class="preview-box flex h-16 w-16 items-center justify-center rounded border p-2"
:class="{ 'bg-gray-100 dark-theme:bg-gray-800': !modelValue }"
>
<img
v-if="modelValue"
:src="modelValue"
class="max-w-full max-h-full object-contain"
class="max-h-full max-w-full object-contain"
/>
<i v-else class="pi pi-image text-gray-400 text-xl" />
<i v-else class="pi pi-image text-xl text-gray-400" />
</div>
<div class="flex flex-col gap-2">

View File

@@ -34,7 +34,8 @@ import InputNumber from 'primevue/inputnumber'
import InputText from 'primevue/inputtext'
import Select from 'primevue/select'
import ToggleSwitch from 'primevue/toggleswitch'
import { type Component, markRaw } from 'vue'
import { markRaw } from 'vue'
import type { Component } from 'vue'
import BackgroundImageUpload from '@/components/common/BackgroundImageUpload.vue'
import CustomFormValue from '@/components/common/CustomFormValue.vue'

View File

@@ -1,7 +1,8 @@
<template>
<div
ref="containerRef"
class="relative overflow-hidden w-full h-full flex items-center justify-center"
class="relative flex h-full w-full items-center justify-center overflow-hidden"
:class="containerClass"
>
<Skeleton
v-if="!isImageLoaded"
@@ -22,7 +23,7 @@
/>
<div
v-if="hasError"
class="absolute inset-0 flex items-center justify-center bg-surface-50 dark-theme:bg-surface-800 text-muted"
class="absolute inset-0 flex items-center justify-center bg-surface-50 text-muted dark-theme:bg-surface-800"
>
<img
src="/assets/images/default-template.png"
@@ -41,17 +42,20 @@ import { computed, onUnmounted, ref, watch } from 'vue'
import { useIntersectionObserver } from '@/composables/useIntersectionObserver'
import { useMediaCache } from '@/services/mediaCacheService'
import type { ClassValue } from '@/utils/tailwindUtil'
const {
src,
alt = '',
containerClass = '',
imageClass = '',
imageStyle,
rootMargin = '300px'
} = defineProps<{
src: string
alt?: string
imageClass?: string | string[] | Record<string, boolean>
containerClass?: ClassValue
imageClass?: ClassValue
imageStyle?: Record<string, any>
rootMargin?: string
}>()

View File

@@ -1,11 +1,11 @@
<template>
<div class="no-results-placeholder p-8 h-full" :class="props.class">
<div class="no-results-placeholder h-full p-8" :class="props.class">
<Card>
<template #content>
<div class="flex flex-col items-center">
<i :class="icon" style="font-size: 3rem; margin-bottom: 1rem" />
<h3>{{ title }}</h3>
<p :class="textClass" class="whitespace-pre-line text-center">
<p :class="textClass" class="text-center whitespace-pre-line">
{{ message }}
</p>
<Button

View File

@@ -28,7 +28,7 @@
</IconField>
<div
v-if="filters?.length"
class="search-filters pt-2 flex flex-wrap gap-2"
class="search-filters flex flex-wrap gap-2 pt-2"
>
<SearchFilterChip
v-for="filter in filters"

View File

@@ -1,7 +1,7 @@
<template>
<div class="system-stats">
<div class="mb-6">
<h2 class="text-2xl font-semibold mb-4">
<h2 class="mb-4 text-2xl font-semibold">
{{ $t('g.systemInfo') }}
</h2>
<div class="grid grid-cols-2 gap-2">
@@ -17,7 +17,7 @@
<Divider />
<div>
<h2 class="text-2xl font-semibold mb-4">
<h2 class="mb-4 text-2xl font-semibold">
{{ $t('g.devices') }}
</h2>
<TabView v-if="props.stats.devices.length > 1">

View File

@@ -2,7 +2,7 @@
<Tree
v-model:expanded-keys="expandedKeys"
v-model:selection-keys="selectionKeys"
class="tree-explorer py-0 px-2 2xl:px-4"
class="tree-explorer px-2 py-0 2xl:px-4"
:class="props.class"
:value="renderedRoot.children"
selection-mode="single"
@@ -47,9 +47,11 @@ import { useTreeFolderOperations } from '@/composables/tree/useTreeFolderOperati
import { useErrorHandling } from '@/composables/useErrorHandling'
import {
InjectKeyExpandedKeys,
InjectKeyHandleEditLabelFunction,
type RenderedTreeExplorerNode,
type TreeExplorerNode
InjectKeyHandleEditLabelFunction
} from '@/types/treeExplorerTypes'
import type {
RenderedTreeExplorerNode,
TreeExplorerNode
} from '@/types/treeExplorerTypes'
import { combineTrees, findNodeByKey } from '@/utils/treeUtil'

View File

@@ -45,10 +45,10 @@ import {
usePragmaticDraggable,
usePragmaticDroppable
} from '@/composables/usePragmaticDragAndDrop'
import {
InjectKeyHandleEditLabelFunction,
type RenderedTreeExplorerNode,
type TreeExplorerDragAndDropData
import { InjectKeyHandleEditLabelFunction } from '@/types/treeExplorerTypes'
import type {
RenderedTreeExplorerNode,
TreeExplorerDragAndDropData
} from '@/types/treeExplorerTypes'
const props = defineProps<{

View File

@@ -12,9 +12,9 @@
:class="{
'pi pi-spin pi-spinner text-neutral-400':
validationState === ValidationState.LOADING,
'pi pi-check text-green-500 cursor-pointer':
'pi pi-check cursor-pointer text-green-500':
validationState === ValidationState.VALID,
'pi pi-times text-red-500 cursor-pointer':
'pi pi-times cursor-pointer text-red-500':
validationState === ValidationState.INVALID
}"
@click="validateUrl(props.modelValue)"

View File

@@ -11,7 +11,7 @@
severity="secondary"
icon="pi pi-dollar"
rounded
class="text-amber-400 p-1"
class="p-1 text-amber-400"
/>
<div :class="textClass">{{ formattedBalance }}</div>
</div>

View File

@@ -17,7 +17,8 @@
<script setup lang="ts" generic="T">
import { useElementSize, useScroll, whenever } from '@vueuse/core'
import { clamp, debounce } from 'es-toolkit/compat'
import { type CSSProperties, computed, onBeforeUnmount, ref, watch } from 'vue'
import { computed, onBeforeUnmount, ref, watch } from 'vue'
import type { CSSProperties } from 'vue'
type GridState = {
start: number

View File

@@ -17,7 +17,7 @@
</template>
<template #header>
<SearchBox v-model="searchQuery" class="max-w-[384px]" />
<SearchBox v-model="searchQuery" size="lg" class="max-w-[384px]" />
</template>
<template #header-right-area>
@@ -29,14 +29,14 @@
@click="resetFilters"
>
<template #icon>
<i-lucide:filter-x />
<i class="icon-[lucide--filter-x]" />
</template>
</IconTextButton>
</div>
</template>
<template #contentFilter>
<div class="relative px-6 pt-2 pb-4 flex gap-2 flex-wrap">
<div class="relative flex flex-wrap gap-2 px-6 pt-2 pb-4">
<!-- Model Filter -->
<MultiSelect
v-model="selectedModelObjects"
@@ -49,7 +49,7 @@
:show-clear-button="true"
>
<template #icon>
<i-lucide:cpu />
<i class="icon-[lucide--cpu]" />
</template>
</MultiSelect>
@@ -63,7 +63,7 @@
:show-clear-button="true"
>
<template #icon>
<i-lucide:target />
<i class="icon-[lucide--target]" />
</template>
</MultiSelect>
@@ -77,7 +77,7 @@
:show-clear-button="true"
>
<template #icon>
<i-lucide:file-text />
<i class="icon-[lucide--file-text]" />
</template>
</MultiSelect>
@@ -87,17 +87,17 @@
v-model="sortBy"
:label="$t('templateWorkflows.sorting', 'Sort by')"
:options="sortOptions"
class="min-w-[270px]"
class="w-62.5"
>
<template #icon>
<i-lucide:arrow-up-down />
<i class="icon-[lucide--arrow-up-down]" />
</template>
</SingleSelect>
</div>
</div>
<div
v-if="!isLoading"
class="px-6 pt-4 pb-2 text-2xl font-semibold text-neutral"
class="text-neutral px-6 pt-4 pb-2 text-2xl font-semibold"
>
<span>
{{ pageTitle }}
@@ -109,10 +109,10 @@
<!-- No Results State (only show when loaded and no results) -->
<div
v-if="!isLoading && filteredTemplates.length === 0"
class="flex flex-col items-center justify-center h-64 text-neutral-500"
class="flex h-64 flex-col items-center justify-center text-neutral-500"
>
<i-lucide:search class="w-12 h-12 mb-4 opacity-50" />
<p class="text-lg mb-2">
<i class="mb-4 icon-[lucide--search] h-12 w-12 opacity-50" />
<p class="mb-2 text-lg">
{{ $t('templateWorkflows.noResults', 'No templates found') }}
</p>
<p class="text-sm">
@@ -128,7 +128,7 @@
<!-- Title -->
<span
v-if="isLoading"
class="inline-block h-8 w-48 bg-dialog-surface rounded animate-pulse"
class="inline-block h-8 w-48 animate-pulse rounded bg-dialog-surface"
></span>
<!-- Template Cards Grid -->
@@ -141,14 +141,16 @@
<CardContainer
v-for="n in isLoading ? 12 : 0"
:key="`initial-skeleton-${n}`"
ratio="smallSquare"
type="workflow-template-card"
size="compact"
variant="ghost"
rounded="lg"
class="hover:bg-white dark-theme:hover:bg-zinc-800"
>
<template #top>
<CardTop ratio="landscape">
<template #default>
<div
class="w-full h-full bg-dialog-surface animate-pulse"
class="h-full w-full animate-pulse bg-dialog-surface"
></div>
</template>
</CardTop>
@@ -157,10 +159,10 @@
<CardBottom>
<div class="px-4 py-3">
<div
class="h-6 bg-dialog-surface rounded animate-pulse mb-2"
class="mb-2 h-6 animate-pulse rounded bg-dialog-surface"
></div>
<div
class="h-4 bg-dialog-surface rounded animate-pulse"
class="h-4 animate-pulse rounded bg-dialog-surface"
></div>
</div>
</CardBottom>
@@ -172,9 +174,11 @@
v-for="template in isLoading ? [] : displayTemplates"
:key="template.name"
ref="cardRefs"
ratio="smallSquare"
type="workflow-template-card"
size="compact"
variant="ghost"
rounded="lg"
:data-testid="`template-workflow-${template.name}`"
class="hover:bg-white dark-theme:hover:bg-zinc-800"
@mouseenter="hoveredTemplate = template.name"
@mouseleave="hoveredTemplate = null"
@click="onLoadWorkflow(template)"
@@ -184,7 +188,7 @@
<template #default>
<!-- Template Thumbnail -->
<div
class="w-full h-full relative rounded-lg overflow-hidden"
class="relative h-full w-full overflow-hidden rounded-lg"
>
<template v-if="template.mediaType === 'audio'">
<AudioThumbnail :src="getBaseThumbnailSrc(template)" />
@@ -248,7 +252,7 @@
</template>
<ProgressSpinner
v-if="loadingTemplate === template.name"
class="absolute inset-0 z-10 w-12 h-12 m-auto"
class="absolute inset-0 z-10 m-auto h-12 w-12"
/>
</div>
</template>
@@ -267,7 +271,7 @@
<CardBottom>
<div class="flex flex-col gap-2 pt-3">
<h3
class="line-clamp-1 text-sm m-0"
class="m-0 line-clamp-1 text-sm"
:title="
getTemplateTitle(
template,
@@ -285,7 +289,7 @@
<div class="flex justify-between gap-2">
<div class="flex-1">
<p
class="line-clamp-2 text-sm text-muted m-0"
class="m-0 line-clamp-2 text-sm text-muted"
:title="getTemplateDescription(template)"
>
{{ getTemplateDescription(template) }}
@@ -316,14 +320,16 @@
<CardContainer
v-for="n in isLoadingMore ? 6 : 0"
:key="`skeleton-${n}`"
ratio="smallSquare"
type="workflow-template-card"
size="compact"
variant="ghost"
rounded="lg"
class="hover:bg-white dark-theme:hover:bg-zinc-800"
>
<template #top>
<CardTop ratio="square">
<template #default>
<div
class="w-full h-full bg-dialog-surface animate-pulse"
class="h-full w-full animate-pulse bg-dialog-surface"
></div>
</template>
</CardTop>
@@ -332,10 +338,10 @@
<CardBottom>
<div class="px-4 py-3">
<div
class="h-6 bg-dialog-surface rounded animate-pulse mb-2"
class="mb-2 h-6 animate-pulse rounded bg-dialog-surface"
></div>
<div
class="h-4 bg-dialog-surface rounded animate-pulse"
class="h-4 animate-pulse rounded bg-dialog-surface"
></div>
</div>
</CardBottom>
@@ -348,7 +354,7 @@
<div
v-if="!isLoading && hasMoreTemplates"
ref="loadTrigger"
class="w-full h-4 flex justify-center items-center mt-4"
class="mt-4 flex h-4 w-full items-center justify-center"
>
<div v-if="isLoadingMore" class="text-sm text-muted">
{{ $t('templateWorkflows.loadingMore', 'Loading more...') }}
@@ -620,10 +626,7 @@ const sortOptions = computed(() => [
value: 'default'
},
{
name: t(
'templateWorkflows.sort.vramLowToHigh',
'VRAM Utilization (Low to High)'
),
name: t('templateWorkflows.sort.vramLowToHigh', 'VRAM Usage (Low to High)'),
value: 'vram-low-to-high'
},
{

View File

@@ -1,16 +1,16 @@
<template>
<div class="flex flex-col gap-4 max-w-96 h-110 p-2">
<div class="text-2xl font-medium mb-2">
<div class="flex h-110 max-w-96 flex-col gap-4 p-2">
<div class="mb-2 text-2xl font-medium">
{{ t('apiNodesSignInDialog.title') }}
</div>
<div class="text-base mb-4">
<div class="mb-4 text-base">
{{ t('apiNodesSignInDialog.message') }}
</div>
<ApiNodesList :node-names="apiNodeNames" />
<div class="flex justify-between items-center">
<div class="flex items-center justify-between">
<Button :label="t('g.learnMore')" link @click="handleLearnMoreClick" />
<div class="flex gap-2">
<Button

View File

@@ -1,7 +1,7 @@
<template>
<section class="prompt-dialog-content flex flex-col gap-6 m-2 mt-4">
<section class="prompt-dialog-content m-2 mt-4 flex flex-col gap-6">
<span>{{ message }}</span>
<ul v-if="itemList?.length" class="pl-4 m-0 flex flex-col gap-2">
<ul v-if="itemList?.length" class="m-0 flex flex-col gap-2 pl-4">
<li v-for="item of itemList" :key="item">
{{ item }}
</li>
@@ -15,14 +15,14 @@
>
{{ hint }}
</Message>
<div class="flex gap-4 justify-end">
<div class="flex justify-end gap-4">
<div
v-if="type === 'overwriteBlueprint'"
class="flex gap-4 justify-start"
class="flex justify-start gap-4"
>
<Checkbox
v-model="doNotAskAgain"
class="flex gap-4 justify-start"
class="flex justify-start gap-4"
input-id="doNotAskAgain"
binary
/>

View File

@@ -13,7 +13,7 @@
<span class="font-bold">{{ error.extensionFile }}</span>
</template>
<div class="flex gap-2 justify-center">
<div class="flex justify-center gap-2">
<Button
v-show="!reportOpen"
text
@@ -29,12 +29,12 @@
</div>
<template v-if="reportOpen">
<Divider />
<ScrollPanel class="w-full h-[400px] max-w-[80vw]">
<pre class="whitespace-pre-wrap break-words">{{ reportContent }}</pre>
<ScrollPanel class="h-[400px] w-full max-w-[80vw]">
<pre class="break-words whitespace-pre-wrap">{{ reportContent }}</pre>
</ScrollPanel>
<Divider />
</template>
<div class="flex gap-4 justify-end">
<div class="flex justify-end gap-4">
<FindIssueButton
:error-message="error.exceptionMessage"
:repo-owner="repoOwner"
@@ -65,10 +65,8 @@ import { api } from '@/scripts/api'
import { app } from '@/scripts/app'
import { useCommandStore } from '@/stores/commandStore'
import { useSystemStatsStore } from '@/stores/systemStatsStore'
import {
type ErrorReportData,
generateErrorReport
} from '@/utils/errorReportUtil'
import { generateErrorReport } from '@/utils/errorReportUtil'
import type { ErrorReportData } from '@/utils/errorReportUtil'
const { error } = defineProps<{
error: Omit<ErrorReportData, 'workflow' | 'systemStats' | 'serverLogs'> & {

View File

@@ -16,7 +16,7 @@
}"
>
<template #option="slotProps">
<div class="flex align-items-center">
<div class="align-items-center flex">
<span class="node-type">{{ slotProps.option.label }}</span>
<span v-if="slotProps.option.hint" class="node-hint">{{
slotProps.option.hint

View File

@@ -3,7 +3,7 @@
v-if="hasMissingCoreNodes"
severity="info"
icon="pi pi-info-circle"
class="my-2 mx-2"
class="mx-2 my-2"
:pt="{
root: { class: 'flex-col' },
text: { class: 'flex-1' }

View File

@@ -5,7 +5,7 @@
:title="t('missingModelsDialog.missingModels')"
:message="t('missingModelsDialog.missingModelsMessage')"
/>
<div class="flex gap-1 mb-4">
<div class="mb-4 flex gap-1">
<Checkbox v-model="doNotAskAgain" binary input-id="doNotAskAgain" />
<label for="doNotAskAgain">{{
t('missingModelsDialog.doNotAskAgain')

View File

@@ -1,5 +1,5 @@
<template>
<div class="w-96 p-2 overflow-x-hidden">
<div class="w-96 overflow-x-hidden p-2">
<ApiKeyForm
v-if="showApiKeyForm"
@back="showApiKeyForm = false"
@@ -7,11 +7,11 @@
/>
<template v-else>
<!-- Header -->
<div class="flex flex-col gap-4 mb-8">
<h1 class="text-2xl font-medium leading-normal my-0">
<div class="mb-8 flex flex-col gap-4">
<h1 class="my-0 text-2xl leading-normal font-medium">
{{ isSignIn ? t('auth.login.title') : t('auth.signup.title') }}
</h1>
<p class="text-base my-0">
<p class="my-0 text-base">
<span class="text-muted">{{
isSignIn
? t('auth.login.newUser')
@@ -92,17 +92,17 @@
>
<img
src="/assets/images/comfy-logo-mono.svg"
class="w-5 h-5 mr-2"
class="mr-2 h-5 w-5"
alt="Comfy"
/>
{{ t('auth.login.useApiKey') }}
</Button>
<small class="text-muted text-center">
<small class="text-center text-muted">
{{ t('auth.apiKey.helpText') }}
<a
:href="`${COMFY_PLATFORM_BASE_URL}/login`"
target="_blank"
class="text-blue-500 cursor-pointer"
class="cursor-pointer text-blue-500"
>
{{ t('auth.apiKey.generateKey') }}
</a>
@@ -119,12 +119,12 @@
</div>
<!-- Terms & Contact -->
<p class="text-xs text-muted mt-8">
<p class="mt-8 text-xs text-muted">
{{ t('auth.login.termsText') }}
<a
href="https://www.comfy.org/terms-of-service"
target="_blank"
class="text-blue-500 cursor-pointer"
class="cursor-pointer text-blue-500"
>
{{ t('auth.login.termsLink') }}
</a>
@@ -132,12 +132,12 @@
<a
href="https://www.comfy.org/privacy"
target="_blank"
class="text-blue-500 cursor-pointer"
class="cursor-pointer text-blue-500"
>
{{ t('auth.login.privacyLink') }} </a
>.
{{ t('auth.login.questionsContactPrefix') }}
<a href="mailto:hello@comfy.org" class="text-blue-500 cursor-pointer">
<a href="mailto:hello@comfy.org" class="cursor-pointer text-blue-500">
hello@comfy.org</a
>.
</p>

View File

@@ -1,21 +1,21 @@
<template>
<div class="flex flex-col w-96 p-2 gap-10">
<div class="flex w-96 flex-col gap-10 p-2">
<div v-if="isInsufficientCredits" class="flex flex-col gap-4">
<h1 class="text-2xl font-medium leading-normal my-0">
<h1 class="my-0 text-2xl leading-normal font-medium">
{{ $t('credits.topUp.insufficientTitle') }}
</h1>
<p class="text-base my-0">
<p class="my-0 text-base">
{{ $t('credits.topUp.insufficientMessage') }}
</p>
</div>
<!-- Balance Section -->
<div class="flex justify-between items-center">
<div class="flex flex-col gap-2 w-full">
<div class="text-muted text-base">
<div class="flex items-center justify-between">
<div class="flex w-full flex-col gap-2">
<div class="text-base text-muted">
{{ $t('credits.yourCreditBalance') }}
</div>
<div class="flex items-center justify-between w-full">
<div class="flex w-full items-center justify-between">
<UserCredit text-class="text-2xl" />
<Button
outlined
@@ -30,7 +30,7 @@
<!-- Amount Input Section -->
<div class="flex flex-col gap-2">
<span class="text-muted text-sm"
<span class="text-sm text-muted"
>{{ $t('credits.topUp.quickPurchase') }}:</span
>
<div class="grid grid-cols-[2fr_1fr] gap-2">

View File

@@ -1,6 +1,6 @@
<template>
<Form
class="flex flex-col gap-6 w-96"
class="flex w-96 flex-col gap-6"
:resolver="zodResolver(updatePasswordSchema)"
@submit="onSubmit"
>
@@ -10,7 +10,7 @@
<Button
type="submit"
:label="$t('userSettings.updatePassword')"
class="h-10 font-medium mt-4"
class="mt-4 h-10 font-medium"
:loading="loading"
/>
</Form>

View File

@@ -4,7 +4,7 @@
severity="secondary"
icon="pi pi-dollar"
rounded
class="text-amber-400 p-1"
class="p-1 text-amber-400"
/>
<InputNumber
v-if="editable"
@@ -21,7 +21,7 @@
/>
<span v-else class="text-xl">{{ amount }}</span>
</div>
<ProgressSpinner v-if="loading" class="w-8 h-8" />
<ProgressSpinner v-if="loading" class="h-8 w-8" />
<Button
v-else
:severity="preselected ? 'primary' : 'secondary'"
@@ -33,9 +33,10 @@
<script setup lang="ts">
import Button from 'primevue/button'
import InputNumber, {
type InputNumberBlurEvent,
type InputNumberInputEvent
import InputNumber from 'primevue/inputnumber'
import type {
InputNumberBlurEvent,
InputNumberInputEvent
} from 'primevue/inputnumber'
import ProgressSpinner from 'primevue/progressspinner'
import Tag from 'primevue/tag'

View File

@@ -1,6 +1,6 @@
<template>
<PanelTemplate value="About" class="about-container">
<h2 class="text-2xl font-bold mb-2">
<h2 class="mb-2 text-2xl font-bold">
{{ $t('g.about') }}
</h2>
<div class="space-y-2">

View File

@@ -1,7 +1,7 @@
<template>
<TabPanel value="Credits" class="credits-container h-full">
<div class="flex flex-col h-full">
<h2 class="text-2xl font-bold mb-2">
<div class="flex h-full flex-col">
<h2 class="mb-2 text-2xl font-bold">
{{ $t('credits.credits') }}
</h2>
@@ -11,7 +11,7 @@
<h3 class="text-sm font-medium text-muted">
{{ $t('credits.yourCreditBalance') }}
</h3>
<div class="flex justify-between items-center">
<div class="flex items-center justify-between">
<UserCredit text-class="text-3xl font-bold" />
<Skeleton v-if="loading" width="2rem" height="2rem" />
<Button
@@ -41,7 +41,7 @@
</div>
</div>
<div class="flex justify-between items-center">
<div class="flex items-center justify-between">
<h3>{{ $t('credits.activity') }}</h3>
<Button
:label="$t('credits.invoiceHistory')"
@@ -66,7 +66,7 @@
<template #body="{ data }">
<div
:class="[
'text-base font-medium text-center',
'text-center text-base font-medium',
data.isPositive ? 'text-sky-500' : 'text-red-400'
]"
>

View File

@@ -51,10 +51,7 @@
class="max-w-64 2xl:max-w-full"
>
<template #body="slotProps">
<div
class="overflow-hidden text-ellipsis whitespace-nowrap"
:title="slotProps.data.id"
>
<div class="truncate" :title="slotProps.data.id">
{{ slotProps.data.label }}
</div>
</template>

View File

@@ -1,8 +1,8 @@
<template>
<TabPanel :value="props.value" class="h-full w-full" :class="props.class">
<div class="flex flex-col h-full w-full gap-2">
<div class="flex h-full w-full flex-col gap-2">
<slot name="header" />
<ScrollPanel class="grow h-0 pr-2">
<ScrollPanel class="h-0 grow pr-2">
<slot />
</ScrollPanel>
<slot name="footer" />

View File

@@ -30,7 +30,7 @@
<div class="event-details">
<!-- Credits Added -->
<template v-if="data.event_type === EventType.CREDIT_ADDED">
<div class="text-green-500 font-semibold">
<div class="font-semibold text-green-500">
{{ $t('credits.added') }} ${{
customerEventService.formatAmount(data.params?.amount)
}}

View File

@@ -1,7 +1,7 @@
<template>
<TabPanel value="User" class="user-settings-container h-full">
<div class="flex flex-col h-full">
<h2 class="text-2xl font-bold mb-2">{{ $t('userSettings.title') }}</h2>
<div class="flex h-full flex-col">
<h2 class="mb-2 text-2xl font-bold">{{ $t('userSettings.title') }}</h2>
<Divider class="mb-3" />
<!-- Normal User Panel -->
@@ -35,7 +35,7 @@
<h3 class="font-medium">
{{ $t('userSettings.provider') }}
</h3>
<div class="text-muted flex items-center gap-1">
<div class="flex items-center gap-1 text-muted">
<i :class="providerIcon" />
{{ providerName }}
<Button
@@ -54,7 +54,7 @@
<ProgressSpinner
v-if="loading"
class="w-8 h-8 mt-4"
class="mt-4 h-8 w-8"
style="--pc-spinner-color: #000"
/>
<div v-else class="mt-4 flex flex-col gap-2">

View File

@@ -1,17 +1,17 @@
<template>
<div class="flex flex-col gap-6">
<div class="flex flex-col gap-4 mb-8">
<h1 class="text-2xl font-medium leading-normal my-0">
<div class="mb-8 flex flex-col gap-4">
<h1 class="my-0 text-2xl leading-normal font-medium">
{{ t('auth.apiKey.title') }}
</h1>
<div class="flex flex-col gap-2">
<p class="text-base my-0 text-muted">
<p class="my-0 text-base text-muted">
{{ t('auth.apiKey.description') }}
</p>
<a
href="https://docs.comfy.org/interface/user#logging-in-with-an-api-key"
target="_blank"
class="text-blue-500 cursor-pointer"
class="cursor-pointer text-blue-500"
>
{{ t('g.learnMore') }}
</a>
@@ -30,7 +30,7 @@
<div class="flex flex-col gap-2">
<label
class="opacity-80 text-base font-medium mb-2"
class="mb-2 text-base font-medium opacity-80"
for="comfy-org-api-key"
>
{{ t('auth.apiKey.label') }}
@@ -50,7 +50,7 @@
<a
:href="`${COMFY_PLATFORM_BASE_URL}/login`"
target="_blank"
class="text-blue-500 cursor-pointer"
class="cursor-pointer text-blue-500"
>
{{ t('auth.apiKey.generateKey') }}
</a>
@@ -58,7 +58,7 @@
<a
href="https://docs.comfy.org/tutorials/api-nodes/overview#log-in-with-api-key-on-non-whitelisted-websites"
target="_blank"
class="text-blue-500 cursor-pointer"
class="cursor-pointer text-blue-500"
>
{{ t('auth.apiKey.whitelistInfo') }}
</a>
@@ -66,7 +66,7 @@
</div>
</div>
<div class="flex justify-between items-center mt-4">
<div class="mt-4 flex items-center justify-between">
<Button type="button" link @click="$emit('back')">
{{ t('g.back') }}
</Button>

View File

@@ -1,9 +1,9 @@
<template>
<!-- Password Field -->
<FormField v-slot="$field" name="password" class="flex flex-col gap-2">
<div class="flex justify-between items-center mb-2">
<div class="mb-2 flex items-center justify-between">
<label
class="opacity-80 text-base font-medium"
class="text-base font-medium opacity-80"
for="comfy-org-sign-up-password"
>
{{ t('auth.signup.passwordLabel') }}
@@ -68,7 +68,7 @@
<!-- Confirm Password Field -->
<FormField v-slot="$field" name="confirmPassword" class="flex flex-col gap-2">
<label
class="opacity-80 text-base font-medium mb-2"
class="mb-2 text-base font-medium opacity-80"
for="comfy-org-sign-up-confirm-password"
>
{{ t('auth.login.confirmPasswordLabel') }}

View File

@@ -7,7 +7,7 @@
>
<!-- Email Field -->
<div class="flex flex-col gap-2">
<label class="opacity-80 text-base font-medium mb-2" :for="emailInputId">
<label class="mb-2 text-base font-medium opacity-80" :for="emailInputId">
{{ t('auth.login.emailLabel') }}
</label>
<InputText
@@ -26,15 +26,15 @@
<!-- Password Field -->
<div class="flex flex-col gap-2">
<div class="flex justify-between items-center mb-2">
<div class="mb-2 flex items-center justify-between">
<label
class="opacity-80 text-base font-medium"
class="text-base font-medium opacity-80"
for="comfy-org-sign-in-password"
>
{{ t('auth.login.passwordLabel') }}
</label>
<span
class="text-muted text-base font-medium cursor-pointer select-none"
class="cursor-pointer text-base font-medium text-muted select-none"
:class="{
'text-link-disabled': !$form.email?.value || $form.email?.invalid
}"
@@ -65,12 +65,12 @@
</Message>
<!-- Submit Button -->
<ProgressSpinner v-if="loading" class="w-8 h-8" />
<ProgressSpinner v-if="loading" class="h-8 w-8" />
<Button
v-else
type="submit"
:label="t('auth.login.loginButton')"
class="h-10 font-medium mt-4"
class="mt-4 h-10 font-medium"
/>
</Form>
</template>
@@ -89,7 +89,8 @@ import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
import { type SignInData, signInSchema } from '@/schemas/signInSchema'
import { signInSchema } from '@/schemas/signInSchema'
import type { SignInData } from '@/schemas/signInSchema'
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
const authStore = useFirebaseAuthStore()

View File

@@ -7,7 +7,7 @@
<!-- Email Field -->
<FormField v-slot="$field" name="email" class="flex flex-col gap-2">
<label
class="opacity-80 text-base font-medium mb-2"
class="mb-2 text-base font-medium opacity-80"
for="comfy-org-sign-up-email"
>
{{ t('auth.signup.emailLabel') }}
@@ -40,11 +40,11 @@
/>
<label
for="comfy-org-sign-up-personal-data-consent"
class="opacity-80 text-base font-medium"
class="text-base font-medium opacity-80"
>
{{ t('auth.signup.personalDataConsentLabel') }}
</label>
<small v-if="$field.error" class="text-red-500 mt-4">{{
<small v-if="$field.error" class="-mt-4 text-red-500">{{
$field.error.message
}}</small>
</FormField>
@@ -58,7 +58,7 @@
<Button
type="submit"
:label="t('auth.signup.signUpButton')"
class="h-10 font-medium mt-4 text-white"
class="mt-4 h-10 font-medium"
/>
</Form>
</template>
@@ -73,7 +73,8 @@ import InputText from 'primevue/inputtext'
import Message from 'primevue/message'
import { useI18n } from 'vue-i18n'
import { type SignUpData, signUpSchema } from '@/schemas/signInSchema'
import { signUpSchema } from '@/schemas/signInSchema'
import type { SignUpData } from '@/schemas/signInSchema'
import PasswordFields from './PasswordFields.vue'

View File

@@ -10,7 +10,7 @@
<BottomPanel />
</template>
<template #graph-canvas-panel>
<div class="absolute top-0 left-0 w-auto max-w-full pointer-events-auto">
<div class="pointer-events-auto absolute top-0 left-0 w-auto max-w-full">
<SecondRowWorkflowTabs
v-if="workflowTabsPosition === 'Topbar (2nd-row)'"
/>
@@ -113,7 +113,6 @@ import { useWorkflowStore } from '@/platform/workflow/management/stores/workflow
import { useWorkflowAutoSave } from '@/platform/workflow/persistence/composables/useWorkflowAutoSave'
import { useWorkflowPersistence } from '@/platform/workflow/persistence/composables/useWorkflowPersistence'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { attachSlotLinkPreviewRenderer } from '@/renderer/core/canvas/links/slotLinkPreviewRenderer'
import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'
import TransformPane from '@/renderer/core/layout/transform/TransformPane.vue'
import MiniMap from '@/renderer/extensions/minimap/MiniMap.vue'
@@ -401,7 +400,6 @@ onMounted(async () => {
// @ts-expect-error fixme ts strict error
await comfyApp.setup(canvasRef.value)
attachSlotLinkPreviewRenderer(comfyApp.canvas)
canvasStore.canvas = comfyApp.canvas
canvasStore.canvas.render_canvas_border = false
workspaceStore.spinner = false

View File

@@ -1,6 +1,6 @@
<template>
<div>
<ZoomControlsModal :visible="isModalVisible" />
<ZoomControlsModal :visible="isModalVisible" @close="hideModal" />
<!-- Backdrop -->
<div
@@ -10,7 +10,7 @@
></div>
<ButtonGroup
class="p-buttongroup-vertical p-1 absolute bottom-4 right-2 md:right-4"
class="p-buttongroup-vertical absolute right-2 bottom-4 p-1 md:right-4"
:style="stringifiedMinimapStyles.buttonGroupStyles"
@wheel="canvasInteractions.handleWheel"
>
@@ -25,7 +25,7 @@
@click="() => commandStore.execute('Comfy.Canvas.Unlock')"
>
<template #icon>
<i-lucide:mouse-pointer-2 />
<i class="icon-[lucide--mouse-pointer-2]" />
</template>
</Button>
@@ -39,12 +39,12 @@
@click="() => commandStore.execute('Comfy.Canvas.Lock')"
>
<template #icon>
<i-lucide:hand />
<i class="icon-[lucide--hand]" />
</template>
</Button>
<!-- vertical line with bg E1DED5 -->
<div class="w-px my-1 bg-[#E1DED5] dark-theme:bg-[#2E3037] mx-2" />
<div class="mx-2 my-1 w-px bg-[#E1DED5] dark-theme:bg-[#2E3037]" />
<Button
v-tooltip.top="fitViewTooltip"
@@ -52,11 +52,11 @@
icon="pi pi-expand"
:aria-label="fitViewTooltip"
:style="stringifiedMinimapStyles.buttonStyles"
class="dark-theme:hover:bg-[#444444]! hover:bg-[#E7E6E6]!"
class="hover:bg-[#E7E6E6]! dark-theme:hover:bg-[#444444]!"
@click="() => commandStore.execute('Comfy.Canvas.FitView')"
>
<template #icon>
<i-lucide:focus />
<i class="icon-[lucide--focus]" />
</template>
</Button>
@@ -73,11 +73,11 @@
>
<span class="inline-flex text-xs">
<span>{{ canvasStore.appScalePercentage }}%</span>
<i-lucide:chevron-down />
<i class="icon-[lucide--chevron-down]" />
</span>
</Button>
<div class="w-px my-1 bg-[#E1DED5] dark-theme:bg-[#2E3037] mx-2" />
<div class="mx-2 my-1 w-px bg-[#E1DED5] dark-theme:bg-[#2E3037]" />
<Button
ref="focusButton"
@@ -90,7 +90,7 @@
@click="() => commandStore.execute('Workspace.ToggleFocusMode')"
>
<template #icon>
<i-lucide:lightbulb />
<i class="icon-[lucide--lightbulb]" />
</template>
</Button>
@@ -111,7 +111,7 @@
@click="() => commandStore.execute('Comfy.Canvas.ToggleLinkVisibility')"
>
<template #icon>
<i-lucide:route-off />
<i class="icon-[lucide--route-off]" />
</template>
</Button>
</ButtonGroup>

View File

@@ -136,7 +136,7 @@ useEventListener(window, 'click', hideTooltip)
pointer-events: none;
background: var(--comfy-input-bg);
border-radius: 5px;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.4);
box-shadow: 0 0 5px rgb(0 0 0 / 0.4);
color: var(--input-text);
font-family: sans-serif;
left: 0;

View File

@@ -2,12 +2,12 @@
<div
ref="toolboxRef"
style="transform: translate(var(--tb-x), var(--tb-y))"
class="fixed left-0 top-0 z-40 pointer-events-none"
class="pointer-events-none fixed top-0 left-0 z-40"
>
<Transition name="slide-up">
<Panel
v-if="visible"
class="rounded-lg selection-toolbox pointer-events-auto"
class="selection-toolbox pointer-events-auto rounded-lg"
:style="`backgroundColor: ${containerStyles.backgroundColor};`"
:pt="{
header: 'hidden',
@@ -67,7 +67,8 @@ import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'
import { useMinimap } from '@/renderer/extensions/minimap/composables/useMinimap'
import { useExtensionService } from '@/services/extensionService'
import { type ComfyCommandImpl, useCommandStore } from '@/stores/commandStore'
import { useCommandStore } from '@/stores/commandStore'
import type { ComfyCommandImpl } from '@/stores/commandStore'
import FrameNodes from './selectionToolbox/FrameNodes.vue'
import NodeOptionsButton from './selectionToolbox/NodeOptionsButton.vue'

View File

@@ -14,7 +14,8 @@
<script setup lang="ts">
import { useEventListener } from '@vueuse/core'
import { type CSSProperties, computed, ref, watch } from 'vue'
import { computed, ref, watch } from 'vue'
import type { CSSProperties } from 'vue'
import EditableText from '@/components/common/EditableText.vue'
import { useAbsolutePosition } from '@/composables/element/useAbsolutePosition'
@@ -47,7 +48,7 @@ const canvasStore = useCanvasStore()
const previousCanvasDraggable = ref(true)
const onEdit = (newValue: string) => {
if (titleEditorStore.titleEditorTarget && newValue.trim() !== '') {
if (titleEditorStore.titleEditorTarget && newValue?.trim()) {
const trimmedTitle = newValue.trim()
titleEditorStore.titleEditorTarget.title = trimmedTitle

View File

@@ -1,10 +1,10 @@
<template>
<div
v-if="visible"
class="w-[250px] absolute flex justify-center right-2 md:right-11 z-1300 bottom-[66px] bg-inherit! border-0!"
class="absolute right-2 bottom-[66px] z-1300 flex w-[250px] justify-center border-0! bg-inherit! md:right-11"
>
<div
class="bg-white dark-theme:bg-[#2b2b2b] border border-gray-200 dark-theme:border-gray-700 rounded-lg shadow-lg p-4 w-4/5"
class="w-4/5 rounded-lg border border-gray-200 bg-white p-4 shadow-lg dark-theme:border-gray-700 dark-theme:bg-[#2b2b2b]"
:style="filteredMinimapStyles"
@click.stop
>
@@ -26,10 +26,10 @@
@mouseleave="stopRepeat"
>
<template #default>
<span class="text-sm font-medium block">{{
<span class="block text-sm font-medium">{{
$t('graphCanvasMenu.zoomIn')
}}</span>
<span class="text-sm text-gray-500 block">{{
<span class="block text-sm text-gray-500">{{
zoomInCommandText
}}</span>
</template>
@@ -52,10 +52,10 @@
@mouseleave="stopRepeat"
>
<template #default>
<span class="text-sm font-medium block">{{
<span class="block text-sm font-medium">{{
$t('graphCanvasMenu.zoomOut')
}}</span>
<span class="text-sm text-gray-500 block">{{
<span class="block text-sm text-gray-500">{{
zoomOutCommandText
}}</span>
</template>
@@ -76,15 +76,15 @@
@click="executeCommand('Comfy.Canvas.FitView')"
>
<template #default>
<span class="text-sm font-medium block">{{
<span class="block text-sm font-medium">{{
$t('zoomControls.zoomToFit')
}}</span>
<span class="text-sm text-gray-500 block">{{
<span class="block text-sm text-gray-500">{{
zoomToFitCommandText
}}</span>
</template>
</Button>
<hr class="border-[#E1DED5] mb-1 dark-theme:border-[#2E3037]" />
<hr class="mb-1 border-[#E1DED5] dark-theme:border-[#2E3037]" />
<Button
severity="secondary"
text
@@ -101,18 +101,18 @@
@click="executeCommand('Comfy.Canvas.ToggleMinimap')"
>
<template #default>
<span class="text-sm font-medium block">{{
<span class="block text-sm font-medium">{{
minimapToggleText
}}</span>
<span class="text-sm text-gray-500 block">{{
<span class="block text-sm text-gray-500">{{
showMinimapCommandText
}}</span>
</template>
</Button>
<hr class="border-[#E1DED5] mt-1 dark-theme:border-[#2E3037]" />
<hr class="mt-1 border-[#E1DED5] dark-theme:border-[#2E3037]" />
<div
ref="zoomInputContainer"
class="flex items-center px-2 bg-[#E7E6E6] focus-within:bg-[#F3F3F3] dark-theme:bg-[#8282821A] rounded p-2 zoomInputContainer"
class="zoomInputContainer flex items-center rounded bg-[#E7E6E6] p-2 px-2 focus-within:bg-[#F3F3F3] dark-theme:bg-[#8282821A]"
>
<InputNumber
ref="zoomInput"
@@ -127,7 +127,7 @@
@input="applyZoom"
@keyup.enter="applyZoom"
/>
<span class="text-sm text-gray-500 -ml-4">%</span>
<span class="-ml-4 text-sm text-gray-500">%</span>
</div>
</div>
</div>
@@ -158,6 +158,10 @@ interface Props {
const props = defineProps<Props>()
const emit = defineEmits<{
close: []
}>()
const interval = ref<number | null>(null)
// Computed properties for reactive states
@@ -177,6 +181,9 @@ const applyZoom = (val: InputNumberInputEvent) => {
const executeCommand = (command: string) => {
void commandStore.execute(command)
if (command === 'Comfy.Canvas.ToggleMinimap') {
emit('close')
}
}
const startRepeat = (command: string) => {
@@ -229,10 +236,10 @@ watch(
</script>
<style>
.zoomInputContainer:focus-within {
border: 1px solid rgb(204, 204, 204);
border: 1px solid rgb(204 204 204);
}
.dark-theme .zoomInputContainer:focus-within {
border: 1px solid rgb(204, 204, 204);
border: 1px solid rgb(204 204 204);
}
</style>

View File

@@ -7,11 +7,11 @@
severity="secondary"
text
data-testid="bypass-button"
class="hover:dark-theme:bg-charcoal-600 hover:bg-[#E7E6E6]"
class="hover:bg-[#E7E6E6] hover:dark-theme:bg-charcoal-600"
@click="toggleBypass"
>
<template #icon>
<i-lucide:ban class="w-4 h-4" />
<i class="icon-[lucide--ban] h-4 w-4" />
</template>
</Button>
</template>

View File

@@ -12,11 +12,11 @@
>
<div class="flex items-center gap-1 px-0">
<i
class="w-4 h-4 pi pi-circle-fill"
class="pi pi-circle-fill h-4 w-4"
:style="{ color: currentColor ?? '' }"
/>
<i
class="w-4 h-4 pi pi-chevron-down py-1"
class="pi pi-chevron-down h-4 w-4 py-1"
:style="{ fontSize: '0.5rem' }"
/>
</div>

View File

@@ -11,7 +11,7 @@
@click="() => commandStore.execute('Comfy.Graph.UnpackSubgraph')"
>
<template #icon>
<i-lucide:expand class="w-4 h-4" />
<i class="icon-[lucide--expand] h-4 w-4" />
</template>
</Button>
<Button
@@ -26,7 +26,7 @@
@click="() => commandStore.execute('Comfy.Graph.ConvertToSubgraph')"
>
<template #icon>
<i-lucide:shrink />
<i class="icon-[lucide--shrink]" />
</template>
</Button>
</template>

View File

@@ -4,13 +4,13 @@
value: t('selectionToolbox.executeButton.tooltip'),
showDelay: 1000
}"
class="dark-theme:bg-[#0B8CE9] bg-[#31B9F4] size-8 !p-0"
class="size-8 bg-[#31B9F4] !p-0 dark-theme:bg-[#0B8CE9]"
text
@mouseenter="() => handleMouseEnter()"
@mouseleave="() => handleMouseLeave()"
@click="handleClick"
>
<i-lucide:play class="fill-path-white w-4 h-4" />
<i class="icon-[lucide--play] size-4 text-white" />
</Button>
</template>
@@ -64,9 +64,3 @@ const handleClick = async () => {
await commandStore.execute('Comfy.QueueSelectedOutputNodes')
}
</script>
<style scoped>
:deep.fill-path-white > path {
fill: white;
stroke: unset;
}
</style>

View File

@@ -9,7 +9,7 @@
severity="secondary"
@click="frameNodes"
>
<i-lucide:frame class="w-4 h-4" />
<i class="icon-[lucide--frame] h-4 w-4" />
</Button>
</template>

View File

@@ -9,7 +9,7 @@
severity="secondary"
@click="toggleHelp"
>
<i-lucide:info class="w-4 h-4" />
<i class="icon-[lucide--info] h-4 w-4" />
</Button>
</template>

View File

@@ -9,7 +9,7 @@
text
@click="openMaskEditor"
>
<i-comfy:mask class="!w-4 !h-4" />
<i-comfy:mask class="!h-4 !w-4" />
</Button>
</template>

View File

@@ -1,34 +1,34 @@
<template>
<div
v-if="option.type === 'divider'"
class="h-px bg-gray-200 dark-theme:bg-zinc-700 my-1"
class="my-1 h-px bg-gray-200 dark-theme:bg-zinc-700"
/>
<div
v-else
role="button"
class="flex items-center gap-2 px-3 py-1.5 text-sm text-left hover:bg-gray-100 dark-theme:hover:bg-zinc-700 rounded cursor-pointer"
class="flex cursor-pointer items-center gap-2 rounded px-3 py-1.5 text-left text-sm hover:bg-gray-100 dark-theme:hover:bg-zinc-700"
@click="handleClick"
>
<i v-if="option.icon" :class="[option.icon, 'w-4 h-4']" />
<i v-if="option.icon" :class="[option.icon, 'h-4 w-4']" />
<span class="flex-1">{{ option.label }}</span>
<span v-if="option.shortcut" class="text-xs opacity-60">
{{ option.shortcut }}
</span>
<i-lucide:chevron-right
<i
v-if="option.hasSubmenu"
:size="14"
class="opacity-60"
class="icon-[lucide--chevron-right] opacity-60"
/>
<Badge
v-if="option.badge"
:severity="option.badge === 'new' ? 'info' : 'secondary'"
:value="t(option.badge)"
:class="{
'bg-[#31B9F4] dark-theme:bg-[#0B8CE9] rounded-4xl':
'rounded-4xl bg-[#31B9F4] dark-theme:bg-[#0B8CE9]':
option.badge === 'new',
'bg-[#9C9EAB] dark-theme:bg-[#000] rounded-4xl':
'rounded-4xl bg-[#9C9EAB] dark-theme:bg-[#000]':
option.badge === 'deprecated',
'text-white uppercase text-[9px] h-4 px-1 gap-2.5': true
'h-4 gap-2.5 px-1 text-[9px] text-white uppercase': true
}"
/>
</div>

View File

@@ -13,7 +13,7 @@
@hide="onPopoverHide"
@wheel="canvasInteractions.forwardEventToCanvas"
>
<div class="flex flex-col p-2 min-w-48">
<div class="flex min-w-48 flex-col p-2">
<MenuOptionItem
v-for="(option, index) in menuOptions"
:key="option.label || `divider-${index}`"
@@ -46,11 +46,13 @@ import {
restoreMoreOptionsSignal
} from '@/composables/canvas/useSelectionToolboxPosition'
import {
type MenuOption,
type SubMenuOption,
registerNodeOptionsInstance,
useMoreOptionsMenu
} from '@/composables/graph/useMoreOptionsMenu'
import type {
MenuOption,
SubMenuOption
} from '@/composables/graph/useMoreOptionsMenu'
import { useSubmenuPositioning } from '@/composables/graph/useSubmenuPositioning'
import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'
import { useMinimap } from '@/renderer/extensions/minimap/composables/useMinimap'

View File

@@ -11,7 +11,7 @@
severity="secondary"
@click="handleClick"
>
<i-lucide:more-vertical class="w-4 h-4" />
<i class="icon-[lucide--more-vertical] h-4 w-4" />
</Button>
</template>

View File

@@ -7,7 +7,7 @@
data-testid="refresh-button"
@click="refreshSelected"
>
<i-lucide:refresh-cw class="w-4 h-4" />
<i class="icon-[lucide--refresh-cw] h-4 w-4" />
</Button>
</template>

View File

@@ -10,7 +10,7 @@
@click="() => commandStore.execute('Comfy.PublishSubgraph')"
>
<template #icon>
<i-lucide:book-open />
<i class="icon-[lucide--book-open]" />
</template>
</Button>
</template>

View File

@@ -28,13 +28,13 @@
>
<div
v-if="subOption.color"
class="w-5 h-5 rounded-full border border-gray-300 dark-theme:border-zinc-600"
class="h-5 w-5 rounded-full border border-gray-300 dark-theme:border-zinc-600"
:style="{ backgroundColor: subOption.color }"
/>
<template v-else-if="!subOption.color">
<i-lucide:check
<i
v-if="isShapeSelected(subOption)"
class="w-4 h-4 flex-shrink-0"
class="icon-[lucide--check] h-4 w-4 flex-shrink-0"
/>
<div v-else class="w-4 flex-shrink-0" />
<span>{{ subOption.label }}</span>

View File

@@ -1,3 +1,3 @@
<template>
<div class="h-6 w-px bg-gray-300/10 dark-theme:bg-gray-600/10 self-center" />
<div class="h-6 w-px self-center bg-gray-300/10 dark-theme:bg-gray-600/10" />
</template>

View File

@@ -1,24 +1,24 @@
<template>
<ScrollPanel
ref="scrollPanelRef"
class="w-full min-h-[400px] rounded-lg px-2 py-2 text-xs"
class="min-h-[400px] w-full rounded-lg px-2 py-2 text-xs"
:pt="{ content: { id: 'chat-scroll-content' } }"
>
<div v-for="(item, i) in parsedHistory" :key="i" class="mb-4">
<!-- Prompt (user, right) -->
<span
:class="{
'opacity-40 pointer-events-none': editIndex !== null && i > editIndex
'pointer-events-none opacity-40': editIndex !== null && i > editIndex
}"
>
<div class="flex justify-end mb-1">
<div class="mb-1 flex justify-end">
<div
class="bg-gray-300 dark-theme:bg-gray-800 rounded-xl px-4 py-1 max-w-[80%] text-right"
class="max-w-[80%] rounded-xl bg-gray-300 px-4 py-1 text-right dark-theme:bg-gray-800"
>
<div class="break-words text-[12px]">{{ item.prompt }}</div>
<div class="text-[12px] break-words">{{ item.prompt }}</div>
</div>
</div>
<div class="flex justify-end mb-2 mr-1">
<div class="mr-1 mb-2 flex justify-end">
<CopyButton :text="item.prompt" />
<Button
v-tooltip="
@@ -26,7 +26,7 @@
"
text
rounded
class="p-1! h-4! w-4! text-gray-400 hover:text-gray-600 hover:dark-theme:text-gray-200 transition"
class="h-4! w-4! p-1! text-gray-400 transition hover:text-gray-600 hover:dark-theme:text-gray-200"
pt:icon:class="text-xs!"
:icon="editIndex === i ? 'pi pi-times' : 'pi pi-pencil'"
:aria-label="
@@ -40,7 +40,7 @@
<ResponseBlurb
:text="item.response"
:class="{
'opacity-25 pointer-events-none': editIndex !== null && i >= editIndex
'pointer-events-none opacity-25': editIndex !== null && i >= editIndex
}"
>
<div v-html="nl2br(linkifyHtml(item.response))" />

View File

@@ -1,11 +1,11 @@
<template>
<div
class="relative w-full text-xs min-h-[28px] max-h-[200px] rounded-lg px-4 py-2 overflow-y-auto"
class="relative max-h-[200px] min-h-[28px] w-full overflow-y-auto rounded-lg px-4 py-2 text-xs"
>
<div class="flex items-center gap-2">
<div class="flex-1 break-all flex items-center gap-2">
<div class="flex flex-1 items-center gap-2 break-all">
<span v-html="formattedText"></span>
<Skeleton v-if="isParentNodeExecuting" class="flex-1! h-4!" />
<Skeleton v-if="isParentNodeExecuting" class="h-4! flex-1!" />
</div>
</div>
</div>

View File

@@ -5,7 +5,7 @@
"
text
rounded
class="p-1! h-4! w-6! text-gray-400 hover:text-gray-600 hover:dark-theme:text-gray-200 transition"
class="h-4! w-6! p-1! text-gray-400 transition hover:text-gray-600 hover:dark-theme:text-gray-200"
pt:icon:class="text-xs!"
:icon="copied ? 'pi pi-check' : 'pi pi-copy'"
:aria-label="

View File

@@ -1,13 +1,13 @@
<template>
<span>
<div class="flex justify-start mb-1">
<div class="rounded-xl px-4 py-1 max-w-[80%]">
<div class="break-words text-[12px]">
<div class="mb-1 flex justify-start">
<div class="max-w-[80%] rounded-xl px-4 py-1">
<div class="text-[12px] break-words">
<slot />
</div>
</div>
</div>
<div class="flex justify-start ml-1">
<div class="ml-1 flex justify-start">
<CopyButton :text="text" />
</div>
</span>

View File

@@ -130,14 +130,8 @@
<script setup lang="ts">
import Button from 'primevue/button'
import {
type CSSProperties,
type Component,
computed,
nextTick,
onMounted,
ref
} from 'vue'
import { computed, nextTick, onMounted, ref } from 'vue'
import type { CSSProperties, Component } from 'vue'
import { useI18n } from 'vue-i18n'
import PuzzleIcon from '@/components/icons/PuzzleIcon.vue'
@@ -526,7 +520,7 @@ onMounted(async () => {
overflow-y: auto;
background: var(--p-content-background);
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
box-shadow: 0 8px 32px rgb(0 0 0 / 0.15);
border: 1px solid var(--p-content-border-color);
backdrop-filter: blur(8px);
position: relative;
@@ -611,7 +605,7 @@ onMounted(async () => {
font-size: 0.8rem;
font-weight: 600;
color: var(--p-text-muted-color);
margin: 0 0 0.5rem 0;
margin: 0 0 0.5rem;
padding: 0 1rem;
text-transform: uppercase;
letter-spacing: 0.5px;
@@ -669,7 +663,7 @@ onMounted(async () => {
background: var(--p-content-background);
border-radius: 12px;
border: 1px solid var(--p-content-border-color);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
box-shadow: 0 8px 32px rgb(0 0 0 / 0.15);
overflow: hidden;
transition: opacity 0.15s ease-out;
}

View File

@@ -24,7 +24,7 @@
v-if="showSearchBox || showSelectedCount || showClearButton"
#header
>
<div class="pt-2 pb-0 px-2 flex flex-col">
<div class="flex flex-col px-2 pt-2 pb-0">
<SearchBox
v-if="showSearchBox"
v-model="searchQuery"
@@ -39,7 +39,7 @@
>
<span
v-if="showSelectedCount"
class="text-sm text-neutral-400 dark-theme:text-zinc-500 px-1"
class="px-1 text-sm text-neutral-400 dark-theme:text-zinc-500"
>
{{
selectedCount > 0
@@ -67,7 +67,7 @@
</span>
<span
v-if="selectedCount > 0"
class="pointer-events-none absolute -right-2 -top-2 z-10 flex h-5 w-5 items-center justify-center rounded-full bg-blue-400 dark-theme:bg-blue-500 text-xs font-semibold text-white"
class="pointer-events-none absolute -top-2 -right-2 z-10 flex h-5 w-5 items-center justify-center rounded-full bg-blue-400 text-xs font-semibold text-white dark-theme:bg-blue-500"
>
{{ selectedCount }}
</span>
@@ -75,27 +75,27 @@
<!-- Chevron size identical to current -->
<template #dropdownicon>
<i-lucide:chevron-down class="text-lg text-neutral-400" />
<i class="icon-[lucide--chevron-down] text-lg text-neutral-400" />
</template>
<!-- Custom option row: square checkbox + label (unchanged layout/colors) -->
<template #option="slotProps">
<div class="flex items-center gap-2" :style="popoverStyle">
<div
class="flex h-4 w-4 p-0.5 shrink-0 items-center justify-center rounded transition-all duration-200"
class="flex h-4 w-4 shrink-0 items-center justify-center rounded p-0.5 transition-all duration-200"
:class="
slotProps.selected
? 'bg-blue-400 dark-theme:border-blue-500 dark-theme:bg-blue-500'
: 'bg-neutral-100 dark-theme:bg-zinc-700'
"
>
<i-lucide:check
<i
v-if="slotProps.selected"
class="text-xs text-bold text-white"
class="text-bold icon-[lucide--check] text-xs text-white"
/>
</div>
<Button
class="border-none outline-none bg-transparent text-left"
class="border-none bg-transparent text-left outline-none"
unstyled
>{{ slotProps.option.name }}</Button
>
@@ -105,7 +105,8 @@
</template>
<script setup lang="ts">
import { type UseFuseOptions, useFuse } from '@vueuse/integrations/useFuse'
import { useFuse } from '@vueuse/integrations/useFuse'
import type { UseFuseOptions } from '@vueuse/integrations/useFuse'
import Button from 'primevue/button'
import type { MultiSelectPassThroughMethodOptions } from 'primevue/multiselect'
import MultiSelect from 'primevue/multiselect'

View File

@@ -0,0 +1,192 @@
import { mount } from '@vue/test-utils'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { nextTick } from 'vue'
import { createI18n } from 'vue-i18n'
import SearchBox from './SearchBox.vue'
const i18n = createI18n({
legacy: false,
locale: 'en',
messages: {
en: {
templateWidgets: {
sort: {
searchPlaceholder: 'Search...'
}
}
}
}
})
describe('SearchBox', () => {
beforeEach(() => {
vi.clearAllMocks()
vi.useFakeTimers()
})
afterEach(() => {
vi.restoreAllMocks()
})
const createWrapper = (props = {}) => {
return mount(SearchBox, {
props: {
modelValue: '',
...props
},
global: {
plugins: [i18n]
}
})
}
describe('debounced search functionality', () => {
it('should debounce search input by 300ms', async () => {
const wrapper = createWrapper()
const input = wrapper.find('input')
// Type search query
await input.setValue('test')
// Model should not update immediately
expect(wrapper.emitted('update:modelValue')).toBeFalsy()
// Advance timers by 299ms (just before debounce delay)
vi.advanceTimersByTime(299)
await nextTick()
expect(wrapper.emitted('update:modelValue')).toBeFalsy()
// Advance timers by 1ms more (reaching 300ms)
vi.advanceTimersByTime(1)
await nextTick()
// Model should now be updated
expect(wrapper.emitted('update:modelValue')).toBeTruthy()
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual(['test'])
})
it('should reset debounce timer on each keystroke', async () => {
const wrapper = createWrapper()
const input = wrapper.find('input')
// Type first character
await input.setValue('t')
vi.advanceTimersByTime(200)
await nextTick()
// Type second character (should reset timer)
await input.setValue('te')
vi.advanceTimersByTime(200)
await nextTick()
// Type third character (should reset timer again)
await input.setValue('tes')
vi.advanceTimersByTime(200)
await nextTick()
// Should not have emitted yet (only 200ms passed since last keystroke)
expect(wrapper.emitted('update:modelValue')).toBeFalsy()
// Advance final 100ms to reach 300ms
vi.advanceTimersByTime(100)
await nextTick()
// Should now emit with final value
expect(wrapper.emitted('update:modelValue')).toBeTruthy()
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual(['tes'])
})
it('should only emit final value after rapid typing', async () => {
const wrapper = createWrapper()
const input = wrapper.find('input')
// Simulate rapid typing
const searchTerms = ['s', 'se', 'sea', 'sear', 'searc', 'search']
for (const term of searchTerms) {
await input.setValue(term)
vi.advanceTimersByTime(50) // Less than debounce delay
}
// Should not have emitted yet
expect(wrapper.emitted('update:modelValue')).toBeFalsy()
// Complete the debounce delay
vi.advanceTimersByTime(300)
await nextTick()
// Should emit only once with final value
expect(wrapper.emitted('update:modelValue')).toHaveLength(1)
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual(['search'])
})
describe('bidirectional model sync', () => {
it('should sync external model changes to internal state', async () => {
const wrapper = createWrapper({ modelValue: 'initial' })
const input = wrapper.find('input')
expect(input.element.value).toBe('initial')
// Update model externally
await wrapper.setProps({ modelValue: 'external update' })
await nextTick()
// Internal state should sync
expect(input.element.value).toBe('external update')
})
})
describe('placeholder', () => {
it('should use custom placeholder when provided', () => {
const wrapper = createWrapper({ placeholder: 'Custom search...' })
const input = wrapper.find('input')
expect(input.attributes('placeholder')).toBe('Custom search...')
expect(input.attributes('aria-label')).toBe('Custom search...')
})
it('should use default placeholder when not provided', () => {
const wrapper = createWrapper()
const input = wrapper.find('input')
expect(input.attributes('placeholder')).toBe('Search...')
expect(input.attributes('aria-label')).toBe('Search...')
})
})
describe('autofocus', () => {
it('should focus input when autofocus is true', async () => {
const wrapper = createWrapper({ autofocus: true })
await nextTick()
const input = wrapper.find('input')
const inputElement = input.element as HTMLInputElement
// Note: In JSDOM, focus() doesn't actually set document.activeElement
// We can only verify that the focus method exists and doesn't throw
expect(inputElement.focus).toBeDefined()
})
it('should not autofocus when autofocus is false', () => {
const wrapper = createWrapper({ autofocus: false })
const input = wrapper.find('input')
expect(document.activeElement).not.toBe(input.element)
})
})
describe('click to focus', () => {
it('should focus input when wrapper is clicked', async () => {
const wrapper = createWrapper()
const wrapperDiv = wrapper.find('[class*="flex"]')
await wrapperDiv.trigger('click')
await nextTick()
// Input should receive focus
const input = wrapper.find('input').element as HTMLInputElement
expect(input.focus).toBeDefined()
})
})
})
})

View File

@@ -1,9 +1,9 @@
<template>
<div :class="wrapperStyle" @click="focusInput">
<i-lucide:search :class="iconColorStyle" />
<i class="icon-[lucide--search]" :class="iconColorStyle" />
<InputText
ref="input"
v-model="searchQuery"
v-model="internalSearchQuery"
:aria-label="
placeholder || t('templateWidgets.sort.searchPlaceholder', 'Search...')
"
@@ -18,12 +18,15 @@
</template>
<script setup lang="ts">
import { useDebounceFn } from '@vueuse/core'
import InputText from 'primevue/inputtext'
import { computed, onMounted, ref } from 'vue'
import { computed, onMounted, ref, watch } from 'vue'
import { t } from '@/i18n'
import { cn } from '@/utils/tailwindUtil'
const SEARCH_DEBOUNCE_DELAY_MS = 300
const {
autofocus = false,
placeholder,
@@ -35,9 +38,30 @@ const {
showBorder?: boolean
size?: 'md' | 'lg'
}>()
// defineModel without arguments uses 'modelValue' as the prop name
const searchQuery = defineModel<string>()
// Internal search query state for immediate UI updates
const internalSearchQuery = ref<string>(searchQuery.value ?? '')
// Create debounced function to update the parent model
const updateSearchQuery = useDebounceFn((value: string) => {
searchQuery.value = value
}, SEARCH_DEBOUNCE_DELAY_MS)
// Watch internal query changes and trigger debounced update
watch(internalSearchQuery, (newValue) => {
void updateSearchQuery(newValue)
})
// Sync external changes back to internal state
watch(searchQuery, (newValue) => {
if (newValue !== internalSearchQuery.value) {
internalSearchQuery.value = newValue || ''
}
})
const input = ref<{ $el: HTMLElement } | null>()
const focusInput = () => {
if (input.value && input.value.$el) {

View File

@@ -38,19 +38,19 @@
<!-- Trigger caret -->
<template #dropdownicon>
<i-lucide:chevron-down class="text-base text-neutral-500" />
<i class="icon-[lucide--chevron-down] text-base text-neutral-500" />
</template>
<!-- Option row -->
<template #option="{ option, selected }">
<div
class="flex items-center justify-between gap-3 w-full"
class="flex w-full items-center justify-between gap-3"
:style="optionStyle"
>
<span class="truncate">{{ option.name }}</span>
<i-lucide:check
<i
v-if="selected"
class="text-neutral-600 dark-theme:text-white"
class="icon-[lucide--check] text-neutral-600 dark-theme:text-white"
/>
</div>
</template>

View File

@@ -1,6 +1,6 @@
<template>
<div
class="relative w-full h-full"
class="relative h-full w-full"
@mouseenter="handleMouseEnter"
@mouseleave="handleMouseLeave"
>
@@ -59,14 +59,14 @@
/>
<div
v-if="enable3DViewer"
class="absolute top-12 right-2 z-20 pointer-events-auto"
class="pointer-events-auto absolute top-12 right-2 z-20"
>
<ViewerControls :node="node" />
</div>
<div
v-if="showRecordingControls"
class="absolute right-2 z-20 pointer-events-auto"
class="pointer-events-auto absolute right-2 z-20"
:class="{
'top-12': !enable3DViewer,
'top-24': enable3DViewer

View File

@@ -1,6 +1,6 @@
<template>
<div
class="relative w-full h-full"
class="relative h-full w-full"
@mouseenter="handleMouseEnter"
@mouseleave="handleMouseLeave"
>
@@ -34,7 +34,7 @@
@up-direction-change="listenUpDirectionChange"
@recording-status-change="listenRecordingStatusChange"
/>
<div class="absolute top-0 left-0 w-full h-full pointer-events-none">
<div class="pointer-events-none absolute top-0 left-0 h-full w-full">
<Load3DControls
:input-spec="inputSpec"
:background-color="backgroundColor"
@@ -69,7 +69,7 @@
</div>
<div
v-if="showRecordingControls"
class="absolute top-12 right-2 z-20 pointer-events-auto"
class="pointer-events-auto absolute top-12 right-2 z-20"
>
<RecordingControls
:node="node"

View File

@@ -1,11 +1,11 @@
<template>
<div
v-if="animations && animations.length > 0"
class="absolute top-0 left-0 w-full flex justify-center pt-2 gap-2 items-center pointer-events-auto z-10"
class="pointer-events-auto absolute top-0 left-0 z-10 flex w-full items-center justify-center gap-2 pt-2"
>
<Button class="p-button-rounded p-button-text" @click="togglePlay">
<i
:class="['pi', playing ? 'pi-pause' : 'pi-play', 'text-white text-lg']"
:class="['pi', playing ? 'pi-pause' : 'pi-play', 'text-lg text-white']"
/>
</Button>

View File

@@ -1,21 +1,21 @@
<template>
<div
class="absolute top-12 left-2 flex flex-col pointer-events-auto z-20 bg-gray-700/30 rounded-lg"
class="pointer-events-auto absolute top-12 left-2 z-20 flex flex-col rounded-lg bg-gray-700/30"
>
<div class="relative show-menu">
<div class="show-menu relative">
<Button class="p-button-rounded p-button-text" @click="toggleMenu">
<i class="pi pi-bars text-white text-lg" />
<i class="pi pi-bars text-lg text-white" />
</Button>
<div
v-show="isMenuOpen"
class="absolute left-12 top-0 bg-black/50 rounded-lg shadow-lg"
class="absolute top-0 left-12 rounded-lg bg-black/50 shadow-lg"
>
<div class="flex flex-col">
<Button
v-for="category in availableCategories"
:key="category"
class="p-button-text w-full flex items-center justify-start"
class="p-button-text flex w-full items-center justify-start"
:class="{ 'bg-gray-600': activeCategory === category }"
@click="selectCategory(category)"
>
@@ -26,7 +26,7 @@
</div>
</div>
<div v-show="activeCategory" class="bg-gray-700/30 rounded-lg">
<div v-show="activeCategory" class="rounded-lg bg-gray-700/30">
<SceneControls
v-if="activeCategory === 'scene'"
ref="sceneControlsRef"
@@ -81,7 +81,7 @@
:class="[
'pi',
showPreview ? 'pi-eye' : 'pi-eye-slash',
'text-white text-lg'
'text-lg text-white'
]"
/>
</Button>

View File

@@ -1,5 +1,5 @@
<template>
<div ref="container" class="w-full h-full relative comfy-load-3d">
<div ref="container" class="comfy-load-3d relative h-full w-full">
<LoadingOverlay ref="loadingOverlayRef" />
</div>
</template>

View File

@@ -6,18 +6,18 @@
@mouseenter="viewer.handleMouseEnter"
@mouseleave="viewer.handleMouseLeave"
>
<div ref="mainContentRef" class="flex-1 relative">
<div ref="mainContentRef" class="relative flex-1">
<div
ref="containerRef"
class="absolute w-full h-full comfy-load-3d-viewer"
class="comfy-load-3d-viewer absolute h-full w-full"
@resize="viewer.handleResize"
/>
</div>
<div class="w-72 flex flex-col">
<div class="flex w-72 flex-col">
<div class="flex-1 overflow-y-auto p-4">
<div class="space-y-2">
<div class="p-2 space-y-4">
<div class="space-y-4 p-2">
<SceneControls
v-model:background-color="viewer.backgroundColor.value"
v-model:show-grid="viewer.showGrid.value"
@@ -26,27 +26,27 @@
/>
</div>
<div class="p-2 space-y-4">
<div class="space-y-4 p-2">
<ModelControls
v-model:up-direction="viewer.upDirection.value"
v-model:material-mode="viewer.materialMode.value"
/>
</div>
<div class="p-2 space-y-4">
<div class="space-y-4 p-2">
<CameraControls
v-model:camera-type="viewer.cameraType.value"
v-model:fov="viewer.fov.value"
/>
</div>
<div class="p-2 space-y-4">
<div class="space-y-4 p-2">
<LightControls
v-model:light-intensity="viewer.lightIntensity.value"
/>
</div>
<div class="p-2 space-y-4">
<div class="space-y-4 p-2">
<ExportControls @export-model="viewer.exportModel" />
</div>
</div>

View File

@@ -2,11 +2,11 @@
<Transition name="fade">
<div
v-if="modelLoading"
class="absolute inset-0 bg-black/50 flex items-center justify-center z-50"
class="absolute inset-0 z-50 flex items-center justify-center bg-black/50"
>
<div class="flex flex-col items-center">
<div class="spinner" />
<div class="text-white mt-4 text-lg">
<div class="mt-4 text-lg text-white">
{{ loadingMessage }}
</div>
</div>

View File

@@ -6,19 +6,19 @@
value: t('load3d.switchCamera'),
showDelay: 300
}"
:class="['pi', getCameraIcon, 'text-white text-lg']"
:class="['pi', getCameraIcon, 'text-lg text-white']"
/>
</Button>
<div v-if="showFOVButton" class="relative show-fov">
<div v-if="showFOVButton" class="show-fov relative">
<Button class="p-button-rounded p-button-text" @click="toggleFOV">
<i
v-tooltip.right="{ value: t('load3d.fov'), showDelay: 300 }"
class="pi pi-expand text-white text-lg"
class="pi pi-expand text-lg text-white"
/>
</Button>
<div
v-show="showFOV"
class="absolute left-12 top-0 bg-black/50 p-4 rounded-lg shadow-lg"
class="absolute top-0 left-12 rounded-lg bg-black/50 p-4 shadow-lg"
style="width: 150px"
>
<Slider

View File

@@ -1,6 +1,6 @@
<template>
<div class="flex flex-col">
<div class="relative show-export-formats">
<div class="show-export-formats relative">
<Button
class="p-button-rounded p-button-text"
@click="toggleExportFormats"
@@ -10,12 +10,12 @@
value: t('load3d.exportModel'),
showDelay: 300
}"
class="pi pi-download text-white text-lg"
class="pi pi-download text-lg text-white"
/>
</Button>
<div
v-show="showExportFormats"
class="absolute left-12 top-0 bg-black/50 rounded-lg shadow-lg"
class="absolute top-0 left-12 rounded-lg bg-black/50 shadow-lg"
>
<div class="flex flex-col">
<Button

View File

@@ -1,6 +1,6 @@
<template>
<div class="flex flex-col">
<div v-if="showLightIntensityButton" class="relative show-light-intensity">
<div v-if="showLightIntensityButton" class="show-light-intensity relative">
<Button
class="p-button-rounded p-button-text"
@click="toggleLightIntensity"
@@ -10,12 +10,12 @@
value: t('load3d.lightIntensity'),
showDelay: 300
}"
class="pi pi-sun text-white text-lg"
class="pi pi-sun text-lg text-white"
/>
</Button>
<div
v-show="showLightIntensity"
class="absolute left-12 top-0 bg-black/50 p-4 rounded-lg shadow-lg"
class="absolute top-0 left-12 rounded-lg bg-black/50 p-4 shadow-lg"
style="width: 150px"
>
<Slider

View File

@@ -1,18 +1,18 @@
<template>
<div class="flex flex-col">
<div class="relative show-up-direction">
<div class="show-up-direction relative">
<Button class="p-button-rounded p-button-text" @click="toggleUpDirection">
<i
v-tooltip.right="{
value: t('load3d.upDirection'),
showDelay: 300
}"
class="pi pi-arrow-up text-white text-lg"
class="pi pi-arrow-up text-lg text-white"
/>
</Button>
<div
v-show="showUpDirection"
class="absolute left-12 top-0 bg-black/50 rounded-lg shadow-lg"
class="absolute top-0 left-12 rounded-lg bg-black/50 shadow-lg"
>
<div class="flex flex-col">
<Button
@@ -28,7 +28,7 @@
</div>
</div>
<div class="relative show-material-mode">
<div class="show-material-mode relative">
<Button
class="p-button-rounded p-button-text"
@click="toggleMaterialMode"
@@ -38,12 +38,12 @@
value: t('load3d.materialMode'),
showDelay: 300
}"
class="pi pi-box text-white text-lg"
class="pi pi-box text-lg text-white"
/>
</Button>
<div
v-show="showMaterialMode"
class="absolute left-12 top-0 bg-black/50 rounded-lg shadow-lg"
class="absolute top-0 left-12 rounded-lg bg-black/50 shadow-lg"
>
<div class="flex flex-col">
<Button
@@ -59,7 +59,7 @@
</div>
</div>
<div v-if="materialMode === 'lineart'" class="relative show-edge-threshold">
<div v-if="materialMode === 'lineart'" class="show-edge-threshold relative">
<Button
class="p-button-rounded p-button-text"
@click="toggleEdgeThreshold"
@@ -69,15 +69,15 @@
value: t('load3d.edgeThreshold'),
showDelay: 300
}"
class="pi pi-sliders-h text-white text-lg"
class="pi pi-sliders-h text-lg text-white"
/>
</Button>
<div
v-show="showEdgeThreshold"
class="absolute left-12 top-0 bg-black/50 p-4 rounded-lg shadow-lg"
class="absolute top-0 left-12 rounded-lg bg-black/50 p-4 shadow-lg"
style="width: 150px"
>
<label class="text-white text-xs mb-1 block"
<label class="mb-1 block text-xs text-white"
>{{ t('load3d.edgeThreshold') }}: {{ edgeThreshold }}°</label
>
<Slider

View File

@@ -1,5 +1,5 @@
<template>
<div class="relative bg-gray-700/30 rounded-lg">
<div class="relative rounded-lg bg-gray-700/30">
<div class="flex flex-col gap-2">
<Button
class="p-button-rounded p-button-text"
@@ -10,7 +10,7 @@
value: t('load3d.resizeNodeMatchOutput'),
showDelay: 300
}"
class="pi pi-window-maximize text-white text-lg"
class="pi pi-window-maximize text-lg text-white"
/>
</Button>
<Button
@@ -31,7 +31,7 @@
:class="[
'pi',
isRecording ? 'pi-circle-fill' : 'pi-video',
'text-white text-lg'
'text-lg text-white'
]"
/>
</Button>
@@ -46,7 +46,7 @@
value: t('load3d.exportRecording'),
showDelay: 300
}"
class="pi pi-download text-white text-lg"
class="pi pi-download text-lg text-white"
/>
</Button>
@@ -60,13 +60,13 @@
value: t('load3d.clearRecording'),
showDelay: 300
}"
class="pi pi-trash text-white text-lg"
class="pi pi-trash text-lg text-white"
/>
</Button>
<div
v-if="recordingDuration > 0 && !isRecording"
class="text-xs text-white text-center mt-1"
class="mt-1 text-center text-xs text-white"
>
{{ formatDuration(recordingDuration) }}
</div>

View File

@@ -7,7 +7,7 @@
>
<i
v-tooltip.right="{ value: t('load3d.showGrid'), showDelay: 300 }"
class="pi pi-table text-white text-lg"
class="pi pi-table text-lg text-white"
/>
</Button>
@@ -18,13 +18,13 @@
value: t('load3d.backgroundColor'),
showDelay: 300
}"
class="pi pi-palette text-white text-lg"
class="pi pi-palette text-lg text-white"
/>
<input
ref="colorPickerRef"
type="color"
:value="backgroundColor"
class="absolute opacity-0 w-0 h-0 p-0 m-0 pointer-events-none"
class="pointer-events-none absolute m-0 h-0 w-0 p-0 opacity-0"
@input="
updateBackgroundColor(($event.target as HTMLInputElement).value)
"
@@ -39,13 +39,13 @@
value: t('load3d.uploadBackgroundImage'),
showDelay: 300
}"
class="pi pi-image text-white text-lg"
class="pi pi-image text-lg text-white"
/>
<input
ref="imagePickerRef"
type="file"
accept="image/*"
class="absolute opacity-0 w-0 h-0 p-0 m-0 pointer-events-none"
class="pointer-events-none absolute m-0 h-0 w-0 p-0 opacity-0"
@change="uploadBackgroundImage"
/>
</Button>
@@ -61,7 +61,7 @@
value: t('load3d.removeBackgroundImage'),
showDelay: 300
}"
class="pi pi-times text-white text-lg"
class="pi pi-times text-lg text-white"
/>
</Button>
</div>

View File

@@ -1,5 +1,5 @@
<template>
<div class="relative bg-gray-700/30 rounded-lg">
<div class="relative rounded-lg bg-gray-700/30">
<div class="flex flex-col gap-2">
<Button class="p-button-rounded p-button-text" @click="openIn3DViewer">
<i
@@ -7,7 +7,7 @@
value: t('load3d.openIn3DViewer'),
showDelay: 300
}"
class="pi pi-expand text-white text-lg"
class="pi pi-expand text-lg text-white"
/>
</Button>
</div>

View File

@@ -6,7 +6,7 @@ https://github.com/Nuked88/ComfyUI-N-Sidebar/blob/7ae7da4a9761009fb6629bc04c6830
<div v-else class="_sb_node_preview">
<div class="_sb_table">
<div
class="node_header text-ellipsis mr-4"
class="node_header mr-4 text-ellipsis"
:title="nodeDef.display_name"
:style="{
backgroundColor: litegraphColors.NODE_DEFAULT_COLOR,
@@ -202,7 +202,6 @@ const truncateDefaultValue = (value: any, charLimit: number = 32): string => {
._sb_node_preview {
background-color: var(--comfy-menu-bg);
font-family: 'Open Sans', sans-serif;
font-size: small;
color: var(--descrip-text);
border: 1px solid var(--descrip-text);
min-width: 300px;
@@ -265,7 +264,7 @@ const truncateDefaultValue = (value: any, charLimit: number = 32): string => {
._long_field {
background: var(--bg-color);
border: 2px solid var(--border-color);
margin: 5px 5px 0 5px;
margin: 5px 5px 0;
border-radius: 10px;
line-height: 1.7;
text-wrap: nowrap;
@@ -278,7 +277,7 @@ const truncateDefaultValue = (value: any, charLimit: number = 32): string => {
._sb_preview_badge {
text-align: center;
background: var(--comfy-input-bg);
font-weight: bold;
font-weight: 700;
color: var(--error-text);
}
</style>

View File

@@ -1,10 +1,10 @@
<template>
<div
class="comfy-vue-node-search-container flex justify-center items-center w-full min-w-96"
class="comfy-vue-node-search-container flex w-full min-w-96 items-center justify-center"
>
<div
v-if="enableNodePreview"
class="comfy-vue-node-preview-container absolute left-[-350px] top-[50px]"
class="comfy-vue-node-preview-container absolute top-[50px] left-[-350px]"
>
<NodePreview
v-if="hoveredSuggestion"

View File

@@ -1,11 +1,11 @@
<template>
<div
class="option-container flex justify-between items-center px-2 py-0 cursor-pointer overflow-hidden w-full"
class="option-container flex w-full cursor-pointer items-center justify-between overflow-hidden px-2 py-0"
>
<div class="option-display-name font-semibold flex flex-col">
<div class="option-display-name flex flex-col font-semibold">
<div>
<span v-if="isBookmarked">
<i class="pi pi-bookmark-fill text-sm mr-1" />
<i class="pi pi-bookmark-fill mr-1 text-sm" />
</span>
<span v-html="highlightQuery(nodeDef.display_name, currentQuery)" />
<span>&nbsp;</span>
@@ -15,7 +15,7 @@
</div>
<div
v-if="showCategory"
class="option-category font-light text-sm text-muted overflow-hidden text-ellipsis whitespace-nowrap"
class="option-category truncate text-sm font-light text-muted"
>
{{ nodeDef.category.replaceAll('/', ' > ') }}
</div>
@@ -56,8 +56,7 @@ import { useNodeBookmarkStore } from '@/stores/nodeBookmarkStore'
import type { ComfyNodeDefImpl } from '@/stores/nodeDefStore'
import { useNodeFrequencyStore } from '@/stores/nodeDefStore'
import { NodeSourceType } from '@/types/nodeSource'
import { highlightQuery } from '@/utils/formatUtil'
import { formatNumberWithSuffix } from '@/utils/formatUtil'
import { formatNumberWithSuffix, highlightQuery } from '@/utils/formatUtil'
const settingStore = useSettingStore()
const showCategory = computed(() =>
@@ -89,7 +88,7 @@ const props = defineProps<{
:deep(.highlight) {
background-color: var(--p-primary-color);
color: var(--p-primary-contrast-color);
font-weight: bold;
font-weight: 700;
border-radius: 0.25rem;
padding: 0 0.125rem;
margin: -0.125rem 0.125rem;

View File

@@ -25,7 +25,7 @@
</teleport>
<div
v-if="selectedTab"
class="sidebar-content-container h-full overflow-y-auto overflow-x-hidden"
class="sidebar-content-container h-full overflow-x-hidden overflow-y-auto"
>
<ExtensionSlot :extension="selectedTab" />
</div>

View File

@@ -89,7 +89,7 @@ const computedTooltip = computed(() => t(tooltip) + tooltipSuffix)
.side-bar-button-selected .side-bar-button-icon {
font-size: var(--sidebar-icon-size) !important;
font-weight: bold;
font-weight: 700;
}
</style>

View File

@@ -5,7 +5,7 @@
@click="toggleShortcutsPanel"
>
<template #icon>
<i-lucide:keyboard />
<i class="icon-[lucide--keyboard]" />
</template>
</SidebarIcon>
</template>

View File

@@ -59,8 +59,7 @@ import { useLitegraphService } from '@/services/litegraphService'
import type { ComfyModelDef, ModelFolder } from '@/stores/modelStore'
import { ResourceState, useModelStore } from '@/stores/modelStore'
import { useModelToNodeStore } from '@/stores/modelToNodeStore'
import type { TreeNode } from '@/types/treeExplorerTypes'
import type { TreeExplorerNode } from '@/types/treeExplorerTypes'
import type { TreeExplorerNode, TreeNode } from '@/types/treeExplorerTypes'
import { isElectron } from '@/utils/envUtil'
import { buildTree } from '@/utils/treeUtil'

View File

@@ -156,8 +156,7 @@ import type {
GroupingStrategyId,
SortingStrategyId
} from '@/types/nodeOrganizationTypes'
import type { TreeNode } from '@/types/treeExplorerTypes'
import type { TreeExplorerNode } from '@/types/treeExplorerTypes'
import type { TreeExplorerNode, TreeNode } from '@/types/treeExplorerTypes'
import type { FuseFilterWithValue } from '@/utils/fuseUtil'
import NodeBookmarkTreeExplorer from './nodeLibrary/NodeBookmarkTreeExplorer.vue'

Some files were not shown because too many files have changed in this diff Show More