mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-08 06:30:04 +00:00
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:
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 &&
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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="{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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' }
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
}>()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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<{
|
||||
|
||||
@@ -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)"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
/>
|
||||
|
||||
@@ -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'> & {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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' }
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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'
|
||||
]"
|
||||
>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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)
|
||||
}}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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') }}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
@click="() => commandStore.execute('Comfy.PublishSubgraph')"
|
||||
>
|
||||
<template #icon>
|
||||
<i-lucide:book-open />
|
||||
<i class="icon-[lucide--book-open]" />
|
||||
</template>
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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))" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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="
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
|
||||
192
src/components/input/SearchBox.test.ts
Normal file
192
src/components/input/SearchBox.test.ts
Normal 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()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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> </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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
@click="toggleShortcutsPanel"
|
||||
>
|
||||
<template #icon>
|
||||
<i-lucide:keyboard />
|
||||
<i class="icon-[lucide--keyboard]" />
|
||||
</template>
|
||||
</SidebarIcon>
|
||||
</template>
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user