mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-01-26 19:09:52 +00:00
style: apply Tailwind CSS class sorting across codebase
Co-authored-by: Amp <amp@ampcode.com> Amp-Thread-ID: https://ampcode.com/threads/T-019bd8c8-bce1-70bc-a125-baf2a1503ee8
This commit is contained in:
@@ -226,8 +226,8 @@ In this project, only the `<i class="icon-[lucide--folder]" />` syntax from unpl
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<i class="icon-[lucide--trophy] text-neutral size-4" />
|
||||
<i class="icon-[lucide--settings] text-neutral size-4" />
|
||||
<i class="text-neutral icon-[lucide--trophy] size-4" />
|
||||
<i class="text-neutral icon-[lucide--settings] size-4" />
|
||||
</template>
|
||||
```
|
||||
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import path from 'node:path'
|
||||
|
||||
export default {
|
||||
'tests-ui/**': () =>
|
||||
'echo "Files in tests-ui/ are deprecated. Colocate tests with source files." && exit 1',
|
||||
|
||||
'./**/*.js': (stagedFiles) => formatAndEslint(stagedFiles),
|
||||
|
||||
'./**/*.{ts,tsx,vue,mts}': (stagedFiles) => [
|
||||
...formatAndEslint(stagedFiles),
|
||||
'pnpm typecheck'
|
||||
]
|
||||
}
|
||||
|
||||
function formatAndEslint(fileNames) {
|
||||
// Convert absolute paths to relative paths for better ESLint resolution
|
||||
const relativePaths = fileNames.map((f) => path.relative(process.cwd(), f))
|
||||
const joinedPaths = relativePaths.map((p) => `"${p}"`).join(' ')
|
||||
return [
|
||||
`pnpm exec oxfmt ${joinedPaths}`,
|
||||
`pnpm exec oxlint --fix ${joinedPaths}`,
|
||||
`pnpm exec eslint --cache --fix --no-warn-ignored ${joinedPaths}`
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
import path from 'node:path'
|
||||
|
||||
export default {
|
||||
'tests-ui/**': () =>
|
||||
'echo "Files in tests-ui/ are deprecated. Colocate tests with source files." && exit 1',
|
||||
|
||||
'./**/*.js': (stagedFiles: string[]) => formatAndEslint(stagedFiles),
|
||||
|
||||
'./**/*.{ts,tsx,vue,mts}': (stagedFiles: string[]) => [
|
||||
@@ -14,7 +17,7 @@ function formatAndEslint(fileNames: string[]) {
|
||||
const relativePaths = fileNames.map((f) => path.relative(process.cwd(), f))
|
||||
const joinedPaths = relativePaths.map((p) => `"${p}"`).join(' ')
|
||||
return [
|
||||
`pnpm exec prettier --cache --write ${joinedPaths}`,
|
||||
`pnpm exec oxfmt ${joinedPaths}`,
|
||||
`pnpm exec oxlint --fix ${joinedPaths}`,
|
||||
`pnpm exec eslint --cache --fix --no-warn-ignored ${joinedPaths}`
|
||||
]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
class="w-full h-full absolute top-0 left-0 z-999 pointer-events-none flex flex-col"
|
||||
class="pointer-events-none absolute top-0 left-0 z-999 flex h-full w-full flex-col"
|
||||
>
|
||||
<slot name="workflow-tabs" />
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<Splitter
|
||||
:key="splitterRefreshKey"
|
||||
class="bg-transparent pointer-events-none border-none flex-1 overflow-hidden"
|
||||
class="pointer-events-none flex-1 overflow-hidden border-none bg-transparent"
|
||||
:state-key="sidebarStateKey"
|
||||
state-storage="local"
|
||||
@resizestart="onResizestart"
|
||||
@@ -30,10 +30,10 @@
|
||||
:class="
|
||||
sidebarLocation === 'left'
|
||||
? cn(
|
||||
'side-bar-panel bg-comfy-menu-bg pointer-events-auto',
|
||||
sidebarPanelVisible && 'min-w-78'
|
||||
)
|
||||
: 'bg-comfy-menu-bg pointer-events-auto'
|
||||
'side-bar-panel pointer-events-auto bg-comfy-menu-bg',
|
||||
sidebarPanelVisible && 'min-w-78'
|
||||
)
|
||||
: 'pointer-events-auto bg-comfy-menu-bg'
|
||||
"
|
||||
:min-size="sidebarLocation === 'left' ? 10 : 15"
|
||||
:size="20"
|
||||
@@ -54,11 +54,17 @@
|
||||
</SplitterPanel>
|
||||
|
||||
<!-- Main panel (always present) -->
|
||||
<SplitterPanel :size="80" class="flex flex-col">
|
||||
<slot name="topmenu" :sidebar-panel-visible />
|
||||
<SplitterPanel
|
||||
:size="80"
|
||||
class="flex flex-col"
|
||||
>
|
||||
<slot
|
||||
name="topmenu"
|
||||
:sidebar-panel-visible
|
||||
/>
|
||||
|
||||
<Splitter
|
||||
class="bg-transparent pointer-events-none border-none splitter-overlay-bottom mr-1 mb-1 ml-1 flex-1"
|
||||
class="splitter-overlay-bottom pointer-events-none mr-1 mb-1 ml-1 flex-1 border-none bg-transparent"
|
||||
layout="vertical"
|
||||
:pt:gutter="
|
||||
cn(
|
||||
@@ -75,7 +81,7 @@
|
||||
</SplitterPanel>
|
||||
<SplitterPanel
|
||||
v-show="bottomPanelVisible && !focusMode"
|
||||
class="bottom-panel border border-(--p-panel-border-color) max-w-full overflow-x-auto bg-comfy-menu-bg pointer-events-auto rounded-lg"
|
||||
class="bottom-panel pointer-events-auto max-w-full overflow-x-auto rounded-lg border border-(--p-panel-border-color) bg-comfy-menu-bg"
|
||||
>
|
||||
<slot name="bottom-panel" />
|
||||
</SplitterPanel>
|
||||
@@ -90,10 +96,10 @@
|
||||
:class="
|
||||
sidebarLocation === 'right'
|
||||
? cn(
|
||||
'side-bar-panel bg-comfy-menu-bg pointer-events-auto',
|
||||
sidebarPanelVisible && 'min-w-78'
|
||||
)
|
||||
: 'bg-comfy-menu-bg pointer-events-auto'
|
||||
'side-bar-panel pointer-events-auto bg-comfy-menu-bg',
|
||||
sidebarPanelVisible && 'min-w-78'
|
||||
)
|
||||
: 'pointer-events-auto bg-comfy-menu-bg'
|
||||
"
|
||||
:min-size="sidebarLocation === 'right' ? 10 : 15"
|
||||
:size="20"
|
||||
@@ -103,7 +109,10 @@
|
||||
sidebarLocation === 'right' ? t('sideToolbar.sidebar') : undefined
|
||||
"
|
||||
>
|
||||
<slot v-if="sidebarLocation === 'left'" name="right-side-panel" />
|
||||
<slot
|
||||
v-if="sidebarLocation === 'left'"
|
||||
name="right-side-panel"
|
||||
/>
|
||||
<slot
|
||||
v-else-if="sidebarLocation === 'right' && sidebarPanelVisible"
|
||||
name="side-bar-panel"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
v-show="workspaceState.focusMode"
|
||||
class="fixed z-9999 flex flex-row no-drag top-0 right-0"
|
||||
class="no-drag fixed top-0 right-0 z-9999 flex flex-row"
|
||||
>
|
||||
<Button
|
||||
v-tooltip="{ value: $t('menu.showMenu'), showDelay: 300 }"
|
||||
|
||||
@@ -32,14 +32,14 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="actionbar-container pointer-events-auto flex gap-2 h-12 items-center rounded-lg border border-interface-stroke bg-comfy-menu-bg px-2 shadow-interface"
|
||||
class="actionbar-container pointer-events-auto flex h-12 items-center gap-2 rounded-lg border border-interface-stroke bg-comfy-menu-bg px-2 shadow-interface"
|
||||
>
|
||||
<ActionBarButtons />
|
||||
<!-- Support for legacy topbar elements attached by custom scripts, hidden if no elements present -->
|
||||
<div
|
||||
ref="legacyCommandsContainerRef"
|
||||
class="[&:not(:has(*>*:not(:empty)))]:hidden"
|
||||
></div>
|
||||
/>
|
||||
<ComfyActionbar />
|
||||
<Button
|
||||
v-tooltip.bottom="queueHistoryTooltipConfig"
|
||||
@@ -54,7 +54,7 @@
|
||||
<i class="icon-[lucide--history] size-4" />
|
||||
<span
|
||||
v-if="queuedCount > 0"
|
||||
class="absolute -top-1 -right-1 min-w-[16px] rounded-full bg-primary-background py-0.25 text-[10px] font-medium leading-[14px] text-base-foreground"
|
||||
class="absolute -top-1 -right-1 min-w-[16px] rounded-full bg-primary-background py-0.25 text-[10px] leading-[14px] font-medium text-base-foreground"
|
||||
>
|
||||
{{ queuedCount }}
|
||||
</span>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<div class="flex h-full items-center" :class="cn(!isDocked && '-ml-2')">
|
||||
<div
|
||||
class="flex h-full items-center"
|
||||
:class="cn(!isDocked && '-ml-2')"
|
||||
>
|
||||
<div
|
||||
v-if="isDragging && !isDocked"
|
||||
:class="actionbarClass"
|
||||
@@ -18,12 +21,15 @@
|
||||
content: { class: isDocked ? 'p-0' : 'p-1' }
|
||||
}"
|
||||
>
|
||||
<div ref="panelRef" class="flex items-center select-none gap-2">
|
||||
<div
|
||||
ref="panelRef"
|
||||
class="flex items-center gap-2 select-none"
|
||||
>
|
||||
<span
|
||||
ref="dragHandleRef"
|
||||
:class="
|
||||
cn(
|
||||
'drag-handle cursor-grab w-3 h-max',
|
||||
'drag-handle h-max w-3 cursor-grab',
|
||||
isDragging && 'cursor-grabbing'
|
||||
)
|
||||
"
|
||||
@@ -282,18 +288,18 @@ const actionbarClass = computed(() =>
|
||||
cn(
|
||||
'w-[200px] border-dashed border-blue-500 opacity-80',
|
||||
'm-1.5 flex items-center justify-center self-stretch',
|
||||
'rounded-md before:w-50 before:-ml-50 before:h-full',
|
||||
'rounded-md before:-ml-50 before:h-full before:w-50',
|
||||
'pointer-events-auto',
|
||||
isMouseOverDropZone.value &&
|
||||
'border-[3px] opacity-100 scale-105 shadow-[0_0_20px] shadow-blue-500'
|
||||
'scale-105 border-[3px] opacity-100 shadow-[0_0_20px] shadow-blue-500'
|
||||
)
|
||||
)
|
||||
const panelClass = computed(() =>
|
||||
cn(
|
||||
'actionbar pointer-events-auto z-1300',
|
||||
isDragging.value && 'select-none pointer-events-none',
|
||||
isDragging.value && 'pointer-events-none select-none',
|
||||
isDocked.value
|
||||
? 'p-0 static border-none bg-transparent'
|
||||
? 'static border-none bg-transparent p-0'
|
||||
: 'fixed shadow-interface'
|
||||
)
|
||||
)
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
class="relative h-full w-full overflow-hidden bg-neutral-900"
|
||||
>
|
||||
<div class="p-terminal h-full w-full rounded-none p-2">
|
||||
<div ref="terminalEl" class="terminal-host h-full" />
|
||||
<div
|
||||
ref="terminalEl"
|
||||
class="terminal-host h-full"
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
v-tooltip.left="{
|
||||
@@ -15,7 +18,7 @@
|
||||
size="sm"
|
||||
:class="
|
||||
cn('absolute top-2 right-8 transition-opacity', {
|
||||
'opacity-0 pointer-events-none select-none': !isHovered
|
||||
'pointer-events-none opacity-0 select-none': !isHovered
|
||||
})
|
||||
"
|
||||
:aria-label="tooltipText"
|
||||
@@ -109,6 +112,6 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
:deep(.p-terminal) .xterm-screen {
|
||||
@apply bg-neutral-900 overflow-hidden;
|
||||
@apply overflow-hidden bg-neutral-900;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -46,9 +46,9 @@
|
||||
:is-active="item.key === activeItemKey"
|
||||
/>
|
||||
</template>
|
||||
<template #separator
|
||||
><span style="transform: scale(1.5)"> / </span></template
|
||||
>
|
||||
<template #separator>
|
||||
<span style="transform: scale(1.5)"> / </span>
|
||||
</template>
|
||||
</Breadcrumb>
|
||||
</div>
|
||||
</template>
|
||||
@@ -233,7 +233,7 @@ onUpdated(() => {
|
||||
}
|
||||
|
||||
:deep(.p-breadcrumb-item) {
|
||||
@apply flex items-center overflow-hidden h-8;
|
||||
@apply flex h-8 items-center overflow-hidden;
|
||||
min-width: calc(var(--p-breadcrumb-item-min-width) + 1rem);
|
||||
border: 1px solid transparent;
|
||||
background-color: transparent;
|
||||
|
||||
@@ -21,8 +21,15 @@
|
||||
class="icon-[lucide--triangle-alert] text-warning-background"
|
||||
/>
|
||||
<span class="p-breadcrumb-item-label px-2">{{ item.label }}</span>
|
||||
<Tag v-if="item.isBlueprint" value="Blueprint" severity="primary" />
|
||||
<i v-if="isActive" class="pi pi-angle-down text-[10px]"></i>
|
||||
<Tag
|
||||
v-if="item.isBlueprint"
|
||||
value="Blueprint"
|
||||
severity="primary"
|
||||
/>
|
||||
<i
|
||||
v-if="isActive"
|
||||
class="pi pi-angle-down text-[10px]"
|
||||
/>
|
||||
</a>
|
||||
<Menu
|
||||
v-if="isActive || isRoot"
|
||||
@@ -208,7 +215,7 @@ defineExpose({
|
||||
}
|
||||
|
||||
.p-breadcrumb-item-label {
|
||||
@apply whitespace-nowrap text-ellipsis overflow-hidden;
|
||||
@apply overflow-hidden text-ellipsis whitespace-nowrap;
|
||||
}
|
||||
|
||||
.active-breadcrumb-item {
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
<div
|
||||
:class="
|
||||
cn(
|
||||
'flex justify-center items-center shrink-0 outline-hidden border-none p-0 rounded-lg shadow-sm transition-all duration-200 cursor-pointer bg-secondary-background',
|
||||
'flex shrink-0 cursor-pointer items-center justify-center rounded-lg border-none bg-secondary-background p-0 shadow-sm outline-hidden transition-all duration-200',
|
||||
backgroundClass
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot></slot>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div :class="containerClasses">
|
||||
<slot></slot>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -14,6 +14,6 @@ const { fullHeight = true } = defineProps<{
|
||||
}>()
|
||||
|
||||
const containerClasses = computed(() =>
|
||||
cn('flex-1 w-full', fullHeight && 'h-full')
|
||||
cn('w-full flex-1', fullHeight && 'h-full')
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div :class="containerClasses">
|
||||
<slot name="top"></slot>
|
||||
<slot name="bottom"></slot>
|
||||
<slot name="top" />
|
||||
<slot name="bottom" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -59,7 +59,7 @@ const containerClasses = computed(() => {
|
||||
outline: cn(
|
||||
hasBorder && 'border-2 border-border-subtle',
|
||||
hasCursor && 'cursor-pointer',
|
||||
'hover:border-border-subtle/50 transition-colors'
|
||||
'transition-colors hover:border-border-subtle/50'
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div :class="chipClasses">
|
||||
<slot name="icon"></slot>
|
||||
<slot name="icon" />
|
||||
<span>{{ label }}</span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -19,9 +19,9 @@ const baseClasses =
|
||||
|
||||
const variantStyles = {
|
||||
dark: 'bg-zinc-500/40 text-white/90',
|
||||
light: cn('backdrop-blur-[2px] bg-base-background/50 text-base-foreground'),
|
||||
light: cn('bg-base-background/50 text-base-foreground backdrop-blur-[2px]'),
|
||||
gray: cn(
|
||||
'backdrop-blur-[2px] bg-modal-card-tag-background text-base-foreground'
|
||||
'bg-modal-card-tag-background text-base-foreground backdrop-blur-[2px]'
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
<template>
|
||||
<span class="relative inline-flex items-center justify-center size-[1em]">
|
||||
<i :class="mainIcon" class="text-[1em]" />
|
||||
<span class="relative inline-flex size-[1em] items-center justify-center">
|
||||
<i
|
||||
:class="mainIcon"
|
||||
class="text-[1em]"
|
||||
/>
|
||||
<i
|
||||
:class="
|
||||
cn(
|
||||
subIcon,
|
||||
'absolute leading-none pointer-events-none',
|
||||
'pointer-events-none absolute leading-none',
|
||||
positionX === 'left' ? 'left-0' : 'right-0',
|
||||
positionY === 'top' ? 'top-0' : 'bottom-0'
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div
|
||||
:class="
|
||||
cn(
|
||||
'relative flex w-full items-center gap-2 bg-comfy-input cursor-text text-comfy-input-foreground',
|
||||
'relative flex w-full cursor-text items-center gap-2 bg-comfy-input text-comfy-input-foreground',
|
||||
customClass,
|
||||
wrapperStyle
|
||||
)
|
||||
@@ -14,19 +14,22 @@
|
||||
:placeholder
|
||||
:autofocus
|
||||
unstyled
|
||||
class="absolute inset-0 size-full pl-11 border-none outline-none bg-transparent text-sm"
|
||||
class="absolute inset-0 size-full border-none bg-transparent pl-11 text-sm outline-none"
|
||||
:aria-label="placeholder"
|
||||
/>
|
||||
<Button
|
||||
v-if="filterIcon"
|
||||
size="icon"
|
||||
variant="textonly"
|
||||
class="filter-button absolute right-0 inset-y-0 m-0 p-0"
|
||||
class="filter-button absolute inset-y-0 right-0 m-0 p-0"
|
||||
@click="$emit('showFilter', $event)"
|
||||
>
|
||||
<i :class="filterIcon" />
|
||||
</Button>
|
||||
<InputIcon v-if="!modelValue" :class="icon" />
|
||||
<InputIcon
|
||||
v-if="!modelValue"
|
||||
:class="icon"
|
||||
/>
|
||||
<Button
|
||||
v-if="modelValue"
|
||||
class="clear-button absolute left-0"
|
||||
@@ -37,7 +40,10 @@
|
||||
<i class="icon-[lucide--x] size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<div v-if="filters?.length" class="search-filters flex flex-wrap gap-2 pt-2">
|
||||
<div
|
||||
v-if="filters?.length"
|
||||
class="search-filters flex flex-wrap gap-2 pt-2"
|
||||
>
|
||||
<SearchFilterChip
|
||||
v-for="filter in filters"
|
||||
:key="filter.id"
|
||||
@@ -109,7 +115,7 @@ watchDebounced(
|
||||
|
||||
const wrapperStyle = computed(() => {
|
||||
if (showBorder) {
|
||||
return cn('rounded p-2 border border-solid border-border-default')
|
||||
return cn('rounded border border-solid border-border-default p-2')
|
||||
}
|
||||
|
||||
// Size-specific classes matching button sizes for consistency
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<Tree
|
||||
v-model:expanded-keys="expandedKeys"
|
||||
v-model:selection-keys="selectionKeys"
|
||||
class="tree-explorer px-2 py-0 2xl:px-4 bg-transparent"
|
||||
class="tree-explorer bg-transparent px-2 py-0 2xl:px-4"
|
||||
:class="props.class"
|
||||
:value="renderedRoot.children"
|
||||
selection-mode="single"
|
||||
@@ -23,17 +23,26 @@
|
||||
}"
|
||||
>
|
||||
<template #folder="{ node }">
|
||||
<slot name="folder" :node="node">
|
||||
<slot
|
||||
name="folder"
|
||||
:node="node"
|
||||
>
|
||||
<TreeExplorerTreeNode :node="node" />
|
||||
</slot>
|
||||
</template>
|
||||
<template #node="{ node }">
|
||||
<slot name="node" :node="node">
|
||||
<slot
|
||||
name="node"
|
||||
:node="node"
|
||||
>
|
||||
<TreeExplorerTreeNode :node="node" />
|
||||
</slot>
|
||||
</template>
|
||||
</Tree>
|
||||
<ContextMenu ref="menu" :model="menuItems" />
|
||||
<ContextMenu
|
||||
ref="menu"
|
||||
:model="menuItems"
|
||||
/>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import ContextMenu from 'primevue/contextmenu'
|
||||
|
||||
@@ -12,13 +12,19 @@
|
||||
>
|
||||
<div class="node-content">
|
||||
<span class="node-label">
|
||||
<slot name="before-label" :node="props.node" />
|
||||
<slot
|
||||
name="before-label"
|
||||
:node="props.node"
|
||||
/>
|
||||
<EditableText
|
||||
:model-value="node.label"
|
||||
:is-editing="isEditing"
|
||||
@edit="handleRename"
|
||||
/>
|
||||
<slot name="after-label" :node="props.node" />
|
||||
<slot
|
||||
name="after-label"
|
||||
:node="props.node"
|
||||
/>
|
||||
</span>
|
||||
<Badge
|
||||
v-if="showNodeBadgeText"
|
||||
@@ -28,9 +34,12 @@
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="node-actions touch:opacity-100 motion-safe:opacity-0 motion-safe:group-hover/tree-node:opacity-100"
|
||||
class="node-actions motion-safe:opacity-0 motion-safe:group-hover/tree-node:opacity-100 touch:opacity-100"
|
||||
>
|
||||
<slot name="actions" :node="props.node" />
|
||||
<slot
|
||||
name="actions"
|
||||
:node="props.node"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col px-4 py-2 text-sm text-muted-foreground border-t border-border-default"
|
||||
class="flex flex-col border-t border-border-default px-4 py-2 text-sm text-muted-foreground"
|
||||
>
|
||||
<p v-if="promptTextReal">
|
||||
{{ promptTextReal }}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<template>
|
||||
<section class="w-full flex gap-2 justify-end px-2 pb-2">
|
||||
<Button :disabled variant="textonly" autofocus @click="$emit('cancel')">
|
||||
<section class="flex w-full justify-end gap-2 px-2 pb-2">
|
||||
<Button
|
||||
:disabled
|
||||
variant="textonly"
|
||||
autofocus
|
||||
@click="$emit('cancel')"
|
||||
>
|
||||
{{ cancelTextX }}
|
||||
</Button>
|
||||
<Button
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex items-center gap-2 p-4 font-bold text-sm text-base-foreground font-inter"
|
||||
class="flex items-center gap-2 p-4 font-inter text-sm font-bold text-base-foreground"
|
||||
>
|
||||
<span v-if="title" class="flex-auto">{{ title }}</span>
|
||||
<span
|
||||
v-if="title"
|
||||
class="flex-auto"
|
||||
>{{ title }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
||||
@@ -14,21 +14,27 @@
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<MissingCoreNodesMessage v-if="!isCloud" :missing-core-nodes />
|
||||
<MissingCoreNodesMessage
|
||||
v-if="!isCloud"
|
||||
:missing-core-nodes
|
||||
/>
|
||||
|
||||
<!-- Missing Nodes List Wrapper -->
|
||||
<div
|
||||
class="comfy-missing-nodes flex flex-col max-h-[256px] rounded-lg py-2 scrollbar-custom bg-secondary-background"
|
||||
class="comfy-missing-nodes flex scrollbar-custom max-h-[256px] flex-col rounded-lg bg-secondary-background py-2"
|
||||
>
|
||||
<div
|
||||
v-for="(node, i) in uniqueNodes"
|
||||
:key="i"
|
||||
class="flex min-h-8 items-center justify-between px-4 py-2 bg-secondary-background text-muted-foreground"
|
||||
class="flex min-h-8 items-center justify-between bg-secondary-background px-4 py-2 text-muted-foreground"
|
||||
>
|
||||
<span class="text-xs">
|
||||
{{ node.label }}
|
||||
</span>
|
||||
<span v-if="node.hint" class="text-xs">{{ node.hint }}</span>
|
||||
<span
|
||||
v-if="node.hint"
|
||||
class="text-xs"
|
||||
>{{ node.hint }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<!-- Cloud mode: Learn More + Got It buttons -->
|
||||
<div
|
||||
v-if="isCloud"
|
||||
class="flex w-full items-center justify-between gap-2 py-2 px-4"
|
||||
class="flex w-full items-center justify-between gap-2 px-4 py-2"
|
||||
>
|
||||
<Button
|
||||
variant="textonly"
|
||||
@@ -12,19 +12,33 @@
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<i class="icon-[lucide--info]"></i>
|
||||
<i class="icon-[lucide--info]" />
|
||||
<span>{{ $t('missingNodes.cloud.learnMore') }}</span>
|
||||
</Button>
|
||||
<Button variant="secondary" size="md" @click="handleGotItClick">{{
|
||||
$t('missingNodes.cloud.gotIt')
|
||||
}}</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="md"
|
||||
@click="handleGotItClick"
|
||||
>
|
||||
{{
|
||||
$t('missingNodes.cloud.gotIt')
|
||||
}}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- OSS mode: Open Manager + Install All buttons -->
|
||||
<div v-else-if="showManagerButtons" class="flex justify-end gap-1 py-2 px-4">
|
||||
<Button variant="textonly" @click="openManager">{{
|
||||
$t('g.openManager')
|
||||
}}</Button>
|
||||
<div
|
||||
v-else-if="showManagerButtons"
|
||||
class="flex justify-end gap-1 px-4 py-2"
|
||||
>
|
||||
<Button
|
||||
variant="textonly"
|
||||
@click="openManager"
|
||||
>
|
||||
{{
|
||||
$t('g.openManager')
|
||||
}}
|
||||
</Button>
|
||||
<PackInstallButton
|
||||
v-if="showInstallAllButton"
|
||||
type="secondary"
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
class="flex min-w-[460px] flex-col rounded-2xl border border-border-default bg-base-background shadow-[1px_1px_8px_0_rgba(0,0,0,0.4)]"
|
||||
>
|
||||
<!-- Header -->
|
||||
<div class="flex py-8 items-center justify-between px-8">
|
||||
<h2 class="text-lg font-bold text-base-foreground m-0">
|
||||
<div class="flex items-center justify-between px-8 py-8">
|
||||
<h2 class="m-0 text-lg font-bold text-base-foreground">
|
||||
{{
|
||||
isInsufficientCredits
|
||||
? $t('credits.topUp.addMoreCreditsToRun')
|
||||
@@ -12,7 +12,7 @@
|
||||
}}
|
||||
</h2>
|
||||
<button
|
||||
class="cursor-pointer rounded border-none bg-transparent p-0 text-muted-foreground transition-colors hover:text-base-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-secondary-foreground"
|
||||
class="focus-visible:ring-secondary-foreground cursor-pointer rounded border-none bg-transparent p-0 text-muted-foreground transition-colors hover:text-base-foreground focus-visible:ring-1 focus-visible:outline-none"
|
||||
@click="() => handleClose()"
|
||||
>
|
||||
<i class="icon-[lucide--x] size-6" />
|
||||
@@ -20,7 +20,7 @@
|
||||
</div>
|
||||
<p
|
||||
v-if="isInsufficientCredits"
|
||||
class="text-sm text-muted-foreground m-0 px-8"
|
||||
class="m-0 px-8 text-sm text-muted-foreground"
|
||||
>
|
||||
{{ $t('credits.topUp.insufficientWorkflowMessage') }}
|
||||
</p>
|
||||
@@ -39,7 +39,7 @@
|
||||
size="lg"
|
||||
:class="
|
||||
cn(
|
||||
'h-10 text-base font-medium w-full focus-visible:ring-secondary-foreground',
|
||||
'focus-visible:ring-secondary-foreground h-10 w-full text-base font-medium',
|
||||
selectedPreset === amount && 'bg-secondary-background-selected'
|
||||
)
|
||||
"
|
||||
@@ -65,9 +65,7 @@
|
||||
@max-reached="showCeilingWarning = true"
|
||||
>
|
||||
<template #prefix>
|
||||
<span class="shrink-0 text-base font-semibold text-base-foreground"
|
||||
>$</span
|
||||
>
|
||||
<span class="shrink-0 text-base font-semibold text-base-foreground">$</span>
|
||||
</template>
|
||||
</FormattedNumberStepper>
|
||||
</div>
|
||||
@@ -95,7 +93,7 @@
|
||||
|
||||
<p
|
||||
v-if="isBelowMin"
|
||||
class="text-sm text-red-500 m-0 px-8 pt-4 text-center flex items-center justify-center gap-1"
|
||||
class="m-0 flex items-center justify-center gap-1 px-8 pt-4 text-center text-sm text-red-500"
|
||||
>
|
||||
<i class="icon-[lucide--component] size-4" />
|
||||
{{
|
||||
@@ -106,7 +104,7 @@
|
||||
</p>
|
||||
<p
|
||||
v-if="showCeilingWarning"
|
||||
class="text-sm text-gold-500 m-0 px-8 pt-4 text-center flex items-center justify-center gap-1"
|
||||
class="m-0 flex items-center justify-center gap-1 px-8 pt-4 text-center text-sm text-gold-500"
|
||||
>
|
||||
<i class="icon-[lucide--component] size-4" />
|
||||
{{
|
||||
@@ -119,11 +117,10 @@
|
||||
href="https://www.comfy.org/cloud/enterprise"
|
||||
target="_blank"
|
||||
class="ml-1 text-inherit"
|
||||
>{{ $t('credits.topUp.contactUs') }}</a
|
||||
>
|
||||
>{{ $t('credits.topUp.contactUs') }}</a>
|
||||
</p>
|
||||
|
||||
<div class="pt-8 pb-8 flex flex-col gap-8 px-8">
|
||||
<div class="flex flex-col gap-8 px-8 pt-8 pb-8">
|
||||
<Button
|
||||
:disabled="!isValidAmount || loading"
|
||||
:loading="loading"
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex items-center justify-between p-2 rounded-lg cursor-pointer transition-all duration-200"
|
||||
class="flex cursor-pointer items-center justify-between rounded-lg p-2 transition-all duration-200"
|
||||
:class="[
|
||||
selected
|
||||
? 'bg-secondary-background border-2 border-border-default'
|
||||
: 'bg-component-node-disabled hover:bg-secondary-background border-2 border-transparent'
|
||||
? 'border-2 border-border-default bg-secondary-background'
|
||||
: 'bg-component-node-disabled border-2 border-transparent hover:bg-secondary-background'
|
||||
]"
|
||||
@click="$emit('select')"
|
||||
>
|
||||
|
||||
@@ -7,7 +7,10 @@
|
||||
>
|
||||
<!-- Email Field -->
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="mb-2 text-base font-medium opacity-80" :for="emailInputId">
|
||||
<label
|
||||
class="mb-2 text-base font-medium opacity-80"
|
||||
:for="emailInputId"
|
||||
>
|
||||
{{ t('auth.login.emailLabel') }}
|
||||
</label>
|
||||
<InputText
|
||||
@@ -19,7 +22,10 @@
|
||||
:placeholder="t('auth.login.emailPlaceholder')"
|
||||
:invalid="$form.email?.invalid"
|
||||
/>
|
||||
<small v-if="$form.email?.invalid" class="text-red-500">{{
|
||||
<small
|
||||
v-if="$form.email?.invalid"
|
||||
class="text-red-500"
|
||||
>{{
|
||||
$form.email.error.message
|
||||
}}</small>
|
||||
</div>
|
||||
@@ -54,13 +60,19 @@
|
||||
fluid
|
||||
class="h-10"
|
||||
/>
|
||||
<small v-if="$form.password?.invalid" class="text-red-500">{{
|
||||
<small
|
||||
v-if="$form.password?.invalid"
|
||||
class="text-red-500"
|
||||
>{{
|
||||
$form.password.error.message
|
||||
}}</small>
|
||||
</div>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<ProgressSpinner v-if="loading" class="mx-auto h-8 w-8" />
|
||||
<ProgressSpinner
|
||||
v-if="loading"
|
||||
class="mx-auto h-8 w-8"
|
||||
/>
|
||||
<Button
|
||||
v-else
|
||||
type="submit"
|
||||
@@ -131,6 +143,6 @@ const handleForgotPassword = async (
|
||||
@reference '../../../../assets/css/style.css';
|
||||
|
||||
.text-link-disabled {
|
||||
@apply opacity-50 cursor-not-allowed;
|
||||
@apply cursor-not-allowed opacity-50;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<ContextMenu
|
||||
ref="contextMenu"
|
||||
:model="menuItems"
|
||||
class="max-h-[80vh] md:max-h-none overflow-y-auto md:overflow-y-visible"
|
||||
class="max-h-[80vh] overflow-y-auto md:max-h-none md:overflow-y-visible"
|
||||
@show="onMenuShow"
|
||||
@hide="onMenuHide"
|
||||
>
|
||||
@@ -12,7 +12,10 @@
|
||||
class="flex items-center gap-2 px-3 py-1.5"
|
||||
@click="item.isColorSubmenu ? showColorPopover($event) : undefined"
|
||||
>
|
||||
<i v-if="item.icon" :class="[item.icon, 'size-4']" />
|
||||
<i
|
||||
v-if="item.icon"
|
||||
:class="[item.icon, 'size-4']"
|
||||
/>
|
||||
<span class="flex-1">{{ item.label }}</span>
|
||||
<span
|
||||
v-if="item.shortcut"
|
||||
|
||||
@@ -11,7 +11,10 @@
|
||||
@click="() => (showColorPicker = !showColorPicker)"
|
||||
>
|
||||
<div class="flex items-center gap-1 px-0">
|
||||
<i class="pi pi-circle-fill" :style="{ color: currentColor ?? '' }" />
|
||||
<i
|
||||
class="pi pi-circle-fill"
|
||||
:style="{ color: currentColor ?? '' }"
|
||||
/>
|
||||
<i class="icon-[lucide--chevron-down]" />
|
||||
</div>
|
||||
</Button>
|
||||
@@ -166,6 +169,6 @@ watch(
|
||||
}
|
||||
|
||||
:deep(.p-togglebutton) {
|
||||
@apply py-2 px-1;
|
||||
@apply px-1 py-2;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
:class="
|
||||
isColorSubmenu
|
||||
? 'flex flex-col gap-1 p-2'
|
||||
: 'flex flex-col p-2 min-w-40'
|
||||
: 'flex min-w-40 flex-col p-2'
|
||||
"
|
||||
>
|
||||
<div
|
||||
@@ -31,12 +31,12 @@
|
||||
:key="subOption.label"
|
||||
:class="
|
||||
cn(
|
||||
'hover:bg-secondary-background-hover rounded cursor-pointer',
|
||||
'cursor-pointer rounded hover:bg-secondary-background-hover',
|
||||
isColorSubmenu
|
||||
? 'w-7 h-7 flex items-center justify-center'
|
||||
? 'flex h-7 w-7 items-center justify-center'
|
||||
: 'flex items-center gap-2 px-3 py-1.5 text-sm',
|
||||
subOption.disabled
|
||||
? 'cursor-not-allowed pointer-events-none text-node-icon-disabled'
|
||||
? 'pointer-events-none cursor-not-allowed text-node-icon-disabled'
|
||||
: 'hover:bg-secondary-background-hover'
|
||||
)
|
||||
"
|
||||
@@ -53,7 +53,10 @@
|
||||
v-if="isShapeSelected(subOption)"
|
||||
class="icon-[lucide--check] size-4 flex-shrink-0"
|
||||
/>
|
||||
<div v-else class="w-4 flex-shrink-0" />
|
||||
<div
|
||||
v-else
|
||||
class="w-4 flex-shrink-0"
|
||||
/>
|
||||
<span>{{ subOption.label }}</span>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,10 @@
|
||||
>
|
||||
<!-- Main Menu Items -->
|
||||
<div class="w-full">
|
||||
<nav class="flex w-full flex-col gap-2" role="menubar">
|
||||
<nav
|
||||
class="flex w-full flex-col gap-2"
|
||||
role="menubar"
|
||||
>
|
||||
<button
|
||||
v-for="menuItem in menuItems"
|
||||
v-show="menuItem.visible !== false"
|
||||
@@ -26,14 +29,20 @@
|
||||
v-if="typeof menuItem.icon === 'object'"
|
||||
:size="16"
|
||||
/>
|
||||
<i v-else :class="menuItem.icon" />
|
||||
<i
|
||||
v-else
|
||||
:class="menuItem.icon"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="menuItem.showRedDot" class="menu-red-dot" />
|
||||
<div
|
||||
v-if="menuItem.showRedDot"
|
||||
class="menu-red-dot"
|
||||
/>
|
||||
</div>
|
||||
<span class="menu-label">{{ menuItem.label }}</span>
|
||||
<i
|
||||
v-if="menuItem.showExternalIcon"
|
||||
class="icon-[lucide--external-link] text-primary w-4 h-4 ml-auto"
|
||||
class="ml-auto icon-[lucide--external-link] h-4 w-4 text-primary"
|
||||
/>
|
||||
<i
|
||||
v-if="menuItem.key === 'more'"
|
||||
@@ -109,7 +118,10 @@
|
||||
@keydown.enter="onReleaseClick(release)"
|
||||
@keydown.space.prevent="onReleaseClick(release)"
|
||||
>
|
||||
<i class="help-menu-icon icon-[lucide--package]" aria-hidden="true" />
|
||||
<i
|
||||
class="help-menu-icon icon-[lucide--package]"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div class="release-content">
|
||||
<span class="release-title">
|
||||
{{
|
||||
@@ -119,7 +131,10 @@
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<time class="release-date" :datetime="release.published_at">
|
||||
<time
|
||||
class="release-date"
|
||||
:datetime="release.published_at"
|
||||
>
|
||||
<span class="normal-state">
|
||||
{{ formatReleaseDate(release.published_at) }}
|
||||
</span>
|
||||
@@ -138,13 +153,23 @@
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
>
|
||||
<i class="pi pi-spin pi-spinner help-menu-icon" aria-hidden="true" />
|
||||
<i
|
||||
class="pi pi-spin pi-spinner help-menu-icon"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span>{{ $t('helpCenter.loadingReleases') }}</span>
|
||||
</div>
|
||||
|
||||
<!-- No Releases State -->
|
||||
<div v-else class="help-menu-item" role="status">
|
||||
<i class="pi pi-info-circle help-menu-icon" aria-hidden="true" />
|
||||
<div
|
||||
v-else
|
||||
class="help-menu-item"
|
||||
role="status"
|
||||
>
|
||||
<i
|
||||
class="pi pi-info-circle help-menu-icon"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span>{{ $t('helpCenter.noRecentReleases') }}</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -10,7 +10,10 @@
|
||||
ref="containerEl"
|
||||
class="relative min-h-0 flex-1 overflow-hidden rounded-[5px] bg-node-component-surface"
|
||||
>
|
||||
<div v-if="isLoading" class="flex size-full items-center justify-center">
|
||||
<div
|
||||
v-if="isLoading"
|
||||
class="flex size-full items-center justify-center"
|
||||
>
|
||||
<span class="text-sm">{{ $t('imageCrop.loading') }}</span>
|
||||
</div>
|
||||
|
||||
@@ -19,7 +22,9 @@
|
||||
class="flex size-full flex-col items-center justify-center text-center"
|
||||
>
|
||||
<i class="mb-2 icon-[lucide--image] h-12 w-12" />
|
||||
<p class="text-sm">{{ $t('imageCrop.noInputImage') }}</p>
|
||||
<p class="text-sm">
|
||||
{{ $t('imageCrop.noInputImage') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<img
|
||||
@@ -28,11 +33,11 @@
|
||||
:src="imageUrl"
|
||||
:alt="$t('imageCrop.cropPreviewAlt')"
|
||||
draggable="false"
|
||||
class="block size-full object-contain select-none brightness-50"
|
||||
class="block size-full object-contain brightness-50 select-none"
|
||||
@load="handleImageLoad"
|
||||
@error="handleImageError"
|
||||
@dragstart.prevent
|
||||
/>
|
||||
>
|
||||
|
||||
<div
|
||||
v-if="imageUrl && !isLoading"
|
||||
@@ -42,7 +47,10 @@
|
||||
@pointermove="handleDragMove"
|
||||
@pointerup="handleDragEnd"
|
||||
>
|
||||
<div class="pointer-events-none size-full" :style="cropImageStyle" />
|
||||
<div
|
||||
class="pointer-events-none size-full"
|
||||
:style="cropImageStyle"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
@@ -57,7 +65,10 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<WidgetBoundingBox v-model="modelValue" class="shrink-0" />
|
||||
<WidgetBoundingBox
|
||||
v-model="modelValue"
|
||||
class="shrink-0"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@
|
||||
{{ $t('g.clearAll') }}
|
||||
</Button>
|
||||
</div>
|
||||
<div class="my-4 h-px bg-border-default"></div>
|
||||
<div class="my-4 h-px bg-border-default" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -147,7 +147,7 @@
|
||||
<template #option="slotProps">
|
||||
<div
|
||||
role="button"
|
||||
class="flex items-center gap-2 cursor-pointer"
|
||||
class="flex cursor-pointer items-center gap-2"
|
||||
:style="popoverStyle"
|
||||
>
|
||||
<div
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
ref="container"
|
||||
class="relative h-full w-full min-h-[200px]"
|
||||
class="relative h-full min-h-[200px] w-full"
|
||||
data-capture-wheel="true"
|
||||
@pointerdown.stop
|
||||
@pointermove.stop
|
||||
@@ -14,7 +14,10 @@
|
||||
@dragleave.stop="handleDragLeave"
|
||||
@drop.prevent.stop="handleDrop"
|
||||
>
|
||||
<LoadingOverlay :loading="loading" :loading-message="loadingMessage" />
|
||||
<LoadingOverlay
|
||||
:loading="loading"
|
||||
:loading-message="loadingMessage"
|
||||
/>
|
||||
<div
|
||||
v-if="!isPreview && isDragging"
|
||||
class="pointer-events-none absolute inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="relative show-slider">
|
||||
<div class="show-slider relative">
|
||||
<Button
|
||||
v-tooltip.right="{ value: tooltipText, showDelay: 300 }"
|
||||
size="icon"
|
||||
@@ -12,7 +12,7 @@
|
||||
</Button>
|
||||
<div
|
||||
v-show="showSlider"
|
||||
class="absolute top-0 left-12 rounded-lg bg-interface-menu-surface p-4 shadow-lg w-[150px]"
|
||||
class="absolute top-0 left-12 w-[150px] rounded-lg bg-interface-menu-surface p-4 shadow-lg"
|
||||
>
|
||||
<Slider
|
||||
v-model="value"
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
:class="
|
||||
cn(
|
||||
'rounded-full',
|
||||
isRecording && 'text-red-500 recording-button-blink'
|
||||
isRecording && 'recording-button-blink text-red-500'
|
||||
)
|
||||
"
|
||||
:aria-label="
|
||||
|
||||
@@ -1,75 +1,78 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-3 pb-3">
|
||||
<h3 class="text-center text-[15px] font-sans text-descrip-text mt-2.5">
|
||||
<h3 class="text-descrip-text mt-2.5 text-center font-sans text-[15px]">
|
||||
{{ t('maskEditor.brushSettings') }}
|
||||
</h3>
|
||||
|
||||
<button :class="textButtonClass" @click="resetToDefault">
|
||||
<button
|
||||
:class="textButtonClass"
|
||||
@click="resetToDefault"
|
||||
>
|
||||
{{ t('maskEditor.resetToDefault') }}
|
||||
</button>
|
||||
|
||||
<!-- Brush Shape -->
|
||||
<div class="flex flex-col gap-3 pb-3">
|
||||
<span class="text-left text-xs font-sans text-descrip-text">
|
||||
<span class="text-descrip-text text-left font-sans text-xs">
|
||||
{{ t('maskEditor.brushShape') }}
|
||||
</span>
|
||||
|
||||
<div
|
||||
class="flex flex-row gap-2.5 items-center h-[50px] w-full rounded-[10px] bg-secondary-background-hover"
|
||||
class="flex h-[50px] w-full flex-row items-center gap-2.5 rounded-[10px] bg-secondary-background-hover"
|
||||
>
|
||||
<div
|
||||
class="maskEditor_sidePanelBrushShapeCircle hover:bg-comfy-menu-bg"
|
||||
:class="
|
||||
cn(
|
||||
store.brushSettings.type === BrushShape.Arc
|
||||
? 'bg-[var(--p-button-text-primary-color)] active'
|
||||
? 'active bg-[var(--p-button-text-primary-color)]'
|
||||
: 'bg-transparent'
|
||||
)
|
||||
"
|
||||
@click="setBrushShape(BrushShape.Arc)"
|
||||
></div>
|
||||
/>
|
||||
|
||||
<div
|
||||
class="maskEditor_sidePanelBrushShapeSquare hover:bg-comfy-menu-bg"
|
||||
:class="
|
||||
cn(
|
||||
store.brushSettings.type === BrushShape.Rect
|
||||
? 'bg-[var(--p-button-text-primary-color)] active'
|
||||
? 'active bg-[var(--p-button-text-primary-color)]'
|
||||
: 'bg-transparent'
|
||||
)
|
||||
"
|
||||
@click="setBrushShape(BrushShape.Rect)"
|
||||
></div>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Color -->
|
||||
<div class="flex flex-col gap-3 pb-3">
|
||||
<span class="text-left text-xs font-sans text-descrip-text">
|
||||
<span class="text-descrip-text text-left font-sans text-xs">
|
||||
{{ t('maskEditor.colorSelector') }}
|
||||
</span>
|
||||
<input
|
||||
ref="colorInputRef"
|
||||
v-model="store.rgbColor"
|
||||
type="color"
|
||||
class="h-10 rounded-md cursor-pointer"
|
||||
/>
|
||||
class="h-10 cursor-pointer rounded-md"
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- Thickness -->
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-left text-xs font-sans text-descrip-text">
|
||||
<span class="text-descrip-text text-left font-sans text-xs">
|
||||
{{ t('maskEditor.thickness') }}
|
||||
</span>
|
||||
<input
|
||||
v-model.number="brushSize"
|
||||
type="number"
|
||||
class="w-16 px-2 py-1 text-sm text-center border rounded-md bg-comfy-menu-bg border-p-form-field-border-color text-input-text"
|
||||
class="border-p-form-field-border-color text-input-text w-16 rounded-md border bg-comfy-menu-bg px-2 py-1 text-center text-sm"
|
||||
:min="1"
|
||||
:max="250"
|
||||
:step="1"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
<SliderControl
|
||||
v-model="brushSize"
|
||||
@@ -84,17 +87,17 @@
|
||||
<!-- Opacity -->
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-left text-xs font-sans text-descrip-text">
|
||||
<span class="text-descrip-text text-left font-sans text-xs">
|
||||
{{ t('maskEditor.opacity') }}
|
||||
</span>
|
||||
<input
|
||||
v-model.number="brushOpacity"
|
||||
type="number"
|
||||
class="w-16 px-2 py-1 text-sm text-center border rounded-md bg-comfy-menu-bg border-p-form-field-border-color text-input-text"
|
||||
class="border-p-form-field-border-color text-input-text w-16 rounded-md border bg-comfy-menu-bg px-2 py-1 text-center text-sm"
|
||||
:min="0"
|
||||
:max="1"
|
||||
:step="0.01"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
<SliderControl
|
||||
v-model="brushOpacity"
|
||||
@@ -109,17 +112,17 @@
|
||||
<!-- Hardness -->
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-left text-xs font-sans text-descrip-text">
|
||||
<span class="text-descrip-text text-left font-sans text-xs">
|
||||
{{ t('maskEditor.hardness') }}
|
||||
</span>
|
||||
<input
|
||||
v-model.number="brushHardness"
|
||||
type="number"
|
||||
class="w-16 px-2 py-1 text-sm text-center border rounded-md bg-comfy-menu-bg border-p-form-field-border-color text-input-text"
|
||||
class="border-p-form-field-border-color text-input-text w-16 rounded-md border bg-comfy-menu-bg px-2 py-1 text-center text-sm"
|
||||
:min="0"
|
||||
:max="1"
|
||||
:step="0.01"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
<SliderControl
|
||||
v-model="brushHardness"
|
||||
@@ -134,17 +137,17 @@
|
||||
<!-- Step Size -->
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-left text-xs font-sans text-descrip-text">
|
||||
<span class="text-descrip-text text-left font-sans text-xs">
|
||||
{{ t('maskEditor.stepSize') }}
|
||||
</span>
|
||||
<input
|
||||
v-model.number="brushStepSize"
|
||||
type="number"
|
||||
class="w-16 px-2 py-1 text-sm text-center border rounded-md bg-comfy-menu-bg border-p-form-field-border-color text-input-text"
|
||||
class="border-p-form-field-border-color text-input-text w-16 rounded-md border bg-comfy-menu-bg px-2 py-1 text-center text-sm"
|
||||
:min="1"
|
||||
:max="100"
|
||||
:step="1"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
<SliderControl
|
||||
v-model="brushStepSize"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-3 pb-3">
|
||||
<h3
|
||||
class="text-center text-[15px] font-sans text-[var(--descrip-text)] mt-2.5"
|
||||
class="mt-2.5 text-center font-sans text-[15px] text-[var(--descrip-text)]"
|
||||
>
|
||||
{{ t('maskEditor.colorSelectSettings') }}
|
||||
</h3>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-3 pb-3">
|
||||
<h3
|
||||
class="text-center text-[15px] font-sans text-[var(--descrip-text)] mt-2.5"
|
||||
class="mt-2.5 text-center font-sans text-[15px] text-[var(--descrip-text)]"
|
||||
>
|
||||
{{ t('maskEditor.layers') }}
|
||||
</h3>
|
||||
@@ -15,29 +15,35 @@
|
||||
@update:model-value="onMaskOpacityChange"
|
||||
/>
|
||||
|
||||
<span class="text-left text-xs font-sans text-[var(--descrip-text)]">{{
|
||||
<span class="text-left font-sans text-xs text-[var(--descrip-text)]">{{
|
||||
t('maskEditor.maskBlendingOptions')
|
||||
}}</span>
|
||||
|
||||
<div
|
||||
class="flex flex-row gap-2.5 items-center min-h-6 relative h-[50px] w-full rounded-[10px] -mt-2 -mb-1.5"
|
||||
class="relative -mt-2 -mb-1.5 flex h-[50px] min-h-6 w-full flex-row items-center gap-2.5 rounded-[10px]"
|
||||
>
|
||||
<select
|
||||
class="maskEditor_sidePanelDropdown"
|
||||
:value="store.maskBlendMode"
|
||||
@change="onBlendModeChange"
|
||||
>
|
||||
<option value="black">{{ t('maskEditor.black') }}</option>
|
||||
<option value="white">{{ t('maskEditor.white') }}</option>
|
||||
<option value="negative">{{ t('maskEditor.negative') }}</option>
|
||||
<option value="black">
|
||||
{{ t('maskEditor.black') }}
|
||||
</option>
|
||||
<option value="white">
|
||||
{{ t('maskEditor.white') }}
|
||||
</option>
|
||||
<option value="negative">
|
||||
{{ t('maskEditor.negative') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<span class="text-left text-xs font-sans text-[var(--descrip-text)]">{{
|
||||
<span class="text-left font-sans text-xs text-[var(--descrip-text)]">{{
|
||||
t('maskEditor.maskLayer')
|
||||
}}</span>
|
||||
<div
|
||||
class="flex flex-row gap-2.5 items-center min-h-6 relative h-[50px] w-full rounded-[10px] bg-secondary-background-hover"
|
||||
class="relative flex h-[50px] min-h-6 w-full flex-row items-center gap-2.5 rounded-[10px] bg-secondary-background-hover"
|
||||
:style="{
|
||||
border: store.activeLayer === 'mask' ? '2px solid #007acc' : 'none'
|
||||
}"
|
||||
@@ -47,9 +53,12 @@
|
||||
class="maskEditor_sidePanelLayerCheckbox"
|
||||
:checked="maskLayerVisible"
|
||||
@change="onMaskLayerVisibilityChange"
|
||||
/>
|
||||
>
|
||||
<div class="maskEditor_sidePanelLayerPreviewContainer">
|
||||
<svg viewBox="0 0 20 20" style="">
|
||||
<svg
|
||||
viewBox="0 0 20 20"
|
||||
style=""
|
||||
>
|
||||
<path
|
||||
class="cls-1"
|
||||
d="M1.31,5.32v9.36c0,.55.45,1,1,1h15.38c.55,0,1-.45,1-1V5.32c0-.55-.45-1-1-1H2.31c-.55,0-1,.45-1,1ZM11.19,13.44c-2.91.94-5.57-1.72-4.63-4.63.34-1.05,1.19-1.9,2.24-2.24,2.91-.94,5.57,1.72,4.63,4.63-.34,1.05-1.19-1.9-2.24,2.24Z"
|
||||
@@ -66,11 +75,11 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<span class="text-left text-xs font-sans text-[var(--descrip-text)]">{{
|
||||
<span class="text-left font-sans text-xs text-[var(--descrip-text)]">{{
|
||||
t('maskEditor.paintLayer')
|
||||
}}</span>
|
||||
<div
|
||||
class="flex flex-row gap-2.5 items-center min-h-6 relative h-[50px] w-full rounded-[10px] bg-secondary-background-hover"
|
||||
class="relative flex h-[50px] min-h-6 w-full flex-row items-center gap-2.5 rounded-[10px] bg-secondary-background-hover"
|
||||
:style="{
|
||||
border: store.activeLayer === 'rgb' ? '2px solid #007acc' : 'none'
|
||||
}"
|
||||
@@ -80,7 +89,7 @@
|
||||
class="maskEditor_sidePanelLayerCheckbox"
|
||||
:checked="paintLayerVisible"
|
||||
@change="onPaintLayerVisibilityChange"
|
||||
/>
|
||||
>
|
||||
<div class="maskEditor_sidePanelLayerPreviewContainer">
|
||||
<svg viewBox="0 0 20 20">
|
||||
<path
|
||||
@@ -106,24 +115,24 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<span class="text-left text-xs font-sans text-[var(--descrip-text)]">{{
|
||||
<span class="text-left font-sans text-xs text-[var(--descrip-text)]">{{
|
||||
t('maskEditor.baseImageLayer')
|
||||
}}</span>
|
||||
<div
|
||||
class="flex flex-row gap-2.5 items-center min-h-6 relative h-[50px] w-full rounded-[10px] bg-secondary-background-hover"
|
||||
class="relative flex h-[50px] min-h-6 w-full flex-row items-center gap-2.5 rounded-[10px] bg-secondary-background-hover"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="maskEditor_sidePanelLayerCheckbox"
|
||||
:checked="baseImageLayerVisible"
|
||||
@change="onBaseImageLayerVisibilityChange"
|
||||
/>
|
||||
>
|
||||
<div class="maskEditor_sidePanelLayerPreviewContainer">
|
||||
<img
|
||||
class="maskEditor_sidePanelImageLayerImage"
|
||||
:src="baseImageSrc"
|
||||
:alt="t('maskEditor.baseLayerPreview')"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,29 +13,32 @@
|
||||
>
|
||||
<canvas
|
||||
ref="imgCanvasRef"
|
||||
class="absolute top-0 left-0 w-full h-full z-0"
|
||||
class="absolute top-0 left-0 z-0 h-full w-full"
|
||||
@contextmenu.prevent
|
||||
/>
|
||||
<canvas
|
||||
ref="rgbCanvasRef"
|
||||
class="absolute top-0 left-0 w-full h-full z-10"
|
||||
class="absolute top-0 left-0 z-10 h-full w-full"
|
||||
@contextmenu.prevent
|
||||
/>
|
||||
<canvas
|
||||
ref="maskCanvasRef"
|
||||
class="absolute top-0 left-0 w-full h-full z-30"
|
||||
class="absolute top-0 left-0 z-30 h-full w-full"
|
||||
@contextmenu.prevent
|
||||
/>
|
||||
<!-- GPU Preview Canvas -->
|
||||
<canvas
|
||||
ref="gpuCanvasRef"
|
||||
class="absolute top-0 left-0 w-full h-full pointer-events-none"
|
||||
class="pointer-events-none absolute top-0 left-0 h-full w-full"
|
||||
:class="{
|
||||
'z-20': store.activeLayer === 'rgb',
|
||||
'z-40': store.activeLayer === 'mask'
|
||||
}"
|
||||
/>
|
||||
<div ref="canvasBackgroundRef" class="bg-white w-full h-full" />
|
||||
<div
|
||||
ref="canvasBackgroundRef"
|
||||
class="h-full w-full bg-white"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="maskEditor-ui-container flex min-h-0 flex-1 flex-col">
|
||||
@@ -60,7 +63,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<BrushCursor v-if="initialized" :container-ref="containerRef" />
|
||||
<BrushCursor
|
||||
v-if="initialized"
|
||||
:container-ref="containerRef"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-3 pb-3">
|
||||
<h3
|
||||
class="text-center text-[15px] font-sans text-[var(--descrip-text)] mt-2.5"
|
||||
class="mt-2.5 text-center font-sans text-[15px] text-[var(--descrip-text)]"
|
||||
>
|
||||
{{ t('maskEditor.paintBucketSettings') }}
|
||||
</h3>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
ref="pointerZoneRef"
|
||||
class="w-[calc(100%-4rem-220px)] h-full"
|
||||
class="h-full w-[calc(100%-4rem-220px)]"
|
||||
@pointerdown="handlePointerDown"
|
||||
@pointermove="handlePointerMove"
|
||||
@pointerup="handlePointerUp"
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col gap-3 pb-3 h-full !items-stretch bg-[var(--comfy-menu-bg)] overflow-y-auto w-55 px-2.5"
|
||||
class="flex h-full w-55 flex-col !items-stretch gap-3 overflow-y-auto bg-[var(--comfy-menu-bg)] px-2.5 pb-3"
|
||||
>
|
||||
<div class="w-full min-h-full">
|
||||
<div class="min-h-full w-full">
|
||||
<SettingsPanelContainer />
|
||||
|
||||
<div class="w-full h-0.5 bg-[var(--border-color)] mt-6 mb-1.5" />
|
||||
<div class="mt-6 mb-1.5 h-0.5 w-full bg-[var(--border-color)]" />
|
||||
|
||||
<ImageLayerSettingsPanel :tool-manager="toolManager" />
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="h-full z-8888 flex flex-col justify-between bg-comfy-menu-bg">
|
||||
<div class="z-8888 flex h-full flex-col justify-between bg-comfy-menu-bg">
|
||||
<div class="flex flex-col">
|
||||
<div
|
||||
v-for="tool in allTools"
|
||||
@@ -13,13 +13,13 @@
|
||||
<div
|
||||
class="flex items-center justify-center"
|
||||
v-html="iconsHtml[tool]"
|
||||
></div>
|
||||
<div class="maskEditor_toolPanelIndicator"></div>
|
||||
/>
|
||||
<div class="maskEditor_toolPanelIndicator" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex flex-col items-center cursor-pointer rounded-md mb-2 transition-colors duration-200 hover:bg-secondary-background-hover"
|
||||
class="mb-2 flex cursor-pointer flex-col items-center rounded-md transition-colors duration-200 hover:bg-secondary-background-hover"
|
||||
:title="t('maskEditor.clickToResetZoom')"
|
||||
@click="onResetZoom"
|
||||
>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div class="flex flex-row gap-2.5 items-center min-h-6 relative">
|
||||
<span class="text-left text-xs font-sans text-[var(--descrip-text)]">{{
|
||||
<div class="relative flex min-h-6 flex-row items-center gap-2.5">
|
||||
<span class="text-left font-sans text-xs text-[var(--descrip-text)]">{{
|
||||
label
|
||||
}}</span>
|
||||
<select
|
||||
class="absolute right-0 h-6 px-1.5 rounded-md border border-border-default transition-colors duration-100 bg-secondary-background focus:outline focus:outline-node-component-border"
|
||||
class="absolute right-0 h-6 rounded-md border border-border-default bg-secondary-background px-1.5 transition-colors duration-100 focus:outline focus:outline-node-component-border"
|
||||
:value="modelValue"
|
||||
@change="onChange"
|
||||
>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-3 pb-3">
|
||||
<span class="text-left text-xs font-sans text-[var(--descrip-text)]">{{
|
||||
<span class="text-left font-sans text-xs text-[var(--descrip-text)]">{{
|
||||
label
|
||||
}}</span>
|
||||
<input
|
||||
@@ -11,7 +11,7 @@
|
||||
:step="step"
|
||||
:value="modelValue"
|
||||
@input="onInput"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="flex flex-row gap-2.5 items-center min-h-6 relative">
|
||||
<span class="text-left text-xs font-sans text-[var(--descrip-text)]">{{
|
||||
<div class="relative flex min-h-6 flex-row items-center gap-2.5">
|
||||
<span class="text-left font-sans text-xs text-[var(--descrip-text)]">{{
|
||||
label
|
||||
}}</span>
|
||||
<label class="maskEditor_sidePanelToggleContainer">
|
||||
@@ -9,8 +9,8 @@
|
||||
class="maskEditor_sidePanelToggleCheckbox"
|
||||
:checked="modelValue"
|
||||
@change="onChange"
|
||||
/>
|
||||
<div class="maskEditor_sidePanelToggleSwitch"></div>
|
||||
>
|
||||
<div class="maskEditor_sidePanelToggleSwitch" />
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<template>
|
||||
<div class="flex w-full items-center justify-between gap-3">
|
||||
<div class="flex items-center gap-3">
|
||||
<h3 class="m-0 text-lg font-semibold">{{ t('maskEditor.title') }}</h3>
|
||||
<h3 class="m-0 text-lg font-semibold">
|
||||
{{ t('maskEditor.title') }}
|
||||
</h3>
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
<button
|
||||
@@ -11,7 +13,7 @@
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 15 15"
|
||||
class="h-6.25 w-6.25 pointer-events-none fill-current"
|
||||
class="pointer-events-none h-6.25 w-6.25 fill-current"
|
||||
>
|
||||
<path
|
||||
d="M8.77,12.18c-.25,0-.46-.2-.46-.46s.2-.46.46-.46c1.47,0,2.67-1.2,2.67-2.67,0-1.57-1.34-2.67-3.26-2.67h-3.98l1.43,1.43c.18.18.18.47,0,.64-.18.18-.47.18-.64,0l-2.21-2.21c-.18-.18-.18-.47,0-.64l2.21-2.21c.18-.18.47-.18.64,0,.18.18.18.47,0,.64l-1.43,1.43h3.98c2.45,0,4.17,1.47,4.17,3.58,0,1.97-1.61,3.58-3.58,3.58Z"
|
||||
@@ -26,7 +28,7 @@
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 15 15"
|
||||
class="h-6.25 w-6.25 pointer-events-none fill-[var(--input-text)]"
|
||||
class="pointer-events-none h-6.25 w-6.25 fill-[var(--input-text)]"
|
||||
>
|
||||
<path
|
||||
class="cls-1"
|
||||
@@ -35,7 +37,7 @@
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div class="h-5 border-l border-border" />
|
||||
<div class="border-border h-5 border-l" />
|
||||
|
||||
<button
|
||||
:class="iconButtonClass"
|
||||
@@ -44,7 +46,7 @@
|
||||
>
|
||||
<svg
|
||||
viewBox="-6 -7 15 15"
|
||||
class="h-6.25 w-6.25 pointer-events-none fill-[var(--input-text)]"
|
||||
class="pointer-events-none h-6.25 w-6.25 fill-[var(--input-text)]"
|
||||
>
|
||||
<path
|
||||
d="m2.25-2.625c.3452 0 .625.2798.625.625v5c0 .3452-.2798.625-.625.625h-5c-.3452 0-.625-.2798-.625-.625v-5c0-.3452.2798-.625.625-.625h5zm1.25.625v5c0 .6904-.5596 1.25-1.25 1.25h-5c-.6904 0-1.25-.5596-1.25-1.25v-5c0-.6904.5596-1.25 1.25-1.25h5c.6904 0 1.25.5596 1.25 1.25zm-.1673-2.3757-.4419.4419-1.5246-1.5246 1.5416-1.5417.442.4419-.7871.7872h.9373c1.3807 0 2.5 1.1193 2.5 2.5h-.625c0-1.0355-.8395-1.875-1.875-1.875h-.9375l.7702.7702z"
|
||||
@@ -59,7 +61,7 @@
|
||||
>
|
||||
<svg
|
||||
viewBox="-9 -7 15 15"
|
||||
class="h-6.25 w-6.25 pointer-events-none fill-[var(--input-text)]"
|
||||
class="pointer-events-none h-6.25 w-6.25 fill-[var(--input-text)]"
|
||||
>
|
||||
<g transform="scale(-1, 1)">
|
||||
<path
|
||||
@@ -76,7 +78,7 @@
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 15 15"
|
||||
class="h-6.25 w-6.25 pointer-events-none fill-[var(--input-text)]"
|
||||
class="pointer-events-none h-6.25 w-6.25 fill-[var(--input-text)]"
|
||||
>
|
||||
<path
|
||||
d="M7.5,1.5c-.28,0-.5.22-.5.5v11c0,.28.22.5.5.5s.5-.22.5-.5v-11c0-.28-.22-.5-.5-.5Z"
|
||||
@@ -92,7 +94,7 @@
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 15 15"
|
||||
class="h-6.25 w-6.25 pointer-events-none fill-[var(--input-text)]"
|
||||
class="pointer-events-none h-6.25 w-6.25 fill-[var(--input-text)]"
|
||||
>
|
||||
<path
|
||||
d="M2,7.5c0-.28.22-.5.5-.5h11c.28,0,.5.22.5.5s-.22.5-.5.5h-11c-.28,0-.5-.22-.5-.5Z"
|
||||
@@ -103,22 +105,35 @@
|
||||
|
||||
<div class="h-5 w-px bg-[var(--p-form-field-border-color)]" />
|
||||
|
||||
<button :class="textButtonClass" @click="onInvert">
|
||||
<button
|
||||
:class="textButtonClass"
|
||||
@click="onInvert"
|
||||
>
|
||||
{{ t('maskEditor.invert') }}
|
||||
</button>
|
||||
|
||||
<button :class="textButtonClass" @click="onClear">
|
||||
<button
|
||||
:class="textButtonClass"
|
||||
@click="onClear"
|
||||
>
|
||||
{{ t('maskEditor.clear') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<Button variant="primary" :disabled="!saveEnabled" @click="handleSave">
|
||||
<Button
|
||||
variant="primary"
|
||||
:disabled="!saveEnabled"
|
||||
@click="handleSave"
|
||||
>
|
||||
<i class="pi pi-check" />
|
||||
{{ saveButtonText }}
|
||||
</Button>
|
||||
<Button variant="secondary" @click="handleCancel">
|
||||
<Button
|
||||
variant="secondary"
|
||||
@click="handleCancel"
|
||||
>
|
||||
<i class="pi pi-times" />
|
||||
{{ t('g.cancel') }}
|
||||
</Button>
|
||||
|
||||
@@ -12,7 +12,10 @@
|
||||
v-html="renderedHelpHtml"
|
||||
/>
|
||||
<!-- Fallback: markdown not found or fetch error -->
|
||||
<div v-else class="fallback-content space-y-6 text-sm">
|
||||
<div
|
||||
v-else
|
||||
class="fallback-content space-y-6 text-sm"
|
||||
>
|
||||
<p v-if="node.description">
|
||||
<strong>{{ $t('g.description') }}:</strong> {{ node.description }}
|
||||
</p>
|
||||
@@ -31,7 +34,10 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="input in inputList" :key="input.name">
|
||||
<tr
|
||||
v-for="input in inputList"
|
||||
:key="input.name"
|
||||
>
|
||||
<td>
|
||||
<code>{{ input.name }}</code>
|
||||
</td>
|
||||
@@ -55,7 +61,10 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="output in outputList" :key="output.name">
|
||||
<tr
|
||||
v-for="output in outputList"
|
||||
:key="output.name"
|
||||
>
|
||||
<td>
|
||||
<code>{{ output.name }}</code>
|
||||
</td>
|
||||
@@ -103,27 +112,27 @@ const outputList = computed(() =>
|
||||
@reference './../../assets/css/style.css';
|
||||
|
||||
.node-help-content :deep(:is(img, video)) {
|
||||
@apply max-w-full h-auto block mb-4;
|
||||
@apply mb-4 block h-auto max-w-full;
|
||||
}
|
||||
|
||||
.markdown-content,
|
||||
.fallback-content {
|
||||
@apply text-sm overflow-visible;
|
||||
@apply overflow-visible text-sm;
|
||||
}
|
||||
|
||||
.markdown-content :deep(h1),
|
||||
.fallback-content h1 {
|
||||
@apply text-[22px] font-bold mt-8 mb-4 first:mt-0;
|
||||
@apply mt-8 mb-4 text-[22px] font-bold first:mt-0;
|
||||
}
|
||||
|
||||
.markdown-content :deep(h2),
|
||||
.fallback-content h2 {
|
||||
@apply text-[18px] font-bold mt-8 mb-4 first:mt-0;
|
||||
@apply mt-8 mb-4 text-[18px] font-bold first:mt-0;
|
||||
}
|
||||
|
||||
.markdown-content :deep(h3),
|
||||
.fallback-content h3 {
|
||||
@apply text-[16px] font-bold mt-8 mb-4 first:mt-0;
|
||||
@apply mt-8 mb-4 text-[16px] font-bold first:mt-0;
|
||||
}
|
||||
|
||||
.markdown-content :deep(h4),
|
||||
@@ -155,7 +164,7 @@ const outputList = computed(() =>
|
||||
.markdown-content :deep(ol),
|
||||
.fallback-content ul,
|
||||
.fallback-content ol {
|
||||
@apply pl-8 my-2;
|
||||
@apply my-2 pl-8;
|
||||
}
|
||||
|
||||
.markdown-content :deep(ul ul),
|
||||
@@ -166,7 +175,7 @@ const outputList = computed(() =>
|
||||
.fallback-content ol ol,
|
||||
.fallback-content ul ol,
|
||||
.fallback-content ol ul {
|
||||
@apply pl-6 my-2;
|
||||
@apply my-2 pl-6;
|
||||
}
|
||||
|
||||
.markdown-content :deep(li),
|
||||
@@ -215,7 +224,7 @@ const outputList = computed(() =>
|
||||
|
||||
.markdown-content :deep(pre),
|
||||
.fallback-content pre {
|
||||
@apply rounded p-4 my-4 overflow-x-auto;
|
||||
@apply my-4 overflow-x-auto rounded p-4;
|
||||
background-color: var(--code-block-bg-color);
|
||||
|
||||
code {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<div class="flex items-center justify-between px-3">
|
||||
<Button
|
||||
class="grow gap-1 justify-center"
|
||||
class="grow justify-center gap-1"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
@click="$emit('showAssets')"
|
||||
@@ -51,7 +51,7 @@
|
||||
@update:selected-sort-mode="$emit('update:selectedSortMode', $event)"
|
||||
/>
|
||||
|
||||
<div class="flex-1 min-h-0 overflow-y-auto">
|
||||
<div class="min-h-0 flex-1 overflow-y-auto">
|
||||
<JobGroupsList
|
||||
:displayed-job-groups="displayedJobGroups"
|
||||
@cancel-item="onCancelItemEvent"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
:class="['flex', 'justify-end', 'w-full', 'pointer-events-none']"
|
||||
>
|
||||
<div
|
||||
class="pointer-events-auto flex w-[350px] min-w-[310px] max-h-[60vh] flex-col overflow-hidden rounded-lg border font-inter transition-colors duration-200 ease-in-out"
|
||||
class="pointer-events-auto flex max-h-[60vh] w-[350px] min-w-[310px] flex-col overflow-hidden rounded-lg border font-inter transition-colors duration-200 ease-in-out"
|
||||
:class="containerClass"
|
||||
@mouseenter="isHovered = true"
|
||||
@mouseleave="isHovered = false"
|
||||
@@ -15,7 +15,7 @@
|
||||
v-model:selected-job-tab="selectedJobTab"
|
||||
v-model:selected-workflow-filter="selectedWorkflowFilter"
|
||||
v-model:selected-sort-mode="selectedSortMode"
|
||||
class="flex-1 min-h-0"
|
||||
class="min-h-0 flex-1"
|
||||
:header-title="headerTitle"
|
||||
:show-concurrent-indicator="showConcurrentIndicator"
|
||||
:concurrent-workflow-count="concurrentWorkflowCount"
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<section
|
||||
class="w-[360px] rounded-2xl border border-interface-stroke bg-interface-panel-surface text-text-primary shadow-interface font-inter"
|
||||
class="w-[360px] rounded-2xl border border-interface-stroke bg-interface-panel-surface font-inter text-text-primary shadow-interface"
|
||||
>
|
||||
<header
|
||||
class="flex items-center justify-between border-b border-interface-stroke px-4 py-4"
|
||||
>
|
||||
<p class="m-0 text-[14px] font-normal leading-none">
|
||||
<p class="m-0 text-[14px] leading-none font-normal">
|
||||
{{ t('sideToolbar.queueProgressOverlay.clearHistoryDialogTitle') }}
|
||||
</p>
|
||||
<Button
|
||||
@@ -31,7 +31,11 @@
|
||||
|
||||
<footer class="flex items-center justify-end px-4 py-4">
|
||||
<div class="flex items-center gap-4 leading-none">
|
||||
<Button variant="muted-textonly" size="lg" @click="onCancel">
|
||||
<Button
|
||||
variant="muted-textonly"
|
||||
size="lg"
|
||||
@click="onCancel"
|
||||
>
|
||||
{{ t('g.cancel') }}
|
||||
</Button>
|
||||
<Button
|
||||
@@ -39,8 +43,9 @@
|
||||
size="lg"
|
||||
:disabled="isClearing"
|
||||
@click="onConfirm"
|
||||
>{{ t('g.clear') }}</Button
|
||||
>
|
||||
{{ t('g.clear') }}
|
||||
</Button>
|
||||
</div>
|
||||
</footer>
|
||||
</section>
|
||||
|
||||
@@ -50,10 +50,10 @@
|
||||
<div
|
||||
v-if="
|
||||
props.state === 'running' &&
|
||||
hasAnyProgressPercent(
|
||||
props.progressTotalPercent,
|
||||
props.progressCurrentPercent
|
||||
)
|
||||
hasAnyProgressPercent(
|
||||
props.progressTotalPercent,
|
||||
props.progressCurrentPercent
|
||||
)
|
||||
"
|
||||
:class="progressBarContainerClass"
|
||||
>
|
||||
@@ -72,7 +72,7 @@
|
||||
<div class="relative z-1 flex items-center gap-1">
|
||||
<div class="relative inline-flex items-center justify-center">
|
||||
<div
|
||||
class="absolute left-1/2 top-1/2 size-10 -translate-x-1/2 -translate-y-1/2"
|
||||
class="absolute top-1/2 left-1/2 size-10 -translate-x-1/2 -translate-y-1/2"
|
||||
@mouseenter.stop="onIconEnter"
|
||||
@mouseleave.stop="onIconLeave"
|
||||
/>
|
||||
@@ -83,7 +83,7 @@
|
||||
v-if="iconImageUrl"
|
||||
:src="iconImageUrl"
|
||||
class="h-full w-full object-cover"
|
||||
/>
|
||||
>
|
||||
<i
|
||||
v-else
|
||||
:class="cn(iconClass, 'size-4', shouldSpin && 'animate-spin')"
|
||||
@@ -93,8 +93,13 @@
|
||||
</div>
|
||||
|
||||
<div class="relative z-1 min-w-0 flex-1">
|
||||
<div class="truncate opacity-90" :title="props.title">
|
||||
<slot name="primary">{{ props.title }}</slot>
|
||||
<div
|
||||
class="truncate opacity-90"
|
||||
:title="props.title"
|
||||
>
|
||||
<slot name="primary">
|
||||
{{ props.title }}
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -143,8 +148,8 @@
|
||||
<Button
|
||||
v-else-if="
|
||||
props.state !== 'completed' &&
|
||||
props.state !== 'running' &&
|
||||
computedShowClear
|
||||
props.state !== 'running' &&
|
||||
computedShowClear
|
||||
"
|
||||
v-tooltip.top="cancelTooltipConfig"
|
||||
variant="destructive"
|
||||
@@ -159,8 +164,9 @@
|
||||
variant="textonly"
|
||||
size="sm"
|
||||
@click.stop="emit('view')"
|
||||
>{{ t('menuLabels.View') }}</Button
|
||||
>
|
||||
{{ t('menuLabels.View') }}
|
||||
</Button>
|
||||
<Button
|
||||
v-if="props.showMenu !== undefined ? props.showMenu : true"
|
||||
v-tooltip.top="moreTooltipConfig"
|
||||
@@ -177,7 +183,9 @@
|
||||
key="secondary"
|
||||
class="pr-2"
|
||||
>
|
||||
<slot name="secondary">{{ props.rightText }}</slot>
|
||||
<slot name="secondary">
|
||||
{{ props.rightText }}
|
||||
</slot>
|
||||
</div>
|
||||
</Transition>
|
||||
<!-- Running job cancel button - always visible -->
|
||||
|
||||
@@ -194,8 +194,8 @@ function handleTitleCancel() {
|
||||
>
|
||||
<!-- Panel Header -->
|
||||
<section class="pt-1">
|
||||
<div class="flex items-center justify-between pl-4 pr-3">
|
||||
<h3 class="my-3.5 text-sm font-semibold line-clamp-2 cursor-default">
|
||||
<div class="flex items-center justify-between pr-3 pl-4">
|
||||
<h3 class="my-3.5 line-clamp-2 cursor-default text-sm font-semibold">
|
||||
<template v-if="allowTitleEdit">
|
||||
<EditableText
|
||||
:model-value="panelTitle"
|
||||
@@ -208,7 +208,7 @@ function handleTitleCancel() {
|
||||
/>
|
||||
<i
|
||||
v-if="!isEditing"
|
||||
class="icon-[lucide--pencil] size-4 text-muted-foreground ml-2 content-center relative top-[2px] hover:text-base-foreground cursor-pointer shrink-0"
|
||||
class="relative top-[2px] ml-2 icon-[lucide--pencil] size-4 shrink-0 cursor-pointer content-center text-muted-foreground hover:text-base-foreground"
|
||||
@click="isEditing = true"
|
||||
/>
|
||||
</template>
|
||||
@@ -242,7 +242,7 @@ function handleTitleCancel() {
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="px-4 pb-2 pt-1 overflow-x-auto">
|
||||
<nav class="overflow-x-auto px-4 pt-1 pb-2">
|
||||
<TabList
|
||||
:model-value="activeTab"
|
||||
@update:model-value="
|
||||
@@ -254,7 +254,7 @@ function handleTitleCancel() {
|
||||
<Tab
|
||||
v-for="tab in tabs"
|
||||
:key="tab.value"
|
||||
class="text-sm py-1 px-2 font-inter transition-all active:scale-95"
|
||||
class="px-2 py-1 font-inter text-sm transition-all active:scale-95"
|
||||
:value="tab.value"
|
||||
>
|
||||
{{ tab.label() }}
|
||||
@@ -284,7 +284,10 @@ function handleTitleCancel() {
|
||||
:nodes="selectedNodes"
|
||||
:must-show-node-title="selectedGroups.length > 0"
|
||||
/>
|
||||
<TabInfo v-else-if="activeTab === 'info'" :nodes="selectedNodes" />
|
||||
<TabInfo
|
||||
v-else-if="activeTab === 'info'"
|
||||
:nodes="selectedNodes"
|
||||
/>
|
||||
<TabSettings
|
||||
v-else-if="activeTab === 'settings'"
|
||||
:nodes="flattedItems"
|
||||
|
||||
@@ -25,21 +25,21 @@ const tooltipConfig = computed(() => {
|
||||
<template>
|
||||
<div class="flex flex-col bg-comfy-menu-bg">
|
||||
<div
|
||||
class="sticky top-0 z-10 flex items-center justify-between backdrop-blur-xl bg-inherit"
|
||||
class="sticky top-0 z-10 flex items-center justify-between bg-inherit backdrop-blur-xl"
|
||||
>
|
||||
<button
|
||||
v-tooltip="tooltipConfig"
|
||||
type="button"
|
||||
:class="
|
||||
cn(
|
||||
'group min-h-12 bg-transparent border-0 outline-0 ring-0 w-full text-left flex items-center justify-between pl-4 pr-3',
|
||||
'group flex min-h-12 w-full items-center justify-between border-0 bg-transparent pr-3 pl-4 text-left ring-0 outline-0',
|
||||
!disabled && 'cursor-pointer'
|
||||
)
|
||||
"
|
||||
:disabled="disabled"
|
||||
@click="isCollapse = !isCollapse"
|
||||
>
|
||||
<span class="text-sm font-semibold line-clamp-2 flex-1">
|
||||
<span class="line-clamp-2 flex-1 text-sm font-semibold">
|
||||
<slot name="label">
|
||||
{{ label }}
|
||||
</slot>
|
||||
@@ -48,7 +48,7 @@ const tooltipConfig = computed(() => {
|
||||
<i
|
||||
:class="
|
||||
cn(
|
||||
'text-muted-foreground group-hover:text-base-foreground group-has-[.subbutton:hover]:text-muted-foreground group-focus:text-base-foreground icon-[lucide--chevron-up] size-4 transition-all',
|
||||
'icon-[lucide--chevron-up] size-4 text-muted-foreground transition-all group-hover:text-base-foreground group-focus:text-base-foreground group-has-[.subbutton:hover]:text-muted-foreground',
|
||||
isCollapse && '-rotate-180',
|
||||
disabled && 'opacity-0'
|
||||
)
|
||||
@@ -57,10 +57,16 @@ const tooltipConfig = computed(() => {
|
||||
</button>
|
||||
</div>
|
||||
<TransitionCollapse>
|
||||
<div v-if="isExpanded" class="pb-4">
|
||||
<div
|
||||
v-if="isExpanded"
|
||||
class="pb-4"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
<slot v-else-if="enableEmptyState && disabled" name="empty">
|
||||
<slot
|
||||
v-else-if="enableEmptyState && disabled"
|
||||
name="empty"
|
||||
>
|
||||
<div>
|
||||
{{ $t('g.empty') }}
|
||||
</div>
|
||||
|
||||
@@ -131,8 +131,8 @@ defineExpose({
|
||||
:tooltip
|
||||
>
|
||||
<template #label>
|
||||
<div class="flex items-center gap-2 flex-1 min-w-0">
|
||||
<span class="flex-1 flex items-center gap-2 min-w-0">
|
||||
<div class="flex min-w-0 flex-1 items-center gap-2">
|
||||
<span class="flex min-w-0 flex-1 items-center gap-2">
|
||||
<span class="truncate">
|
||||
<slot name="label">
|
||||
{{ displayLabel }}
|
||||
@@ -140,7 +140,7 @@ defineExpose({
|
||||
</span>
|
||||
<span
|
||||
v-if="parentGroup"
|
||||
class="text-xs text-muted-foreground truncate flex-1 text-right min-w-11"
|
||||
class="min-w-11 flex-1 truncate text-right text-xs text-muted-foreground"
|
||||
:title="parentGroup.title"
|
||||
>
|
||||
{{ parentGroup.title }}
|
||||
@@ -150,7 +150,7 @@ defineExpose({
|
||||
v-if="canShowLocateButton"
|
||||
variant="textonly"
|
||||
size="icon-sm"
|
||||
class="subbutton shrink-0 mr-3 size-8 cursor-pointer text-muted-foreground hover:text-base-foreground"
|
||||
class="subbutton mr-3 size-8 shrink-0 cursor-pointer text-muted-foreground hover:text-base-foreground"
|
||||
:title="t('rightSidePanel.locateNode')"
|
||||
:aria-label="t('rightSidePanel.locateNode')"
|
||||
@click.stop="handleLocateNode"
|
||||
@@ -160,11 +160,13 @@ defineExpose({
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #empty><slot name="empty" /></template>
|
||||
<template #empty>
|
||||
<slot name="empty" />
|
||||
</template>
|
||||
|
||||
<div
|
||||
ref="widgetsContainer"
|
||||
class="space-y-2 rounded-lg px-4 pt-1 relative"
|
||||
class="relative space-y-2 rounded-lg px-4 pt-1"
|
||||
>
|
||||
<TransitionGroup name="list-scale">
|
||||
<WidgetItem
|
||||
|
||||
@@ -111,7 +111,7 @@ onBeforeUnmount(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="px-4 pt-1 pb-4 flex gap-2 border-b border-interface-stroke">
|
||||
<div class="flex gap-2 border-b border-interface-stroke px-4 pt-1 pb-4">
|
||||
<FormSearchInput
|
||||
v-model="searchQuery"
|
||||
:searcher
|
||||
@@ -130,7 +130,7 @@ onBeforeUnmount(() => {
|
||||
@update:collapse="nextTick(setDraggableState)"
|
||||
>
|
||||
<template #empty>
|
||||
<div class="text-sm text-muted-foreground px-4 text-center py-10">
|
||||
<div class="px-4 py-10 text-center text-sm text-muted-foreground">
|
||||
{{
|
||||
isSearching
|
||||
? t('rightSidePanel.noneSearchDesc')
|
||||
|
||||
@@ -50,17 +50,21 @@ async function searcher(query: string) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="px-4 pt-1 pb-4 flex gap-2 border-b border-interface-stroke">
|
||||
<div class="flex gap-2 border-b border-interface-stroke px-4 pt-1 pb-4">
|
||||
<FormSearchInput
|
||||
v-model="searchQuery"
|
||||
:searcher
|
||||
:update-key="widgetsSectionDataList"
|
||||
/>
|
||||
</div>
|
||||
<TransitionGroup tag="div" name="list-scale" class="relative">
|
||||
<TransitionGroup
|
||||
tag="div"
|
||||
name="list-scale"
|
||||
class="relative"
|
||||
>
|
||||
<div
|
||||
v-if="isSearching && searchedWidgetsSectionDataList.length === 0"
|
||||
class="text-sm text-muted-foreground px-4 text-center pt-5 pb-15"
|
||||
class="px-4 pt-5 pb-15 text-center text-sm text-muted-foreground"
|
||||
>
|
||||
{{ $t('rightSidePanel.noneSearchDesc') }}
|
||||
</div>
|
||||
|
||||
@@ -59,17 +59,21 @@ const label = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="px-4 pt-1 pb-4 flex gap-2 border-b border-interface-stroke">
|
||||
<div class="flex gap-2 border-b border-interface-stroke px-4 pt-1 pb-4">
|
||||
<FormSearchInput
|
||||
v-model="searchQuery"
|
||||
:searcher
|
||||
:update-key="widgetsSectionDataList"
|
||||
/>
|
||||
</div>
|
||||
<TransitionGroup tag="div" name="list-scale" class="relative">
|
||||
<TransitionGroup
|
||||
tag="div"
|
||||
name="list-scale"
|
||||
class="relative"
|
||||
>
|
||||
<div
|
||||
v-if="searchedWidgetsSectionDataList.length === 0"
|
||||
class="text-sm text-muted-foreground px-4 py-10 text-center"
|
||||
class="px-4 py-10 text-center text-sm text-muted-foreground"
|
||||
>
|
||||
{{
|
||||
isSearching
|
||||
|
||||
@@ -202,7 +202,7 @@ const label = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="px-4 pt-1 pb-4 flex gap-2 border-b border-interface-stroke">
|
||||
<div class="flex gap-2 border-b border-interface-stroke px-4 pt-1 pb-4">
|
||||
<FormSearchInput
|
||||
v-model="searchQuery"
|
||||
:searcher
|
||||
@@ -226,7 +226,7 @@ const label = computed(() => {
|
||||
@update:collapse="nextTick(setDraggableState)"
|
||||
>
|
||||
<template #empty>
|
||||
<div class="text-sm text-muted-foreground px-4 text-center pt-5 pb-15">
|
||||
<div class="px-4 pt-5 pb-15 text-center text-sm text-muted-foreground">
|
||||
{{ t('rightSidePanel.noneSearchDesc') }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -99,7 +99,7 @@ function handleToggleFavorite() {
|
||||
|
||||
const buttonClasses = cn([
|
||||
'border-none bg-transparent',
|
||||
'w-full flex items-center gap-2 rounded px-3 py-2 text-sm',
|
||||
'flex w-full items-center gap-2 rounded px-3 py-2 text-sm',
|
||||
'cursor-pointer transition-all hover:bg-secondary-background-hover active:scale-95'
|
||||
])
|
||||
</script>
|
||||
@@ -107,7 +107,7 @@ const buttonClasses = cn([
|
||||
<template>
|
||||
<MoreButton
|
||||
is-vertical
|
||||
class="text-muted-foreground bg-transparent hover:text-base-foreground hover:bg-secondary-background-hover active:scale-95 transition-all"
|
||||
class="bg-transparent text-muted-foreground transition-all hover:bg-secondary-background-hover hover:text-base-foreground active:scale-95"
|
||||
>
|
||||
<template #default="{ close }">
|
||||
<button
|
||||
|
||||
@@ -106,9 +106,9 @@ const displayLabel = customRef((track, trigger) => {
|
||||
<div
|
||||
:class="
|
||||
cn(
|
||||
'widget-item col-span-full grid grid-cols-subgrid rounded-lg group',
|
||||
'widget-item group col-span-full grid grid-cols-subgrid rounded-lg',
|
||||
isDraggable &&
|
||||
'draggable-item !will-change-auto drag-handle cursor-grab bg-comfy-menu-bg [&.is-draggable]:cursor-grabbing outline-comfy-menu-bg [&.is-draggable]:outline-4 [&.is-draggable]:outline-offset-0 [&.is-draggable]:opacity-70'
|
||||
'draggable-item drag-handle cursor-grab bg-comfy-menu-bg outline-comfy-menu-bg !will-change-auto [&.is-draggable]:cursor-grabbing [&.is-draggable]:opacity-70 [&.is-draggable]:outline-4 [&.is-draggable]:outline-offset-0'
|
||||
)
|
||||
"
|
||||
>
|
||||
@@ -116,7 +116,7 @@ const displayLabel = customRef((track, trigger) => {
|
||||
<div
|
||||
:class="
|
||||
cn(
|
||||
'min-h-8 flex items-center justify-between gap-1 mb-1.5 min-w-0',
|
||||
'mb-1.5 flex min-h-8 min-w-0 items-center justify-between gap-1',
|
||||
isDraggable && 'pointer-events-none'
|
||||
)
|
||||
"
|
||||
@@ -126,7 +126,7 @@ const displayLabel = customRef((track, trigger) => {
|
||||
:model-value="displayLabel"
|
||||
:is-editing="isEditing"
|
||||
:input-attrs="{ placeholder: widget.name }"
|
||||
class="text-sm leading-8 p-0 m-0 truncate pointer-events-auto cursor-text"
|
||||
class="pointer-events-auto m-0 cursor-text truncate p-0 text-sm leading-8"
|
||||
@edit="displayLabel = $event"
|
||||
@cancel="isEditing = false"
|
||||
@click="isEditing = true"
|
||||
@@ -134,11 +134,11 @@ const displayLabel = customRef((track, trigger) => {
|
||||
|
||||
<span
|
||||
v-if="(showNodeName || hasParents) && sourceNodeName"
|
||||
class="text-xs text-muted-foreground flex-1 p-0 my-0 mx-1 truncate text-right min-w-10"
|
||||
class="mx-1 my-0 min-w-10 flex-1 truncate p-0 text-right text-xs text-muted-foreground"
|
||||
>
|
||||
{{ sourceNodeName }}
|
||||
</span>
|
||||
<div class="flex items-center gap-1 shrink-0 pointer-events-auto">
|
||||
<div class="pointer-events-auto flex shrink-0 items-center gap-1">
|
||||
<WidgetActions
|
||||
v-model:label="displayLabel"
|
||||
:widget="widget"
|
||||
@@ -152,12 +152,12 @@ const displayLabel = customRef((track, trigger) => {
|
||||
<div
|
||||
v-if="
|
||||
!hiddenFavoriteIndicator &&
|
||||
favoritedWidgetsStore.isFavorited(favoriteNode, widget.name)
|
||||
favoritedWidgetsStore.isFavorited(favoriteNode, widget.name)
|
||||
"
|
||||
class="relative z-2 pointer-events-none"
|
||||
class="pointer-events-none relative z-2"
|
||||
>
|
||||
<i
|
||||
class="absolute -right-1 -top-1 pi pi-star-fill text-xs text-muted-foreground pointer-events-none"
|
||||
class="pi pi-star-fill pointer-events-none absolute -top-1 -right-1 text-xs text-muted-foreground"
|
||||
/>
|
||||
</div>
|
||||
<!-- widget content -->
|
||||
@@ -173,7 +173,7 @@ const displayLabel = customRef((track, trigger) => {
|
||||
<div
|
||||
:class="
|
||||
cn(
|
||||
'pointer-events-none mt-1.5 mx-auto max-w-40 w-1/2 h-1 rounded-lg bg-transparent transition-colors duration-150',
|
||||
'pointer-events-none mx-auto mt-1.5 h-1 w-1/2 max-w-40 rounded-lg bg-transparent transition-colors duration-150',
|
||||
'group-hover:bg-interface-stroke group-[.is-draggable]:bg-component-node-widget-background-highlighted',
|
||||
!isDraggable && 'opacity-0'
|
||||
)
|
||||
|
||||
@@ -18,14 +18,14 @@ defineProps<{
|
||||
v-tooltip.left="
|
||||
tooltip
|
||||
? {
|
||||
value: tooltip,
|
||||
showDelay: 300
|
||||
}
|
||||
value: tooltip,
|
||||
showDelay: 300
|
||||
}
|
||||
: null
|
||||
"
|
||||
:class="
|
||||
cn(
|
||||
'text-sm text-muted-foreground truncate',
|
||||
'truncate text-sm text-muted-foreground',
|
||||
tooltip ? 'cursor-help' : '',
|
||||
singleline ? 'flex-1' : ''
|
||||
)
|
||||
|
||||
@@ -107,14 +107,14 @@ const nodeColor = computed<NodeColorOption['name'] | null>({
|
||||
<template>
|
||||
<LayoutField :label="t('rightSidePanel.color')">
|
||||
<div
|
||||
class="bg-secondary-background border-none rounded-lg p-1 grid grid-cols-5 gap-1 justify-items-center"
|
||||
class="grid grid-cols-5 justify-items-center gap-1 rounded-lg border-none bg-secondary-background p-1"
|
||||
>
|
||||
<button
|
||||
v-for="option of colorOptions"
|
||||
:key="option.name"
|
||||
:class="
|
||||
cn(
|
||||
'size-8 rounded-lg bg-transparent border-0 outline-0 ring-0 text-left flex justify-center items-center cursor-pointer',
|
||||
'flex size-8 cursor-pointer items-center justify-center rounded-lg border-0 bg-transparent text-left ring-0 outline-0',
|
||||
option.name === nodeColor
|
||||
? 'bg-interface-menu-component-surface-selected'
|
||||
: 'hover:bg-interface-menu-component-surface-selected'
|
||||
|
||||
@@ -125,7 +125,7 @@ function openFullSettings() {
|
||||
<LayoutField :label="t('rightSidePanel.globalSettings.gridSpacing')">
|
||||
<div
|
||||
:class="
|
||||
cn(WidgetInputBaseClass, 'flex items-center gap-2 pl-3 pr-2')
|
||||
cn(WidgetInputBaseClass, 'flex items-center gap-2 pr-2 pl-3')
|
||||
"
|
||||
>
|
||||
<Slider
|
||||
@@ -189,7 +189,7 @@ function openFullSettings() {
|
||||
|
||||
<!-- View all settings button -->
|
||||
<div
|
||||
class="flex items-center justify-center p-4 border-b border-interface-stroke"
|
||||
class="flex items-center justify-center border-b border-interface-stroke p-4"
|
||||
>
|
||||
<Button
|
||||
variant="muted-textonly"
|
||||
|
||||
@@ -239,8 +239,11 @@ onBeforeUnmount(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="activeNode" class="subgraph-edit-section flex h-full flex-col">
|
||||
<div class="px-4 pb-4 pt-1 flex gap-2 border-b border-interface-stroke">
|
||||
<div
|
||||
v-if="activeNode"
|
||||
class="subgraph-edit-section flex h-full flex-col"
|
||||
>
|
||||
<div class="flex gap-2 border-b border-interface-stroke px-4 pt-1 pb-4">
|
||||
<FormSearchInput v-model="searchQuery" />
|
||||
</div>
|
||||
|
||||
@@ -248,10 +251,10 @@ onBeforeUnmount(() => {
|
||||
<div
|
||||
v-if="
|
||||
searchQuery &&
|
||||
filteredActive.length === 0 &&
|
||||
filteredCandidates.length === 0
|
||||
filteredActive.length === 0 &&
|
||||
filteredCandidates.length === 0
|
||||
"
|
||||
class="text-sm text-muted-foreground px-4 py-10 text-center"
|
||||
class="px-4 py-10 text-center text-sm text-muted-foreground"
|
||||
>
|
||||
{{ $t('rightSidePanel.noneSearchDesc') }}
|
||||
</div>
|
||||
@@ -261,19 +264,21 @@ onBeforeUnmount(() => {
|
||||
class="flex flex-col border-b border-interface-stroke"
|
||||
>
|
||||
<div
|
||||
class="sticky top-0 z-10 flex items-center justify-between backdrop-blur-xl min-h-12 px-4"
|
||||
class="sticky top-0 z-10 flex min-h-12 items-center justify-between px-4 backdrop-blur-xl"
|
||||
>
|
||||
<div class="text-sm font-semibold uppercase line-clamp-1">
|
||||
<div class="line-clamp-1 text-sm font-semibold uppercase">
|
||||
{{ $t('subgraphStore.shown') }}
|
||||
</div>
|
||||
<a
|
||||
class="cursor-pointer text-right text-xs font-normal text-text-secondary hover:text-azure-600 whitespace-nowrap"
|
||||
class="cursor-pointer text-right text-xs font-normal whitespace-nowrap text-text-secondary hover:text-azure-600"
|
||||
@click.stop="hideAll"
|
||||
>
|
||||
{{ $t('subgraphStore.hideAll') }}</a
|
||||
>
|
||||
{{ $t('subgraphStore.hideAll') }}</a>
|
||||
</div>
|
||||
<div ref="draggableItems" class="pb-2 px-2 space-y-0.5 mt-0.5">
|
||||
<div
|
||||
ref="draggableItems"
|
||||
class="mt-0.5 space-y-0.5 px-2 pb-2"
|
||||
>
|
||||
<SubgraphNodeWidget
|
||||
v-for="[node, widget] in filteredActive"
|
||||
:key="toKey([node, widget])"
|
||||
@@ -293,19 +298,18 @@ onBeforeUnmount(() => {
|
||||
class="flex flex-col border-b border-interface-stroke"
|
||||
>
|
||||
<div
|
||||
class="sticky top-0 z-10 flex items-center justify-between backdrop-blur-xl min-h-12 px-4"
|
||||
class="sticky top-0 z-10 flex min-h-12 items-center justify-between px-4 backdrop-blur-xl"
|
||||
>
|
||||
<div class="text-sm font-semibold uppercase line-clamp-1">
|
||||
<div class="line-clamp-1 text-sm font-semibold uppercase">
|
||||
{{ $t('subgraphStore.hidden') }}
|
||||
</div>
|
||||
<a
|
||||
class="cursor-pointer text-right text-xs font-normal text-text-secondary hover:text-azure-600 whitespace-nowrap"
|
||||
class="cursor-pointer text-right text-xs font-normal whitespace-nowrap text-text-secondary hover:text-azure-600"
|
||||
@click.stop="showAll"
|
||||
>
|
||||
{{ $t('subgraphStore.showAll') }}</a
|
||||
>
|
||||
{{ $t('subgraphStore.showAll') }}</a>
|
||||
</div>
|
||||
<div class="pb-2 px-2 space-y-0.5 mt-0.5">
|
||||
<div class="mt-0.5 space-y-0.5 px-2 pb-2">
|
||||
<SubgraphNodeWidget
|
||||
v-for="[node, widget] in filteredCandidates"
|
||||
:key="toKey([node, widget])"
|
||||
|
||||
@@ -27,19 +27,21 @@ function getIcon() {
|
||||
<div
|
||||
:class="
|
||||
cn(
|
||||
'flex py-1 px-2 break-all rounded items-center gap-1',
|
||||
'flex items-center gap-1 rounded px-2 py-1 break-all',
|
||||
'bg-node-component-surface',
|
||||
props.isDraggable &&
|
||||
'draggable-item drag-handle cursor-grab [&.is-draggable]:cursor-grabbing hover:ring-1 ring-accent-background',
|
||||
'draggable-item drag-handle cursor-grab ring-accent-background hover:ring-1 [&.is-draggable]:cursor-grabbing',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
>
|
||||
<div class="pointer-events-none flex-1">
|
||||
<div class="text-xs text-text-secondary line-clamp-1">
|
||||
<div class="line-clamp-1 text-xs text-text-secondary">
|
||||
{{ nodeTitle }}
|
||||
</div>
|
||||
<div class="text-sm line-clamp-1 leading-8">{{ widgetName }}</div>
|
||||
<div class="line-clamp-1 text-sm leading-8">
|
||||
{{ widgetName }}
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="muted-textonly"
|
||||
@@ -51,7 +53,7 @@ function getIcon() {
|
||||
</Button>
|
||||
<div
|
||||
v-if="isDraggable"
|
||||
class="size-4 pointer-events-none icon-[lucide--grip-vertical]"
|
||||
class="pointer-events-none icon-[lucide--grip-vertical] size-4"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -17,7 +17,12 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="_footer">
|
||||
<Button type="button" @click="submit">{{ $t('g.add') }}</Button>
|
||||
<Button
|
||||
type="button"
|
||||
@click="submit"
|
||||
>
|
||||
{{ $t('g.add') }}
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -76,6 +81,6 @@ const submit = () => {
|
||||
}
|
||||
|
||||
._footer {
|
||||
@apply flex flex-col pt-4 items-end;
|
||||
@apply flex flex-col items-end pt-4;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useCommandStore } from '@/stores/commandStore'
|
||||
const canvasStore = useCanvasStore()
|
||||
</script>
|
||||
<template>
|
||||
<div class="p-1 bg-secondary-background rounded-lg w-10">
|
||||
<div class="w-10 rounded-lg bg-secondary-background p-1">
|
||||
<Button
|
||||
size="icon"
|
||||
:title="t('linearMode.linearMode')"
|
||||
|
||||
@@ -14,10 +14,13 @@
|
||||
:class="
|
||||
isOverflowing
|
||||
? 'side-tool-bar-container overflow-y-auto'
|
||||
: 'flex flex-col h-full'
|
||||
: 'flex h-full flex-col'
|
||||
"
|
||||
>
|
||||
<div ref="topToolbarRef" :class="groupClasses">
|
||||
<div
|
||||
ref="topToolbarRef"
|
||||
:class="groupClasses"
|
||||
>
|
||||
<ComfyMenuButton />
|
||||
<SidebarIcon
|
||||
v-for="tab in tabs"
|
||||
@@ -35,12 +38,19 @@
|
||||
<SidebarTemplatesButton />
|
||||
</div>
|
||||
|
||||
<div ref="bottomToolbarRef" class="mt-auto" :class="groupClasses">
|
||||
<div
|
||||
ref="bottomToolbarRef"
|
||||
class="mt-auto"
|
||||
:class="groupClasses"
|
||||
>
|
||||
<SidebarLogoutIcon
|
||||
v-if="userStore.isMultiUserServer"
|
||||
:is-small="isSmall"
|
||||
/>
|
||||
<SidebarHelpCenterIcon v-if="!isIntegratedTabBar" :is-small="isSmall" />
|
||||
<SidebarHelpCenterIcon
|
||||
v-if="!isIntegratedTabBar"
|
||||
:is-small="isSmall"
|
||||
/>
|
||||
<SidebarBottomPanelToggleButton :is-small="isSmall" />
|
||||
<SidebarShortcutsToggleButton :is-small="isSmall" />
|
||||
<SidebarSettingsButton :is-small="isSmall" />
|
||||
@@ -158,8 +168,8 @@ const getTabTooltipSuffix = (tab: SidebarTabExtension) => {
|
||||
const isOverflowing = ref(false)
|
||||
const groupClasses = computed(() =>
|
||||
cn(
|
||||
'sidebar-item-group flex flex-col items-center overflow-hidden flex-shrink-0',
|
||||
!isConnected.value && 'rounded-lg shadow-interface pointer-events-auto'
|
||||
'sidebar-item-group flex flex-shrink-0 flex-col items-center overflow-hidden',
|
||||
!isConnected.value && 'pointer-events-auto rounded-lg shadow-interface'
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -17,12 +17,19 @@
|
||||
>
|
||||
<div class="side-bar-button-content">
|
||||
<slot name="icon">
|
||||
<OverlayBadge v-if="shouldShowBadge" :value="overlayValue">
|
||||
<OverlayBadge
|
||||
v-if="shouldShowBadge"
|
||||
:value="overlayValue"
|
||||
>
|
||||
<i
|
||||
v-if="typeof icon === 'string'"
|
||||
:class="icon + ' side-bar-button-icon'"
|
||||
/>
|
||||
<component :is="icon" v-else class="side-bar-button-icon" />
|
||||
<component
|
||||
:is="icon"
|
||||
v-else
|
||||
class="side-bar-button-icon"
|
||||
/>
|
||||
</OverlayBadge>
|
||||
<i
|
||||
v-else-if="typeof icon === 'string'"
|
||||
@@ -34,7 +41,10 @@
|
||||
class="side-bar-button-icon"
|
||||
/>
|
||||
</slot>
|
||||
<span v-if="label && !isSmall" class="side-bar-button-label">{{
|
||||
<span
|
||||
v-if="label && !isSmall"
|
||||
class="side-bar-button-label"
|
||||
>{{
|
||||
t(label)
|
||||
}}</span>
|
||||
</div>
|
||||
@@ -117,7 +127,7 @@ const computedTooltip = computed(() => t(tooltip) + tooltipSuffix)
|
||||
}
|
||||
|
||||
.side-bar-button-label {
|
||||
@apply text-[10px] text-center;
|
||||
@apply text-center text-[10px];
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,10 @@
|
||||
@mouseleave="onJobLeave(job.id)"
|
||||
@click.stop
|
||||
>
|
||||
<template v-if="hoveredJobId === job.id" #actions>
|
||||
<template
|
||||
v-if="hoveredJobId === job.id"
|
||||
#actions
|
||||
>
|
||||
<Button
|
||||
v-if="canCancelJob"
|
||||
:variant="cancelAction.variant"
|
||||
@@ -33,7 +36,10 @@
|
||||
:aria-label="cancelAction.label"
|
||||
@click.stop="runCancelJob()"
|
||||
>
|
||||
<i :class="cancelAction.icon" class="size-4" />
|
||||
<i
|
||||
:class="cancelAction.icon"
|
||||
class="size-4"
|
||||
/>
|
||||
</Button>
|
||||
</template>
|
||||
</AssetsListItem>
|
||||
@@ -44,7 +50,7 @@
|
||||
:class="cn('px-2', activeJobItems.length && 'mt-2')"
|
||||
>
|
||||
<div
|
||||
class="flex items-center py-2 text-sm font-normal leading-normal text-muted-foreground font-inter"
|
||||
class="flex items-center py-2 font-inter text-sm leading-normal font-normal text-muted-foreground"
|
||||
>
|
||||
{{ t('sideToolbar.generatedAssetsHeader') }}
|
||||
</div>
|
||||
@@ -79,7 +85,10 @@
|
||||
@contextmenu.prevent.stop="emit('context-menu', $event, item.asset)"
|
||||
@click.stop="emit('select-asset', item.asset)"
|
||||
>
|
||||
<template v-if="hoveredAssetId === item.asset.id" #actions>
|
||||
<template
|
||||
v-if="hoveredAssetId === item.asset.id"
|
||||
#actions
|
||||
>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
@@ -194,7 +203,7 @@ function getAssetCardClass(selected: boolean): string {
|
||||
'w-full text-text-primary transition-colors hover:bg-secondary-background-hover',
|
||||
'cursor-pointer',
|
||||
selected &&
|
||||
'bg-secondary-background-hover ring-1 ring-inset ring-modal-card-border-highlighted'
|
||||
'bg-secondary-background-hover ring-1 ring-modal-card-border-highlighted ring-inset'
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
role="button"
|
||||
@click="copyJobId"
|
||||
>
|
||||
<i class="icon-[lucide--copy] text-sm"></i>
|
||||
<i class="icon-[lucide--copy] text-sm" />
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
@@ -25,19 +25,39 @@
|
||||
</template>
|
||||
<template #tool-buttons>
|
||||
<!-- Normal Tab View -->
|
||||
<TabList v-if="!isInFolderView" v-model="activeTab">
|
||||
<Tab class="font-inter" value="output">{{
|
||||
$t('sideToolbar.labels.generated')
|
||||
}}</Tab>
|
||||
<Tab class="font-inter" value="input">{{
|
||||
$t('sideToolbar.labels.imported')
|
||||
}}</Tab>
|
||||
<TabList
|
||||
v-if="!isInFolderView"
|
||||
v-model="activeTab"
|
||||
>
|
||||
<Tab
|
||||
class="font-inter"
|
||||
value="output"
|
||||
>
|
||||
{{
|
||||
$t('sideToolbar.labels.generated')
|
||||
}}
|
||||
</Tab>
|
||||
<Tab
|
||||
class="font-inter"
|
||||
value="input"
|
||||
>
|
||||
{{
|
||||
$t('sideToolbar.labels.imported')
|
||||
}}
|
||||
</Tab>
|
||||
</TabList>
|
||||
</template>
|
||||
<template #header>
|
||||
<!-- Job Detail View Header -->
|
||||
<div v-if="isInFolderView" class="px-2 2xl:px-4">
|
||||
<Button variant="secondary" size="lg" @click="exitFolderView">
|
||||
<div
|
||||
v-if="isInFolderView"
|
||||
class="px-2 2xl:px-4"
|
||||
>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="lg"
|
||||
@click="exitFolderView"
|
||||
>
|
||||
<i class="icon-[lucide--arrow-left] size-4" />
|
||||
<span>{{ $t('sideToolbar.backToAssets') }}</span>
|
||||
</Button>
|
||||
@@ -49,7 +69,7 @@
|
||||
v-model:sort-by="sortBy"
|
||||
v-model:view-mode="viewMode"
|
||||
v-model:media-type-filters="mediaTypeFilters"
|
||||
class="pb-1 px-2 2xl:px-4"
|
||||
class="px-2 pb-1 2xl:px-4"
|
||||
:show-generation-time-sort="activeTab === 'output'"
|
||||
/>
|
||||
<div
|
||||
@@ -76,7 +96,11 @@
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Divider v-else type="dashed" class="my-2" />
|
||||
<Divider
|
||||
v-else
|
||||
type="dashed"
|
||||
class="my-2"
|
||||
/>
|
||||
</template>
|
||||
<template #body>
|
||||
<div v-if="showLoadingState">
|
||||
@@ -95,7 +119,11 @@
|
||||
:message="$t('sideToolbar.noFilesFoundMessage')"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="relative size-full" @click="handleEmptySpaceClick">
|
||||
<div
|
||||
v-else
|
||||
class="relative size-full"
|
||||
@click="handleEmptySpaceClick"
|
||||
>
|
||||
<AssetsSidebarListView
|
||||
v-if="isListView"
|
||||
:assets="displayAssets"
|
||||
@@ -134,10 +162,13 @@
|
||||
<div
|
||||
v-if="hasSelection"
|
||||
ref="footerRef"
|
||||
class="flex gap-1 h-18 w-full items-center justify-between"
|
||||
class="flex h-18 w-full items-center justify-between gap-1"
|
||||
>
|
||||
<div class="flex-1 pl-4">
|
||||
<div ref="selectionCountButtonRef" class="inline-flex w-48">
|
||||
<div
|
||||
ref="selectionCountButtonRef"
|
||||
class="inline-flex w-48"
|
||||
>
|
||||
<Button
|
||||
variant="secondary"
|
||||
:class="cn(isCompact && 'text-left')"
|
||||
@@ -147,13 +178,13 @@
|
||||
isHoveringSelectionCount
|
||||
? $t('mediaAsset.selection.deselectAll')
|
||||
: $t('mediaAsset.selection.selectedCount', {
|
||||
count: totalOutputCount
|
||||
})
|
||||
count: totalOutputCount
|
||||
})
|
||||
}}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex shrink gap-2 pr-4 items-center-safe justify-end-safe">
|
||||
<div class="flex shrink items-center-safe justify-end-safe gap-2 pr-4">
|
||||
<template v-if="isCompact">
|
||||
<!-- Compact mode: Icon only -->
|
||||
<Button
|
||||
@@ -163,7 +194,10 @@
|
||||
>
|
||||
<i class="icon-[lucide--trash-2] size-4" />
|
||||
</Button>
|
||||
<Button size="icon" @click="handleDownloadSelected">
|
||||
<Button
|
||||
size="icon"
|
||||
@click="handleDownloadSelected"
|
||||
>
|
||||
<i class="icon-[lucide--download] size-4" />
|
||||
</Button>
|
||||
</template>
|
||||
@@ -177,7 +211,10 @@
|
||||
<span>{{ $t('mediaAsset.selection.deleteSelected') }}</span>
|
||||
<i class="icon-[lucide--trash-2] size-4" />
|
||||
</Button>
|
||||
<Button variant="secondary" @click="handleDownloadSelected">
|
||||
<Button
|
||||
variant="secondary"
|
||||
@click="handleDownloadSelected"
|
||||
>
|
||||
<span>{{ $t('mediaAsset.selection.downloadSelected') }}</span>
|
||||
<i class="icon-[lucide--download] size-4" />
|
||||
</Button>
|
||||
|
||||
@@ -5,17 +5,20 @@
|
||||
>
|
||||
<div class="comfy-vue-side-bar-header flex flex-col gap-2">
|
||||
<Toolbar
|
||||
class="min-h-16 bg-transparent rounded-none border-x-0 border-t-0 px-2 2xl:px-4"
|
||||
class="min-h-16 rounded-none border-x-0 border-t-0 bg-transparent px-2 2xl:px-4"
|
||||
>
|
||||
<template #start>
|
||||
<span class="truncate font-bold" :title="props.title">
|
||||
<span
|
||||
class="truncate font-bold"
|
||||
:title="props.title"
|
||||
>
|
||||
{{ props.title }}
|
||||
</span>
|
||||
<slot name="alt-title" />
|
||||
</template>
|
||||
<template #end>
|
||||
<div
|
||||
class="touch:w-auto touch:opacity-100 flex flex-row overflow-hidden transition-all duration-200 motion-safe:w-0 motion-safe:opacity-0 motion-safe:group-focus-within/sidebar-tab:w-auto motion-safe:group-focus-within/sidebar-tab:opacity-100 motion-safe:group-hover/sidebar-tab:w-auto motion-safe:group-hover/sidebar-tab:opacity-100"
|
||||
class="flex flex-row overflow-hidden transition-all duration-200 motion-safe:w-0 motion-safe:opacity-0 motion-safe:group-focus-within/sidebar-tab:w-auto motion-safe:group-focus-within/sidebar-tab:opacity-100 motion-safe:group-hover/sidebar-tab:w-auto motion-safe:group-hover/sidebar-tab:opacity-100 touch:w-auto touch:opacity-100"
|
||||
>
|
||||
<slot name="tool-buttons" />
|
||||
</div>
|
||||
|
||||
@@ -32,12 +32,12 @@ const isActive = computed(() => currentValue?.value === value)
|
||||
const tabClasses = computed(() => {
|
||||
return cn(
|
||||
// Base styles from TextButton
|
||||
'flex items-center justify-center shrink-0',
|
||||
'px-2.5 py-2 text-sm rounded-lg cursor-pointer transition-all duration-200',
|
||||
'outline-hidden border-none',
|
||||
'flex shrink-0 items-center justify-center',
|
||||
'cursor-pointer rounded-lg px-2.5 py-2 text-sm transition-all duration-200',
|
||||
'border-none outline-hidden',
|
||||
// State styles with semantic tokens
|
||||
isActive.value
|
||||
? 'bg-interface-menu-component-surface-hovered text-text-primary text-bold'
|
||||
? 'text-bold bg-interface-menu-component-surface-hovered text-text-primary'
|
||||
: 'bg-transparent text-text-secondary hover:bg-button-hover-surface focus:bg-button-hover-surface'
|
||||
)
|
||||
})
|
||||
|
||||
@@ -11,14 +11,20 @@
|
||||
<div
|
||||
:class="
|
||||
cn(
|
||||
'flex items-center gap-1 rounded-full hover:bg-interface-button-hover-surface justify-center',
|
||||
compact && 'size-full aspect-square'
|
||||
'flex items-center justify-center gap-1 rounded-full hover:bg-interface-button-hover-surface',
|
||||
compact && 'aspect-square size-full'
|
||||
)
|
||||
"
|
||||
>
|
||||
<UserAvatar :photo-url="photoURL" :class="compact && 'size-full'" />
|
||||
<UserAvatar
|
||||
:photo-url="photoURL"
|
||||
:class="compact && 'size-full'"
|
||||
/>
|
||||
|
||||
<i v-if="showArrow" class="icon-[lucide--chevron-down] size-3 px-1" />
|
||||
<i
|
||||
v-if="showArrow"
|
||||
class="icon-[lucide--chevron-down] size-3 px-1"
|
||||
/>
|
||||
</div>
|
||||
</Button>
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<!-- A popover that shows current user information and actions -->
|
||||
<template>
|
||||
<div
|
||||
class="current-user-popover w-80 -m-3 p-2 rounded-lg border border-border-default bg-base-background shadow-[1px_1px_8px_0_rgba(0,0,0,0.4)]"
|
||||
class="current-user-popover -m-3 w-80 rounded-lg border border-border-default bg-base-background p-2 shadow-[1px_1px_8px_0_rgba(0,0,0,0.4)]"
|
||||
>
|
||||
<!-- User Info Section -->
|
||||
<div class="flex flex-col items-center px-0 py-3 mb-4">
|
||||
<div class="mb-4 flex flex-col items-center px-0 py-3">
|
||||
<UserAvatar
|
||||
class="mb-1"
|
||||
:photo-url="userPhotoUrl"
|
||||
@@ -18,32 +18,41 @@
|
||||
<h3 class="my-0 mb-1 truncate text-base font-bold text-base-foreground">
|
||||
{{ userDisplayName || $t('g.user') }}
|
||||
</h3>
|
||||
<p v-if="userEmail" class="my-0 truncate text-sm text-muted">
|
||||
<p
|
||||
v-if="userEmail"
|
||||
class="my-0 truncate text-sm text-muted"
|
||||
>
|
||||
{{ userEmail }}
|
||||
</p>
|
||||
<span
|
||||
v-if="subscriptionTierName"
|
||||
class="my-0 text-xs text-foreground bg-secondary-background-hover rounded-full uppercase px-2 py-0.5 font-bold mt-2"
|
||||
class="text-foreground my-0 mt-2 rounded-full bg-secondary-background-hover px-2 py-0.5 text-xs font-bold uppercase"
|
||||
>
|
||||
{{ subscriptionTierName }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Credits Section -->
|
||||
<div v-if="isActiveSubscription" class="flex items-center gap-2 px-4 py-2">
|
||||
<i class="icon-[lucide--component] text-amber-400 text-sm" />
|
||||
<div
|
||||
v-if="isActiveSubscription"
|
||||
class="flex items-center gap-2 px-4 py-2"
|
||||
>
|
||||
<i class="icon-[lucide--component] text-sm text-amber-400" />
|
||||
<Skeleton
|
||||
v-if="authStore.isFetchingBalance"
|
||||
width="4rem"
|
||||
height="1.25rem"
|
||||
class="w-full"
|
||||
/>
|
||||
<span v-else class="text-base font-semibold text-base-foreground">{{
|
||||
<span
|
||||
v-else
|
||||
class="text-base font-semibold text-base-foreground"
|
||||
>{{
|
||||
formattedBalance
|
||||
}}</span>
|
||||
<i
|
||||
v-tooltip="{ value: $t('credits.unified.tooltip'), showDelay: 300 }"
|
||||
class="icon-[lucide--circle-help] cursor-help text-base text-muted-foreground mr-auto"
|
||||
class="mr-auto icon-[lucide--circle-help] cursor-help text-base text-muted-foreground"
|
||||
/>
|
||||
<Button
|
||||
variant="secondary"
|
||||
@@ -56,7 +65,10 @@
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div v-else class="flex justify-center px-4">
|
||||
<div
|
||||
v-else
|
||||
class="flex justify-center px-4"
|
||||
>
|
||||
<SubscribeButton
|
||||
:fluid="false"
|
||||
:label="$t('subscription.subscribeToComfyCloud')"
|
||||
@@ -66,32 +78,32 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Divider class="my-2 mx-0" />
|
||||
<Divider class="mx-0 my-2" />
|
||||
|
||||
<div
|
||||
v-if="isActiveSubscription"
|
||||
class="flex items-center gap-2 px-4 py-2 cursor-pointer hover:bg-secondary-background-hover"
|
||||
class="flex cursor-pointer items-center gap-2 px-4 py-2 hover:bg-secondary-background-hover"
|
||||
data-testid="partner-nodes-menu-item"
|
||||
@click="handleOpenPartnerNodesInfo"
|
||||
>
|
||||
<i class="icon-[lucide--tag] text-muted-foreground text-sm" />
|
||||
<span class="text-sm text-base-foreground flex-1">{{
|
||||
<i class="icon-[lucide--tag] text-sm text-muted-foreground" />
|
||||
<span class="flex-1 text-sm text-base-foreground">{{
|
||||
$t('subscription.partnerNodesCredits')
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex items-center gap-2 px-4 py-2 cursor-pointer hover:bg-secondary-background-hover"
|
||||
class="flex cursor-pointer items-center gap-2 px-4 py-2 hover:bg-secondary-background-hover"
|
||||
data-testid="plans-pricing-menu-item"
|
||||
@click="handleOpenPlansAndPricing"
|
||||
>
|
||||
<i class="icon-[lucide--receipt-text] text-muted-foreground text-sm" />
|
||||
<span class="text-sm text-base-foreground flex-1">{{
|
||||
<i class="icon-[lucide--receipt-text] text-sm text-muted-foreground" />
|
||||
<span class="flex-1 text-sm text-base-foreground">{{
|
||||
$t('subscription.plansAndPricing')
|
||||
}}</span>
|
||||
<span
|
||||
v-if="canUpgrade"
|
||||
class="text-xs font-bold text-base-background bg-base-foreground px-1.5 py-0.5 rounded-full"
|
||||
class="rounded-full bg-base-foreground px-1.5 py-0.5 text-xs font-bold text-base-background"
|
||||
>
|
||||
{{ $t('subscription.upgrade') }}
|
||||
</span>
|
||||
@@ -99,36 +111,36 @@
|
||||
|
||||
<div
|
||||
v-if="isActiveSubscription"
|
||||
class="flex items-center gap-2 px-4 py-2 cursor-pointer hover:bg-secondary-background-hover"
|
||||
class="flex cursor-pointer items-center gap-2 px-4 py-2 hover:bg-secondary-background-hover"
|
||||
data-testid="manage-plan-menu-item"
|
||||
@click="handleOpenPlanAndCreditsSettings"
|
||||
>
|
||||
<i class="icon-[lucide--file-text] text-muted-foreground text-sm" />
|
||||
<span class="text-sm text-base-foreground flex-1">{{
|
||||
<i class="icon-[lucide--file-text] text-sm text-muted-foreground" />
|
||||
<span class="flex-1 text-sm text-base-foreground">{{
|
||||
$t('subscription.managePlan')
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex items-center gap-2 px-4 py-2 cursor-pointer hover:bg-secondary-background-hover"
|
||||
class="flex cursor-pointer items-center gap-2 px-4 py-2 hover:bg-secondary-background-hover"
|
||||
data-testid="user-settings-menu-item"
|
||||
@click="handleOpenUserSettings"
|
||||
>
|
||||
<i class="icon-[lucide--settings-2] text-muted-foreground text-sm" />
|
||||
<span class="text-sm text-base-foreground flex-1">{{
|
||||
<i class="icon-[lucide--settings-2] text-sm text-muted-foreground" />
|
||||
<span class="flex-1 text-sm text-base-foreground">{{
|
||||
$t('userSettings.accountSettings')
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<Divider class="my-2 mx-0" />
|
||||
<Divider class="mx-0 my-2" />
|
||||
|
||||
<div
|
||||
class="flex items-center gap-2 px-4 py-2 cursor-pointer hover:bg-secondary-background-hover"
|
||||
class="flex cursor-pointer items-center gap-2 px-4 py-2 hover:bg-secondary-background-hover"
|
||||
data-testid="logout-menu-item"
|
||||
@click="handleLogout"
|
||||
>
|
||||
<i class="icon-[lucide--log-out] text-muted-foreground text-sm" />
|
||||
<span class="text-sm text-base-foreground flex-1">{{
|
||||
<i class="icon-[lucide--log-out] text-sm text-muted-foreground" />
|
||||
<span class="flex-1 text-sm text-base-foreground">{{
|
||||
$t('auth.signOut.signOut')
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
v-if="!isLoggedIn"
|
||||
variant="textonly"
|
||||
size="icon"
|
||||
:class="cn('group rounded-full text-base-foreground p-0', className)"
|
||||
:class="cn('group rounded-full p-0 text-base-foreground', className)"
|
||||
:aria-label="t('g.login')"
|
||||
@click="handleSignIn()"
|
||||
@mouseenter="showPopover"
|
||||
@@ -22,13 +22,14 @@
|
||||
@mouseover="cancelHidePopover"
|
||||
>
|
||||
<div>
|
||||
<div class="mb-1">{{ t('auth.loginButton.tooltipHelp') }}</div>
|
||||
<div class="mb-1">
|
||||
{{ t('auth.loginButton.tooltipHelp') }}
|
||||
</div>
|
||||
<a
|
||||
:href="apiNodesOverviewUrl"
|
||||
target="_blank"
|
||||
class="text-neutral-500 hover:text-primary"
|
||||
>{{ t('auth.loginButton.tooltipLearnMore') }}</a
|
||||
>
|
||||
>{{ t('auth.loginButton.tooltipLearnMore') }}</a>
|
||||
</div>
|
||||
</Popover>
|
||||
</template>
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
variant="textonly"
|
||||
@click="toggleHelpCenter"
|
||||
>
|
||||
<div class="not-md:hidden">{{ $t('menu.helpAndFeedback') }}</div>
|
||||
<i class="icon-[lucide--circle-help] ml-0.5" />
|
||||
<div class="not-md:hidden">
|
||||
{{ $t('menu.helpAndFeedback') }}
|
||||
</div>
|
||||
<i class="ml-0.5 icon-[lucide--circle-help]" />
|
||||
<span
|
||||
v-if="shouldShowRedDot"
|
||||
class="absolute top-[7px] right-[7px] size-1.5 rounded-full bg-[#ff3b30]"
|
||||
|
||||
@@ -18,7 +18,11 @@
|
||||
>
|
||||
{{ badge.label }}
|
||||
</div>
|
||||
<div v-else class="size-2 shrink-0 rounded-full" :class="dotClasses" />
|
||||
<div
|
||||
v-else
|
||||
class="size-2 shrink-0 rounded-full"
|
||||
:class="dotClasses"
|
||||
/>
|
||||
<Popover
|
||||
ref="popover"
|
||||
append-to="body"
|
||||
@@ -37,8 +41,13 @@
|
||||
>
|
||||
{{ badge.label }}
|
||||
</div>
|
||||
<div class="text-sm font-inter">{{ badge.text }}</div>
|
||||
<div v-if="badge.tooltip" class="text-xs">
|
||||
<div class="font-inter text-sm">
|
||||
{{ badge.text }}
|
||||
</div>
|
||||
<div
|
||||
v-if="badge.tooltip"
|
||||
class="text-xs"
|
||||
>
|
||||
{{ badge.tooltip }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -90,8 +99,13 @@
|
||||
>
|
||||
{{ badge.label }}
|
||||
</div>
|
||||
<div class="text-sm font-inter">{{ badge.text }}</div>
|
||||
<div v-if="badge.tooltip" class="text-xs">
|
||||
<div class="font-inter text-sm">
|
||||
{{ badge.text }}
|
||||
</div>
|
||||
<div
|
||||
v-if="badge.tooltip"
|
||||
class="text-xs"
|
||||
>
|
||||
{{ badge.tooltip }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -117,7 +131,10 @@
|
||||
>
|
||||
{{ badge.label }}
|
||||
</div>
|
||||
<div class="font-inter text-sm" :class="textClasses">
|
||||
<div
|
||||
class="font-inter text-sm"
|
||||
:class="textClasses"
|
||||
>
|
||||
{{ badge.text }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div>
|
||||
<Button
|
||||
v-tooltip="{ value: $t('g.moreWorkflows'), showDelay: 300 }"
|
||||
class="rounded-none h-full w-auto aspect-square"
|
||||
class="aspect-square h-full w-auto rounded-none"
|
||||
variant="muted-textonly"
|
||||
size="icon"
|
||||
:aria-label="$t('g.moreWorkflows')"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div
|
||||
ref="positionRef"
|
||||
class="absolute bottom-0 left-1/2 -translate-x-1/2"
|
||||
></div>
|
||||
/>
|
||||
<Popover
|
||||
ref="popoverRef"
|
||||
append-to="body"
|
||||
@@ -24,7 +24,7 @@
|
||||
:src="thumbnailUrl"
|
||||
class="block h-[200px] rounded-lg object-cover p-2"
|
||||
:style="{ width: `${POPOVER_WIDTH}px` }"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
<div class="workflow-preview-footer">
|
||||
<span class="workflow-preview-name">{{ workflowFilename }}</span>
|
||||
@@ -143,7 +143,7 @@ defineExpose({
|
||||
@reference '../../assets/css/style.css';
|
||||
|
||||
.workflow-preview-content {
|
||||
@apply flex flex-col rounded-xl overflow-hidden;
|
||||
@apply flex flex-col overflow-hidden rounded-xl;
|
||||
max-width: var(--popover-width);
|
||||
background-color: var(--comfy-menu-bg);
|
||||
color: var(--fg-color);
|
||||
@@ -163,11 +163,11 @@ defineExpose({
|
||||
}
|
||||
|
||||
.workflow-preview-footer {
|
||||
@apply pt-1 pb-2 px-3;
|
||||
@apply px-3 pt-1 pb-2;
|
||||
}
|
||||
|
||||
.workflow-preview-name {
|
||||
@apply block text-sm font-medium overflow-hidden text-ellipsis whitespace-nowrap;
|
||||
@apply block overflow-hidden text-sm font-medium text-ellipsis whitespace-nowrap;
|
||||
color: var(--fg-color);
|
||||
}
|
||||
</style>
|
||||
@@ -178,7 +178,7 @@ defineExpose({
|
||||
.workflow-popover-fade {
|
||||
--p-popover-background: transparent;
|
||||
--p-popover-content-padding: 0;
|
||||
@apply bg-transparent rounded-xl shadow-lg;
|
||||
@apply rounded-xl bg-transparent shadow-lg;
|
||||
transition: opacity 0.15s ease-out !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
v-if="showOverflowArrows"
|
||||
variant="muted-textonly"
|
||||
size="icon"
|
||||
class="overflow-arrow overflow-arrow-left h-full w-auto aspect-square"
|
||||
class="overflow-arrow overflow-arrow-left aspect-square h-full w-auto"
|
||||
:aria-label="$t('g.scrollLeft')"
|
||||
:disabled="!leftArrowEnabled"
|
||||
@mousedown="whileMouseDown($event, () => scroll(-1))"
|
||||
@@ -45,7 +45,7 @@
|
||||
v-if="showOverflowArrows"
|
||||
variant="muted-textonly"
|
||||
size="icon"
|
||||
class="overflow-arrow overflow-arrow-right h-full w-auto aspect-square"
|
||||
class="overflow-arrow overflow-arrow-right aspect-square h-full w-auto"
|
||||
:aria-label="$t('g.scrollRight')"
|
||||
:disabled="!rightArrowEnabled"
|
||||
@mousedown="whileMouseDown($event, () => scroll(1))"
|
||||
@@ -59,7 +59,7 @@
|
||||
/>
|
||||
<Button
|
||||
v-tooltip="{ value: $t('sideToolbar.newBlankWorkflow'), showDelay: 300 }"
|
||||
class="new-blank-workflow-button no-drag shrink-0 rounded-none h-full w-auto aspect-square"
|
||||
class="new-blank-workflow-button no-drag aspect-square h-full w-auto shrink-0 rounded-none"
|
||||
variant="muted-textonly"
|
||||
size="icon"
|
||||
:aria-label="$t('sideToolbar.newBlankWorkflow')"
|
||||
@@ -78,15 +78,30 @@
|
||||
compact
|
||||
class="shrink-0 p-1"
|
||||
/>
|
||||
<LoginButton v-else-if="isDesktop" class="p-1" />
|
||||
<LoginButton
|
||||
v-else-if="isDesktop"
|
||||
class="p-1"
|
||||
/>
|
||||
</div>
|
||||
<ContextMenu ref="menu" :model="contextMenuItems">
|
||||
<ContextMenu
|
||||
ref="menu"
|
||||
:model="contextMenuItems"
|
||||
>
|
||||
<template #itemicon="{ item }">
|
||||
<OverlayIcon v-if="item.overlayIcon" v-bind="item.overlayIcon" />
|
||||
<i v-else-if="item.icon" :class="item.icon" />
|
||||
<OverlayIcon
|
||||
v-if="item.overlayIcon"
|
||||
v-bind="item.overlayIcon"
|
||||
/>
|
||||
<i
|
||||
v-else-if="item.icon"
|
||||
:class="item.icon"
|
||||
/>
|
||||
</template>
|
||||
</ContextMenu>
|
||||
<div v-if="isDesktop" class="window-actions-spacer app-drag shrink-0" />
|
||||
<div
|
||||
v-if="isDesktop"
|
||||
class="window-actions-spacer app-drag shrink-0"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -383,13 +398,13 @@ onUpdated(() => {
|
||||
}
|
||||
|
||||
:deep(.p-togglebutton) {
|
||||
@apply p-0 bg-transparent rounded-none shrink relative border-0 border-r border-solid;
|
||||
@apply relative shrink rounded-none border-0 border-r border-solid bg-transparent p-0;
|
||||
border-right-color: var(--border-color);
|
||||
min-width: 90px;
|
||||
}
|
||||
|
||||
.overflow-arrow {
|
||||
@apply px-2 rounded-none;
|
||||
@apply rounded-none px-2;
|
||||
}
|
||||
|
||||
.overflow-arrow[disabled] {
|
||||
@@ -418,7 +433,7 @@ onUpdated(() => {
|
||||
}
|
||||
|
||||
:deep(.p-togglebutton.p-togglebutton-checked) {
|
||||
@apply border-b border-solid h-full;
|
||||
@apply h-full border-b border-solid;
|
||||
border-bottom-color: var(--p-button-text-primary-color);
|
||||
}
|
||||
|
||||
@@ -446,7 +461,7 @@ onUpdated(() => {
|
||||
}
|
||||
|
||||
:deep(.p-selectbutton) {
|
||||
@apply rounded-none h-full;
|
||||
@apply h-full rounded-none;
|
||||
}
|
||||
|
||||
.workflow-tabs-container-desktop {
|
||||
|
||||
@@ -36,21 +36,21 @@ defineProps<{
|
||||
:side-offset="5"
|
||||
:collision-padding="10"
|
||||
v-bind="$attrs"
|
||||
class="rounded-lg p-2 bg-base-background shadow-sm border border-border-subtle will-change-[transform,opacity] data-[state=open]:data-[side=top]:animate-slideDownAndFade data-[state=open]:data-[side=right]:animate-slideLeftAndFade data-[state=open]:data-[side=bottom]:animate-slideUpAndFade data-[state=open]:data-[side=left]:animate-slideRightAndFade"
|
||||
class="data-[state=open]:data-[side=top]:animate-slideDownAndFade data-[state=open]:data-[side=right]:animate-slideLeftAndFade data-[state=open]:data-[side=bottom]:animate-slideUpAndFade data-[state=open]:data-[side=left]:animate-slideRightAndFade rounded-lg border border-border-subtle bg-base-background p-2 shadow-sm will-change-[transform,opacity]"
|
||||
>
|
||||
<slot>
|
||||
<div class="flex flex-col p-1">
|
||||
<section
|
||||
v-for="(entryGroup, index) in entries ?? []"
|
||||
:key="index"
|
||||
class="flex flex-col border-b-2 last:border-none border-border-subtle"
|
||||
class="flex flex-col border-b-2 border-border-subtle last:border-none"
|
||||
>
|
||||
<div
|
||||
v-for="{ label, action, icon } in entryGroup"
|
||||
:key="label"
|
||||
:class="
|
||||
cn(
|
||||
'flex flex-row gap-4 p-2 rounded-sm my-1',
|
||||
'my-1 flex flex-row gap-4 rounded-sm p-2',
|
||||
action &&
|
||||
'cursor-pointer hover:bg-secondary-background-hover'
|
||||
)
|
||||
@@ -63,7 +63,10 @@ defineProps<{
|
||||
}
|
||||
"
|
||||
>
|
||||
<i v-if="icon" :class="icon" />
|
||||
<i
|
||||
v-if="icon"
|
||||
:class="icon"
|
||||
/>
|
||||
{{ label }}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -20,10 +20,17 @@ whenever(feedbackRef, () => {
|
||||
<template>
|
||||
<Popover>
|
||||
<template #button>
|
||||
<Button variant="inverted" class="rounded-full size-12">
|
||||
<Button
|
||||
variant="inverted"
|
||||
class="size-12 rounded-full"
|
||||
>
|
||||
<i class="icon-[lucide--circle-question-mark] size-6" />
|
||||
</Button>
|
||||
</template>
|
||||
<div ref="feedbackRef" data-tf-auto-resize :data-tf-widget />
|
||||
<div
|
||||
ref="feedbackRef"
|
||||
data-tf-auto-resize
|
||||
:data-tf-widget
|
||||
/>
|
||||
</Popover>
|
||||
</template>
|
||||
|
||||
@@ -47,7 +47,7 @@ const transform = computed(() => {
|
||||
<template>
|
||||
<div
|
||||
ref="zoomPane"
|
||||
class="contain-size place-content-center"
|
||||
class="place-content-center contain-size"
|
||||
@wheel="handleWheel"
|
||||
@pointerdown.prevent="handleDown"
|
||||
@pointermove="handleMove"
|
||||
|
||||
@@ -2,11 +2,11 @@ import type { VariantProps } from 'cva'
|
||||
import { cva } from 'cva'
|
||||
|
||||
export const buttonVariants = cva({
|
||||
base: 'relative inline-flex items-center justify-center gap-2 cursor-pointer whitespace-nowrap appearance-none border-none rounded-md text-sm font-medium font-inter transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
||||
base: 'focus-visible:ring-ring relative inline-flex cursor-pointer appearance-none items-center justify-center gap-2 rounded-md border-none font-inter text-sm font-medium whitespace-nowrap transition-colors focus-visible:ring-1 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
||||
variants: {
|
||||
variant: {
|
||||
secondary:
|
||||
'bg-secondary-background text-secondary-foreground hover:bg-secondary-background-hover',
|
||||
'text-secondary-foreground bg-secondary-background hover:bg-secondary-background-hover',
|
||||
primary:
|
||||
'bg-primary-background text-base-foreground hover:bg-primary-background-hover',
|
||||
inverted:
|
||||
@@ -14,11 +14,11 @@ export const buttonVariants = cva({
|
||||
destructive:
|
||||
'bg-destructive-background text-base-foreground hover:bg-destructive-background-hover',
|
||||
textonly:
|
||||
'text-base-foreground bg-transparent hover:bg-secondary-background-hover',
|
||||
'bg-transparent text-base-foreground hover:bg-secondary-background-hover',
|
||||
'muted-textonly':
|
||||
'text-muted-foreground bg-transparent hover:bg-secondary-background-hover',
|
||||
'bg-transparent text-muted-foreground hover:bg-secondary-background-hover',
|
||||
'destructive-textonly':
|
||||
'text-destructive-background bg-transparent hover:bg-destructive-background/10',
|
||||
'bg-transparent text-destructive-background hover:bg-destructive-background/10',
|
||||
'overlay-white': 'bg-white text-gray-600 hover:bg-white/90'
|
||||
},
|
||||
size: {
|
||||
|
||||
@@ -50,7 +50,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
data-slot="slider-track"
|
||||
:class="
|
||||
cn(
|
||||
'bg-node-stroke relative grow overflow-hidden rounded-full',
|
||||
'relative grow overflow-hidden rounded-full bg-node-stroke',
|
||||
'cursor-pointer overflow-visible',
|
||||
`before:absolute before:-inset-2 before:block before:bg-transparent`,
|
||||
'data-[orientation=horizontal]:h-0.5 data-[orientation=horizontal]:w-full',
|
||||
@@ -70,9 +70,9 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
data-slot="slider-thumb"
|
||||
:class="
|
||||
cn(
|
||||
'bg-node-component-surface-highlight ring-node-component-surface-selected block size-3.5 shrink-0 rounded-full shadow-sm transition-[color,box-shadow]',
|
||||
'block size-3.5 shrink-0 rounded-full bg-node-component-surface-highlight shadow-sm ring-node-component-surface-selected transition-[color,box-shadow]',
|
||||
'cursor-grab',
|
||||
'before:absolute before:-inset-1 before:block before:bg-transparent before:rounded-full',
|
||||
'before:absolute before:-inset-1 before:block before:rounded-full before:bg-transparent',
|
||||
'hover:ring-2 focus-visible:ring-2 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50',
|
||||
{ 'cursor-grabbing': pressed }
|
||||
)
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
:for="inputId"
|
||||
:class="
|
||||
cn(
|
||||
'flex h-10 cursor-text items-center rounded-lg bg-secondary-background text-secondary-foreground hover:bg-secondary-background-hover focus-within:ring-1 focus-within:ring-secondary-foreground',
|
||||
disabled && 'opacity-50 pointer-events-none'
|
||||
'text-secondary-foreground focus-within:ring-secondary-foreground flex h-10 cursor-text items-center rounded-lg bg-secondary-background focus-within:ring-1 hover:bg-secondary-background-hover',
|
||||
disabled && 'pointer-events-none opacity-50'
|
||||
)
|
||||
"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="flex h-full w-8 cursor-pointer items-center justify-center rounded-l-lg border-none bg-transparent text-muted-foreground transition-colors hover:text-base-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-secondary-foreground disabled:opacity-30"
|
||||
class="focus-visible:ring-secondary-foreground flex h-full w-8 cursor-pointer items-center justify-center rounded-l-lg border-none bg-transparent text-muted-foreground transition-colors hover:text-base-foreground focus-visible:ring-1 focus-visible:outline-none focus-visible:ring-inset disabled:opacity-30"
|
||||
:disabled="disabled || modelValue <= min"
|
||||
:aria-label="$t('g.decrement')"
|
||||
@click="handleStep(-1)"
|
||||
@@ -28,17 +28,17 @@
|
||||
type="text"
|
||||
inputmode="numeric"
|
||||
:style="{ width: `${inputWidth}ch` }"
|
||||
class="min-w-0 rounded border-none bg-transparent text-center text-base-foreground font-medium text-lg focus-visible:outline-none"
|
||||
class="min-w-0 rounded border-none bg-transparent text-center text-lg font-medium text-base-foreground focus-visible:outline-none"
|
||||
:disabled="disabled"
|
||||
@input="handleInputChange"
|
||||
@blur="handleInputBlur"
|
||||
@focus="handleInputFocus"
|
||||
/>
|
||||
>
|
||||
<slot name="suffix" />
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="flex h-full w-8 cursor-pointer items-center justify-center rounded-r-lg border-none bg-transparent text-muted-foreground transition-colors hover:text-base-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-secondary-foreground disabled:opacity-30"
|
||||
class="focus-visible:ring-secondary-foreground flex h-full w-8 cursor-pointer items-center justify-center rounded-r-lg border-none bg-transparent text-muted-foreground transition-colors hover:text-base-foreground focus-visible:ring-1 focus-visible:outline-none focus-visible:ring-inset disabled:opacity-30"
|
||||
:disabled="disabled || modelValue >= max"
|
||||
:aria-label="$t('g.increment')"
|
||||
@click="handleStep(1)"
|
||||
|
||||
@@ -60,7 +60,7 @@ onClickOutside(rootEl, () => {
|
||||
cn(
|
||||
'group relative flex flex-wrap items-center gap-2 rounded-lg bg-transparent p-2 text-xs text-base-foreground',
|
||||
!internalDisabled &&
|
||||
'hover:bg-modal-card-background-hovered focus-within:bg-modal-card-background-hovered',
|
||||
'focus-within:bg-modal-card-background-hovered hover:bg-modal-card-background-hovered',
|
||||
!disabled && !isEditing && 'cursor-pointer',
|
||||
className
|
||||
)
|
||||
@@ -71,7 +71,7 @@ onClickOutside(rootEl, () => {
|
||||
<i
|
||||
v-if="!disabled && !isEditing"
|
||||
aria-hidden="true"
|
||||
class="icon-[lucide--square-pen] absolute bottom-2 right-2 size-4 text-muted-foreground"
|
||||
class="absolute right-2 bottom-2 icon-[lucide--square-pen] size-4 text-muted-foreground"
|
||||
/>
|
||||
</TagsInputRoot>
|
||||
</template>
|
||||
|
||||
@@ -39,7 +39,7 @@ onUnmounted(() => {
|
||||
v-bind="forwardedProps"
|
||||
:class="
|
||||
cn(
|
||||
'min-h-6 flex-1 bg-transparent text-xs text-muted-foreground placeholder:text-muted-foreground focus:outline-none appearance-none border-none',
|
||||
'min-h-6 flex-1 appearance-none border-none bg-transparent text-xs text-muted-foreground placeholder:text-muted-foreground focus:outline-none',
|
||||
!isEditing && 'pointer-events-none',
|
||||
className
|
||||
)
|
||||
|
||||
@@ -17,7 +17,7 @@ const forwardedProps = useForwardProps(restProps)
|
||||
v-bind="forwardedProps"
|
||||
:class="
|
||||
cn(
|
||||
'flex h-6 items-center gap-1 rounded-sm bg-modal-card-tag-background py-1 pl-2 pr-1 text-modal-card-tag-foreground backdrop-blur-sm ring-offset-base-background data-[state=active]:ring-2 data-[state=active]:ring-base-foreground data-[state=active]:ring-offset-1',
|
||||
'flex h-6 items-center gap-1 rounded-sm bg-modal-card-tag-background py-1 pr-1 pl-2 text-modal-card-tag-foreground ring-offset-base-background backdrop-blur-sm data-[state=active]:ring-2 data-[state=active]:ring-base-foreground data-[state=active]:ring-offset-1',
|
||||
className
|
||||
)
|
||||
"
|
||||
|
||||
@@ -24,7 +24,7 @@ const { t } = useI18n()
|
||||
:aria-label="t('g.removeTag')"
|
||||
:class="
|
||||
cn(
|
||||
'opacity-60 hover:bg-transparent hover:opacity-100 transition-[opacity,width] duration-150 w-4 data-[disabled]:w-0 data-[disabled]:opacity-0 data-[disabled]:pointer-events-none overflow-hidden',
|
||||
'w-4 overflow-hidden opacity-60 transition-[opacity,width] duration-150 hover:bg-transparent hover:opacity-100 data-[disabled]:pointer-events-none data-[disabled]:w-0 data-[disabled]:opacity-0',
|
||||
className
|
||||
)
|
||||
"
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<div class="base-widget-layout rounded-2xl overflow-hidden relative">
|
||||
<div class="base-widget-layout relative overflow-hidden rounded-2xl">
|
||||
<Button
|
||||
v-show="!isRightPanelOpen && hasRightPanel"
|
||||
size="lg"
|
||||
:class="
|
||||
cn('absolute top-4 right-18 z-10', 'transition-opacity duration-200', {
|
||||
'opacity-0 pointer-events-none': isRightPanelOpen || !hasRightPanel
|
||||
'pointer-events-none opacity-0': isRightPanelOpen || !hasRightPanel
|
||||
})
|
||||
"
|
||||
@click="toggleRightPanel"
|
||||
@@ -14,7 +14,7 @@
|
||||
</Button>
|
||||
<Button
|
||||
size="lg"
|
||||
class="absolute top-4 right-6 z-10 transition-opacity duration-200 w-10"
|
||||
class="absolute top-4 right-6 z-10 w-10 transition-opacity duration-200"
|
||||
@click="closeDialog"
|
||||
>
|
||||
<i class="pi pi-times" />
|
||||
@@ -29,18 +29,22 @@
|
||||
PANEL_SIZES.maxWidth
|
||||
]"
|
||||
>
|
||||
<slot name="leftPanel"></slot>
|
||||
<slot name="leftPanel" />
|
||||
</nav>
|
||||
</Transition>
|
||||
|
||||
<div class="flex-1 flex bg-base-background">
|
||||
<div class="flex flex-1 bg-base-background">
|
||||
<div class="flex h-full w-full flex-col">
|
||||
<header
|
||||
v-if="$slots.header"
|
||||
class="w-full h-18 px-6 flex items-center justify-between gap-2"
|
||||
class="flex h-18 w-full items-center justify-between gap-2 px-6"
|
||||
>
|
||||
<div class="flex flex-1 shrink-0 gap-2">
|
||||
<Button v-if="!notMobile" size="icon" @click="toggleLeftPanel">
|
||||
<Button
|
||||
v-if="!notMobile"
|
||||
size="icon"
|
||||
@click="toggleLeftPanel"
|
||||
>
|
||||
<i
|
||||
:class="
|
||||
cn(
|
||||
@@ -51,13 +55,13 @@
|
||||
"
|
||||
/>
|
||||
</Button>
|
||||
<slot name="header"></slot>
|
||||
<slot name="header" />
|
||||
</div>
|
||||
<slot name="header-right-area"></slot>
|
||||
<slot name="header-right-area" />
|
||||
<div
|
||||
:class="
|
||||
cn(
|
||||
'flex justify-end gap-2 w-0',
|
||||
'flex w-0 justify-end gap-2',
|
||||
hasRightPanel && !isRightPanelOpen ? 'min-w-22' : 'min-w-10'
|
||||
)
|
||||
"
|
||||
@@ -74,7 +78,7 @@
|
||||
|
||||
<main class="flex min-h-0 flex-1 flex-col">
|
||||
<!-- Fallback title bar when no leftPanel is provided -->
|
||||
<slot name="contentFilter"></slot>
|
||||
<slot name="contentFilter" />
|
||||
<h2
|
||||
v-if="!$slots.leftPanel"
|
||||
class="text-xxl m-0 px-6 pt-2 pb-6 capitalize"
|
||||
@@ -82,17 +86,17 @@
|
||||
{{ contentTitle }}
|
||||
</h2>
|
||||
<div
|
||||
class="min-h-0 flex-1 px-6 pt-0 pb-10 overflow-y-auto scrollbar-custom"
|
||||
class="scrollbar-custom min-h-0 flex-1 overflow-y-auto px-6 pt-0 pb-10"
|
||||
>
|
||||
<slot name="content"></slot>
|
||||
<slot name="content" />
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<aside
|
||||
v-if="hasRightPanel && isRightPanelOpen"
|
||||
class="w-1/4 min-w-40 max-w-80 pt-16 pb-8"
|
||||
class="w-1/4 max-w-80 min-w-40 pt-16 pb-8"
|
||||
>
|
||||
<slot name="rightPanel"></slot>
|
||||
<slot name="rightPanel" />
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<i :class="icon" class="text-neutral text-sm shrink-0" />
|
||||
<i
|
||||
:class="icon"
|
||||
class="text-neutral shrink-0 text-sm"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
disabled: !isOverflowing,
|
||||
pt: { text: { class: 'whitespace-nowrap' } }
|
||||
}"
|
||||
class="flex cursor-pointer items-start gap-2 rounded-md px-4 py-3 text-sm transition-colors text-base-foreground"
|
||||
class="flex cursor-pointer items-start gap-2 rounded-md px-4 py-3 text-sm text-base-foreground transition-colors"
|
||||
:class="
|
||||
active
|
||||
? 'bg-interface-menu-component-surface-selected'
|
||||
@@ -15,12 +15,21 @@
|
||||
@mouseenter="checkOverflow"
|
||||
@click="onClick"
|
||||
>
|
||||
<div v-if="icon" class="pt-0.5">
|
||||
<div
|
||||
v-if="icon"
|
||||
class="pt-0.5"
|
||||
>
|
||||
<NavIcon :icon="icon" />
|
||||
</div>
|
||||
<i v-else class="text-neutral icon-[lucide--folder] text-xs shrink-0" />
|
||||
<span ref="textRef" class="min-w-0 truncate">
|
||||
<slot></slot>
|
||||
<i
|
||||
v-else
|
||||
class="text-neutral icon-[lucide--folder] shrink-0 text-xs"
|
||||
/>
|
||||
<span
|
||||
ref="textRef"
|
||||
class="min-w-0 truncate"
|
||||
>
|
||||
<slot />
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div
|
||||
:class="
|
||||
cn(
|
||||
'flex items-center justify-between m-0 px-3 py-0 pt-5',
|
||||
'm-0 flex items-center justify-between px-3 py-0 pt-5',
|
||||
collapsible && 'cursor-pointer select-none'
|
||||
)
|
||||
"
|
||||
@@ -15,7 +15,7 @@
|
||||
v-if="collapsible"
|
||||
:class="
|
||||
cn(
|
||||
'pi transition-transform duration-200 text-xs text-text-secondary ',
|
||||
'pi text-xs text-text-secondary transition-transform duration-200',
|
||||
isCollapsed ? 'pi-chevron-right' : 'pi-chevron-down'
|
||||
)
|
||||
"
|
||||
|
||||
@@ -1,173 +1,173 @@
|
||||
# @ComfyOrg/litegraph
|
||||
|
||||
This is the litegraph version used in [ComfyUI_frontend](https://github.com/Comfy-Org/ComfyUI_frontend).
|
||||
|
||||
It is a fork of the original `litegraph.js`. Some APIs may by unchanged, however it is largely incompatible with the original.
|
||||
|
||||
Some early highlights:
|
||||
|
||||
- Accumulated comfyUI custom changes (2024-01 ~ 2024-05) (https://github.com/Comfy-Org/litegraph.js/pull/1)
|
||||
- Type schema change for ComfyUI_frontend TS migration (https://github.com/Comfy-Org/litegraph.js/pull/3)
|
||||
- Zoom fix (https://github.com/Comfy-Org/litegraph.js/pull/7)
|
||||
- Emit search box triggering custom events (<https://github.com/Comfy-Org/litegraph.js/pull/10>)
|
||||
- Truncate overflowing combo widget text (<https://github.com/Comfy-Org/litegraph.js/pull/17>)
|
||||
- Sort node based on ID on graph serialization (<https://github.com/Comfy-Org/litegraph.js/pull/21>)
|
||||
- Fix empty input not used when connecting links (<https://github.com/Comfy-Org/litegraph.js/pull/24>)
|
||||
- Batch output connection move/disconnect (<https://github.com/Comfy-Org/litegraph.js/pull/39>)
|
||||
- And now with hundreds more...
|
||||
|
||||
# Usage
|
||||
|
||||
This library is included as a git subtree in the ComfyUI frontend project at `src/lib/litegraph`.
|
||||
|
||||
# litegraph.js
|
||||
|
||||
A TypeScript library to create graphs in the browser similar to Unreal Blueprints.
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Description of the original litegraph.js</summary>
|
||||
|
||||
A library in Javascript to create graphs in the browser similar to Unreal Blueprints. Nodes can be programmed easily and it includes an editor to construct and tests the graphs.
|
||||
|
||||
It can be integrated easily in any existing web applications and graphs can be run without the need of the editor.
|
||||
|
||||
</details>
|
||||
|
||||

|
||||
|
||||
## Features
|
||||
|
||||
- Renders on Canvas2D (zoom in/out and panning, easy to render complex interfaces, can be used inside a WebGLTexture)
|
||||
- Easy to use editor (searchbox, keyboard shortcuts, multiple selection, context menu, ...)
|
||||
- Optimized to support hundreds of nodes per graph (on editor but also on execution)
|
||||
- Customizable theme (colors, shapes, background)
|
||||
- Callbacks to personalize every action/drawing/event of nodes
|
||||
- Graphs can be executed in NodeJS
|
||||
- Highly customizable nodes (color, shape, widgets, custom rendering)
|
||||
- Easy to integrate in any JS application (one single file, no dependencies)
|
||||
- Typescript support
|
||||
|
||||
## Integration
|
||||
|
||||
This library is integrated as a git subtree in the ComfyUI frontend project. To use it in your code:
|
||||
|
||||
```typescript
|
||||
import { LGraph, LGraphNode, LiteGraph } from '@/lib/litegraph'
|
||||
```
|
||||
|
||||
## How to code a new Node type
|
||||
|
||||
Here is an example of how to build a node that sums two inputs:
|
||||
|
||||
```ts
|
||||
import { LiteGraph, LGraphNode } from './litegraph'
|
||||
|
||||
class MyAddNode extends LGraphNode {
|
||||
// Name to show
|
||||
title = 'Sum'
|
||||
|
||||
constructor() {
|
||||
this.addInput('A', 'number')
|
||||
this.addInput('B', 'number')
|
||||
this.addOutput('A+B', 'number')
|
||||
this.properties.precision = 1
|
||||
}
|
||||
|
||||
// Function to call when the node is executed
|
||||
onExecute() {
|
||||
var A = this.getInputData(0)
|
||||
if (A === undefined) A = 0
|
||||
var B = this.getInputData(1)
|
||||
if (B === undefined) B = 0
|
||||
this.setOutputData(0, A + B)
|
||||
}
|
||||
}
|
||||
|
||||
// Register the node type
|
||||
LiteGraph.registerNodeType('basic/sum', MyAddNode)
|
||||
```
|
||||
|
||||
## Server side
|
||||
|
||||
It also works server-side using NodeJS although some nodes do not work in server (audio, graphics, input, etc).
|
||||
|
||||
```ts
|
||||
import { LiteGraph, LGraph } from './litegraph.js'
|
||||
|
||||
const graph = new LGraph()
|
||||
|
||||
const firstNode = LiteGraph.createNode('basic/sum')
|
||||
graph.add(firstNode)
|
||||
|
||||
const secondNode = LiteGraph.createNode('basic/sum')
|
||||
graph.add(secondNode)
|
||||
|
||||
firstNode.connect(0, secondNode, 1)
|
||||
|
||||
graph.start()
|
||||
```
|
||||
|
||||
## Projects using it
|
||||
|
||||
### [ComfyUI](https://github.com/comfyanonymous/ComfyUI)
|
||||
|
||||

|
||||
|
||||
### Projects using the original litegraph.js
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Click to expand</summary>
|
||||
|
||||
### [webglstudio.org](http://webglstudio.org)
|
||||
|
||||

|
||||
|
||||
### [MOI Elephant](http://moiscript.weebly.com/elephant-systegraveme-nodal.html)
|
||||
|
||||

|
||||
|
||||
### Mynodes
|
||||
|
||||

|
||||
|
||||
</details>
|
||||
|
||||
## Feedback
|
||||
|
||||
Please [open an issue](https://github.com/Comfy-Org/litegraph.js/issues/) on the GitHub repo.
|
||||
|
||||
# Development
|
||||
|
||||
Litegraph has no runtime dependencies. The build tooling has been tested on Node.JS 20.18.x
|
||||
|
||||
## Releasing
|
||||
|
||||
Use GitHub actions to release normal versions.
|
||||
|
||||
1. Run the `Release a New Version` action, selecting the version increment type
|
||||
1. Merge the resolution PR
|
||||
1. A GitHub release is automatically published on merge
|
||||
|
||||
### Pre-release
|
||||
|
||||
The action directly translates `Version increment type` to the pnpm version command. `Pre-release ID (suffix)` is the option for the `--preid` argument.
|
||||
|
||||
e.g. Use `prerelease` increment type to automatically bump the patch version and create a pre-release version. Subsequent runs of prerelease will update the prerelease version only.
|
||||
Use `patch` when ready to remove the pre-release suffix.
|
||||
|
||||
## Contributors
|
||||
|
||||
You can find the [current list of contributors](https://github.com/Comfy-Org/litegraph.js/graphs/contributors) on GitHub.
|
||||
|
||||
### Contributors (pre-fork)
|
||||
|
||||
- atlasan
|
||||
- kriffe
|
||||
- rappestad
|
||||
- InventivetalentDev
|
||||
- NateScarlet
|
||||
- coderofsalvation
|
||||
- ilyabesk
|
||||
- gausszhou
|
||||
# @ComfyOrg/litegraph
|
||||
|
||||
This is the litegraph version used in [ComfyUI_frontend](https://github.com/Comfy-Org/ComfyUI_frontend).
|
||||
|
||||
It is a fork of the original `litegraph.js`. Some APIs may by unchanged, however it is largely incompatible with the original.
|
||||
|
||||
Some early highlights:
|
||||
|
||||
- Accumulated comfyUI custom changes (2024-01 ~ 2024-05) (https://github.com/Comfy-Org/litegraph.js/pull/1)
|
||||
- Type schema change for ComfyUI_frontend TS migration (https://github.com/Comfy-Org/litegraph.js/pull/3)
|
||||
- Zoom fix (https://github.com/Comfy-Org/litegraph.js/pull/7)
|
||||
- Emit search box triggering custom events (<https://github.com/Comfy-Org/litegraph.js/pull/10>)
|
||||
- Truncate overflowing combo widget text (<https://github.com/Comfy-Org/litegraph.js/pull/17>)
|
||||
- Sort node based on ID on graph serialization (<https://github.com/Comfy-Org/litegraph.js/pull/21>)
|
||||
- Fix empty input not used when connecting links (<https://github.com/Comfy-Org/litegraph.js/pull/24>)
|
||||
- Batch output connection move/disconnect (<https://github.com/Comfy-Org/litegraph.js/pull/39>)
|
||||
- And now with hundreds more...
|
||||
|
||||
# Usage
|
||||
|
||||
This library is included as a git subtree in the ComfyUI frontend project at `src/lib/litegraph`.
|
||||
|
||||
# litegraph.js
|
||||
|
||||
A TypeScript library to create graphs in the browser similar to Unreal Blueprints.
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Description of the original litegraph.js</summary>
|
||||
|
||||
A library in Javascript to create graphs in the browser similar to Unreal Blueprints. Nodes can be programmed easily and it includes an editor to construct and tests the graphs.
|
||||
|
||||
It can be integrated easily in any existing web applications and graphs can be run without the need of the editor.
|
||||
|
||||
</details>
|
||||
|
||||

|
||||
|
||||
## Features
|
||||
|
||||
- Renders on Canvas2D (zoom in/out and panning, easy to render complex interfaces, can be used inside a WebGLTexture)
|
||||
- Easy to use editor (searchbox, keyboard shortcuts, multiple selection, context menu, ...)
|
||||
- Optimized to support hundreds of nodes per graph (on editor but also on execution)
|
||||
- Customizable theme (colors, shapes, background)
|
||||
- Callbacks to personalize every action/drawing/event of nodes
|
||||
- Graphs can be executed in NodeJS
|
||||
- Highly customizable nodes (color, shape, widgets, custom rendering)
|
||||
- Easy to integrate in any JS application (one single file, no dependencies)
|
||||
- Typescript support
|
||||
|
||||
## Integration
|
||||
|
||||
This library is integrated as a git subtree in the ComfyUI frontend project. To use it in your code:
|
||||
|
||||
```typescript
|
||||
import { LGraph, LGraphNode, LiteGraph } from '@/lib/litegraph'
|
||||
```
|
||||
|
||||
## How to code a new Node type
|
||||
|
||||
Here is an example of how to build a node that sums two inputs:
|
||||
|
||||
```ts
|
||||
import { LiteGraph, LGraphNode } from './litegraph'
|
||||
|
||||
class MyAddNode extends LGraphNode {
|
||||
// Name to show
|
||||
title = 'Sum'
|
||||
|
||||
constructor() {
|
||||
this.addInput('A', 'number')
|
||||
this.addInput('B', 'number')
|
||||
this.addOutput('A+B', 'number')
|
||||
this.properties.precision = 1
|
||||
}
|
||||
|
||||
// Function to call when the node is executed
|
||||
onExecute() {
|
||||
var A = this.getInputData(0)
|
||||
if (A === undefined) A = 0
|
||||
var B = this.getInputData(1)
|
||||
if (B === undefined) B = 0
|
||||
this.setOutputData(0, A + B)
|
||||
}
|
||||
}
|
||||
|
||||
// Register the node type
|
||||
LiteGraph.registerNodeType('basic/sum', MyAddNode)
|
||||
```
|
||||
|
||||
## Server side
|
||||
|
||||
It also works server-side using NodeJS although some nodes do not work in server (audio, graphics, input, etc).
|
||||
|
||||
```ts
|
||||
import { LiteGraph, LGraph } from './litegraph.js'
|
||||
|
||||
const graph = new LGraph()
|
||||
|
||||
const firstNode = LiteGraph.createNode('basic/sum')
|
||||
graph.add(firstNode)
|
||||
|
||||
const secondNode = LiteGraph.createNode('basic/sum')
|
||||
graph.add(secondNode)
|
||||
|
||||
firstNode.connect(0, secondNode, 1)
|
||||
|
||||
graph.start()
|
||||
```
|
||||
|
||||
## Projects using it
|
||||
|
||||
### [ComfyUI](https://github.com/comfyanonymous/ComfyUI)
|
||||
|
||||

|
||||
|
||||
### Projects using the original litegraph.js
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Click to expand</summary>
|
||||
|
||||
### [webglstudio.org](http://webglstudio.org)
|
||||
|
||||

|
||||
|
||||
### [MOI Elephant](http://moiscript.weebly.com/elephant-systegraveme-nodal.html)
|
||||
|
||||

|
||||
|
||||
### Mynodes
|
||||
|
||||

|
||||
|
||||
</details>
|
||||
|
||||
## Feedback
|
||||
|
||||
Please [open an issue](https://github.com/Comfy-Org/litegraph.js/issues/) on the GitHub repo.
|
||||
|
||||
# Development
|
||||
|
||||
Litegraph has no runtime dependencies. The build tooling has been tested on Node.JS 20.18.x
|
||||
|
||||
## Releasing
|
||||
|
||||
Use GitHub actions to release normal versions.
|
||||
|
||||
1. Run the `Release a New Version` action, selecting the version increment type
|
||||
1. Merge the resolution PR
|
||||
1. A GitHub release is automatically published on merge
|
||||
|
||||
### Pre-release
|
||||
|
||||
The action directly translates `Version increment type` to the pnpm version command. `Pre-release ID (suffix)` is the option for the `--preid` argument.
|
||||
|
||||
e.g. Use `prerelease` increment type to automatically bump the patch version and create a pre-release version. Subsequent runs of prerelease will update the prerelease version only.
|
||||
Use `patch` when ready to remove the pre-release suffix.
|
||||
|
||||
## Contributors
|
||||
|
||||
You can find the [current list of contributors](https://github.com/Comfy-Org/litegraph.js/graphs/contributors) on GitHub.
|
||||
|
||||
### Contributors (pre-fork)
|
||||
|
||||
- atlasan
|
||||
- kriffe
|
||||
- rappestad
|
||||
- InventivetalentDev
|
||||
- NateScarlet
|
||||
- coderofsalvation
|
||||
- ilyabesk
|
||||
- gausszhou
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<div class="absolute left-2 bottom-2 flex flex-wrap justify-start gap-1">
|
||||
<div class="absolute bottom-2 left-2 flex flex-wrap justify-start gap-1">
|
||||
<span
|
||||
v-for="badge in badges"
|
||||
:key="badge.label"
|
||||
:class="
|
||||
cn(
|
||||
'px-2 py-1 rounded text-xs font-bold uppercase tracking-wider text-modal-card-tag-foreground bg-modal-card-tag-background break-all'
|
||||
'rounded bg-modal-card-tag-background px-2 py-1 text-xs font-bold tracking-wider break-all text-modal-card-tag-foreground uppercase'
|
||||
)
|
||||
"
|
||||
>
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
:tabindex="interactive ? 0 : -1"
|
||||
:class="
|
||||
cn(
|
||||
'rounded-2xl overflow-hidden transition-all duration-200 bg-modal-card-background p-2 gap-2 flex flex-col h-full',
|
||||
'flex h-full flex-col gap-2 overflow-hidden rounded-2xl bg-modal-card-background p-2 transition-all duration-200',
|
||||
interactive &&
|
||||
'group appearance-none bg-transparent m-0 outline-none text-left hover:bg-secondary-background focus:bg-secondary-background border-none focus:outline-solid outline-base-foreground outline-4'
|
||||
'group m-0 appearance-none border-none bg-transparent text-left outline-4 outline-base-foreground outline-none hover:bg-secondary-background focus:bg-secondary-background focus:outline-solid'
|
||||
)
|
||||
"
|
||||
@keydown.enter.self="interactive && $emit('select', asset)"
|
||||
@@ -25,22 +25,25 @@
|
||||
v-else
|
||||
:src="asset.preview_url"
|
||||
:alt="displayName"
|
||||
class="size-full object-cover cursor-pointer"
|
||||
class="size-full cursor-pointer object-cover"
|
||||
role="button"
|
||||
@click.self="interactive && $emit('select', asset)"
|
||||
/>
|
||||
>
|
||||
|
||||
<AssetBadgeGroup :badges="asset.badges" />
|
||||
<IconGroup
|
||||
v-if="showAssetOptions"
|
||||
:class="
|
||||
cn(
|
||||
'absolute top-2 right-2 invisible group-hover:visible',
|
||||
'invisible absolute top-2 right-2 group-hover:visible',
|
||||
dropdownMenuButton?.isOpen && 'visible'
|
||||
)
|
||||
"
|
||||
>
|
||||
<MoreButton ref="dropdown-menu-button" size="sm">
|
||||
<MoreButton
|
||||
ref="dropdown-menu-button"
|
||||
size="sm"
|
||||
>
|
||||
<template #default>
|
||||
<Button
|
||||
v-if="flags.assetRenameEnabled"
|
||||
@@ -66,13 +69,13 @@
|
||||
</MoreButton>
|
||||
</IconGroup>
|
||||
</div>
|
||||
<div class="max-h-32 flex flex-col gap-2 justify-between flex-auto">
|
||||
<div class="flex max-h-32 flex-auto flex-col justify-between gap-2">
|
||||
<h3
|
||||
:id="titleId"
|
||||
v-tooltip.top="{ value: displayName, showDelay: tooltipDelay }"
|
||||
:class="
|
||||
cn(
|
||||
'mb-2 m-0 text-base font-semibold line-clamp-2 wrap-anywhere',
|
||||
'm-0 mb-2 line-clamp-2 text-base font-semibold wrap-anywhere',
|
||||
'text-base-foreground'
|
||||
)
|
||||
"
|
||||
@@ -90,22 +93,31 @@
|
||||
v-tooltip.top="{ value: asset.description, showDelay: tooltipDelay }"
|
||||
:class="
|
||||
cn(
|
||||
'm-0 text-sm leading-6 overflow-hidden [-webkit-box-orient:vertical] [-webkit-line-clamp:2] [display:-webkit-box] text-muted-foreground'
|
||||
'm-0 [display:-webkit-box] overflow-hidden text-sm leading-6 text-muted-foreground [-webkit-box-orient:vertical] [-webkit-line-clamp:2]'
|
||||
)
|
||||
"
|
||||
>
|
||||
{{ asset.description }}
|
||||
</p>
|
||||
<div class="flex gap-4 text-xs text-muted-foreground mt-auto">
|
||||
<span v-if="asset.stats.stars" class="flex items-center gap-1">
|
||||
<div class="mt-auto flex gap-4 text-xs text-muted-foreground">
|
||||
<span
|
||||
v-if="asset.stats.stars"
|
||||
class="flex items-center gap-1"
|
||||
>
|
||||
<i class="icon-[lucide--star] size-3" />
|
||||
{{ asset.stats.stars }}
|
||||
</span>
|
||||
<span v-if="asset.stats.downloadCount" class="flex items-center gap-1">
|
||||
<span
|
||||
v-if="asset.stats.downloadCount"
|
||||
class="flex items-center gap-1"
|
||||
>
|
||||
<i class="icon-[lucide--download] size-3" />
|
||||
{{ asset.stats.downloadCount }}
|
||||
</span>
|
||||
<span v-if="asset.stats.formattedDate" class="flex items-center gap-1">
|
||||
<span
|
||||
v-if="asset.stats.formattedDate"
|
||||
class="flex items-center gap-1"
|
||||
>
|
||||
<i class="icon-[lucide--clock] size-3" />
|
||||
{{ asset.stats.formattedDate }}
|
||||
</span>
|
||||
@@ -192,7 +204,7 @@ function confirmDeletion() {
|
||||
confirmText: t('g.delete'),
|
||||
// TODO: These need to be put into the new Button Variants once we have them.
|
||||
confirmClass: cn(
|
||||
'bg-danger-200 text-base-foreground hover:bg-danger-200/80 focus:bg-danger-200/80 focus:ring ring-base-foreground'
|
||||
'bg-danger-200 text-base-foreground ring-base-foreground hover:bg-danger-200/80 focus:bg-danger-200/80 focus:ring'
|
||||
),
|
||||
optionsDisabled,
|
||||
onCancel: () => {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex gap-4 items-center justify-between px-6 pt-2 pb-6"
|
||||
class="flex items-center justify-between gap-4 px-6 pt-2 pb-6"
|
||||
data-component-id="asset-filter-bar"
|
||||
>
|
||||
<div
|
||||
class="flex gap-4 items-center"
|
||||
class="flex items-center gap-4"
|
||||
data-component-id="asset-filter-bar-left"
|
||||
>
|
||||
<MultiSelect
|
||||
@@ -38,7 +38,10 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center" data-component-id="asset-filter-bar-right">
|
||||
<div
|
||||
class="flex items-center"
|
||||
data-component-id="asset-filter-bar-right"
|
||||
>
|
||||
<SingleSelect
|
||||
v-model="sortBy"
|
||||
:label="$t('assetBrowser.sortBy')"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user