Compare commits
7 Commits
devtools/r
...
v1.32.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
92968f3f9b | ||
|
|
73692464ef | ||
|
|
61b3ca046a | ||
|
|
f8912ebaf4 | ||
|
|
80b87c1277 | ||
|
|
00fa9b691b | ||
|
|
0cff8eb357 |
1
.github/workflows/release-version-bump.yaml
vendored
@@ -59,7 +59,6 @@ jobs:
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Bump version
|
||||
id: bump-version
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
import type { FullConfig } from '@playwright/test'
|
||||
import { config as loadEnv } from 'dotenv'
|
||||
import dotenv from 'dotenv'
|
||||
|
||||
import { backupPath } from './utils/backupUtils'
|
||||
import { syncDevtools } from './utils/devtoolsSync'
|
||||
|
||||
loadEnv()
|
||||
dotenv.config()
|
||||
|
||||
export default function globalSetup(_: FullConfig) {
|
||||
export default function globalSetup(config: FullConfig) {
|
||||
if (!process.env.CI) {
|
||||
if (process.env.TEST_COMFYUI_DIR) {
|
||||
backupPath([process.env.TEST_COMFYUI_DIR, 'user'])
|
||||
backupPath([process.env.TEST_COMFYUI_DIR, 'models'], {
|
||||
renameAndReplaceWithScaffolding: true
|
||||
})
|
||||
|
||||
syncDevtools(process.env.TEST_COMFYUI_DIR)
|
||||
} else {
|
||||
console.warn(
|
||||
'Set TEST_COMFYUI_DIR in .env to prevent user data (settings, workflows, etc.) from being overwritten'
|
||||
|
||||
|
Before Width: | Height: | Size: 119 KiB After Width: | Height: | Size: 125 KiB |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 115 KiB After Width: | Height: | Size: 119 KiB |
|
Before Width: | Height: | Size: 139 KiB After Width: | Height: | Size: 150 KiB |
|
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 142 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 119 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
@@ -1,52 +0,0 @@
|
||||
import fs from 'fs-extra'
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
export function syncDevtools(targetComfyDir: string): boolean {
|
||||
if (!targetComfyDir) {
|
||||
console.warn('syncDevtools skipped: TEST_COMFYUI_DIR not set')
|
||||
return false
|
||||
}
|
||||
|
||||
// Validate and sanitize the target directory path
|
||||
const resolvedTargetDir = path.resolve(targetComfyDir)
|
||||
|
||||
// Basic path validation to prevent directory traversal
|
||||
if (resolvedTargetDir.includes('..') || !path.isAbsolute(resolvedTargetDir)) {
|
||||
console.error('syncDevtools failed: Invalid target directory path')
|
||||
return false
|
||||
}
|
||||
|
||||
const moduleDir =
|
||||
typeof __dirname !== 'undefined'
|
||||
? __dirname
|
||||
: path.dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
const devtoolsSrc = path.resolve(moduleDir, '..', '..', 'tools', 'devtools')
|
||||
|
||||
if (!fs.pathExistsSync(devtoolsSrc)) {
|
||||
console.warn(
|
||||
`syncDevtools skipped: source directory not found at ${devtoolsSrc}`
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
const devtoolsDest = path.resolve(
|
||||
resolvedTargetDir,
|
||||
'custom_nodes',
|
||||
'ComfyUI_devtools'
|
||||
)
|
||||
|
||||
console.warn(`syncDevtools: copying ${devtoolsSrc} -> ${devtoolsDest}`)
|
||||
|
||||
try {
|
||||
fs.removeSync(devtoolsDest)
|
||||
fs.ensureDirSync(devtoolsDest)
|
||||
fs.copySync(devtoolsSrc, devtoolsDest, { overwrite: true })
|
||||
console.warn('syncDevtools: copy complete')
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error(`Failed to sync DevTools to ${devtoolsDest}:`, error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@comfyorg/comfyui-frontend",
|
||||
"private": true,
|
||||
"version": "1.32.5",
|
||||
"version": "1.32.6",
|
||||
"type": "module",
|
||||
"repository": "https://github.com/Comfy-Org/ComfyUI_frontend",
|
||||
"homepage": "https://comfy.org",
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</div>
|
||||
|
||||
<Panel
|
||||
class="pointer-events-auto z-1000"
|
||||
class="pointer-events-auto z-1010"
|
||||
:style="style"
|
||||
:class="panelClass"
|
||||
:pt="{
|
||||
|
||||
@@ -46,11 +46,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { normalizeI18nKey } from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import type { ComfyCommandImpl } from '@/stores/commandStore'
|
||||
import { normalizeI18nKey } from '@/utils/formatUtil'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
|
||||
@@ -47,7 +47,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { appendJsonExt } from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import InputText from 'primevue/inputtext'
|
||||
import type { MenuState } from 'primevue/menu'
|
||||
import Menu from 'primevue/menu'
|
||||
@@ -64,6 +63,7 @@ import {
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { useSubgraphNavigationStore } from '@/stores/subgraphNavigationStore'
|
||||
import { appendJsonExt } from '@/utils/formatUtil'
|
||||
|
||||
interface Props {
|
||||
item: MenuItem
|
||||
|
||||
@@ -12,9 +12,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { formatSize } from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
|
||||
import type { DeviceStats } from '@/schemas/apiSchema'
|
||||
import { formatSize } from '@/utils/formatUtil'
|
||||
|
||||
const props = defineProps<{
|
||||
device: DeviceStats
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { formatSize } from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import Button from 'primevue/button'
|
||||
import ProgressBar from 'primevue/progressbar'
|
||||
import { computed, ref } from 'vue'
|
||||
@@ -91,6 +90,7 @@ import { useI18n } from 'vue-i18n'
|
||||
import { useCopyToClipboard } from '@/composables/useCopyToClipboard'
|
||||
import { useDownload } from '@/composables/useDownload'
|
||||
import { useElectronDownloadStore } from '@/stores/electronDownloadStore'
|
||||
import { formatSize } from '@/utils/formatUtil'
|
||||
|
||||
const props = defineProps<{
|
||||
url: string
|
||||
|
||||
@@ -43,13 +43,13 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { formatSize } from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import Button from 'primevue/button'
|
||||
import Message from 'primevue/message'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { useCopyToClipboard } from '@/composables/useCopyToClipboard'
|
||||
import { useDownload } from '@/composables/useDownload'
|
||||
import { formatSize } from '@/utils/formatUtil'
|
||||
|
||||
const props = defineProps<{
|
||||
url: string
|
||||
|
||||
@@ -36,7 +36,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { formatSize } from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import Divider from 'primevue/divider'
|
||||
import TabPanel from 'primevue/tabpanel'
|
||||
import TabView from 'primevue/tabview'
|
||||
@@ -44,6 +43,7 @@ import { computed } from 'vue'
|
||||
|
||||
import DeviceInfo from '@/components/common/DeviceInfo.vue'
|
||||
import type { SystemStats } from '@/schemas/apiSchema'
|
||||
import { formatSize } from '@/utils/formatUtil'
|
||||
|
||||
const props = defineProps<{
|
||||
stats: SystemStats
|
||||
|
||||
@@ -23,12 +23,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { isValidUrl } from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import IconField from 'primevue/iconfield'
|
||||
import InputIcon from 'primevue/inputicon'
|
||||
import InputText from 'primevue/inputtext'
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
|
||||
import { isValidUrl } from '@/utils/formatUtil'
|
||||
import { checkUrlReachable } from '@/utils/networkUtil'
|
||||
import { ValidationState } from '@/utils/validationUtil'
|
||||
|
||||
|
||||
@@ -18,12 +18,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { formatMetronomeCurrency } from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import Skeleton from 'primevue/skeleton'
|
||||
import Tag from 'primevue/tag'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
|
||||
import { formatMetronomeCurrency } from '@/utils/formatUtil'
|
||||
|
||||
const { textClass } = defineProps<{
|
||||
textClass?: string
|
||||
|
||||
@@ -112,7 +112,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { formatMetronomeCurrency } from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import Button from 'primevue/button'
|
||||
import Column from 'primevue/column'
|
||||
import DataTable from 'primevue/datatable'
|
||||
@@ -130,6 +129,7 @@ import { useTelemetry } from '@/platform/telemetry'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
|
||||
import { formatMetronomeCurrency } from '@/utils/formatUtil'
|
||||
|
||||
interface CreditHistoryItemData {
|
||||
title: string
|
||||
|
||||
@@ -126,7 +126,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { normalizeI18nKey } from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import { FilterMatchMode } from '@primevue/core/api'
|
||||
import Button from 'primevue/button'
|
||||
import Column from 'primevue/column'
|
||||
@@ -147,6 +146,7 @@ import {
|
||||
KeybindingImpl,
|
||||
useKeybindingStore
|
||||
} from '@/stores/keybindingStore'
|
||||
import { normalizeI18nKey } from '@/utils/formatUtil'
|
||||
|
||||
import PanelTemplate from './PanelTemplate.vue'
|
||||
import KeyComboDisplay from './keybinding/KeyComboDisplay.vue'
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { normalizeI18nKey } from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import { useEventListener } from '@vueuse/core'
|
||||
import { nextTick, ref } from 'vue'
|
||||
|
||||
@@ -24,6 +23,7 @@ import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { app as comfyApp } from '@/scripts/app'
|
||||
import { isDOMWidget } from '@/scripts/domWidget'
|
||||
import { useNodeDefStore } from '@/stores/nodeDefStore'
|
||||
import { normalizeI18nKey } from '@/utils/formatUtil'
|
||||
|
||||
let idleTimeout: number
|
||||
const nodeDefStore = useNodeDefStore()
|
||||
|
||||
@@ -14,12 +14,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { normalizeI18nKey } from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import Button from 'primevue/button'
|
||||
|
||||
import { st } from '@/i18n'
|
||||
import type { ComfyCommand } from '@/stores/commandStore'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { normalizeI18nKey } from '@/utils/formatUtil'
|
||||
|
||||
defineProps<{
|
||||
command: ComfyCommand
|
||||
|
||||
@@ -12,12 +12,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { linkifyHtml, nl2br } from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import Skeleton from 'primevue/skeleton'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
|
||||
import type { NodeId } from '@/lib/litegraph/src/litegraph'
|
||||
import { useExecutionStore } from '@/stores/executionStore'
|
||||
import { linkifyHtml, nl2br } from '@/utils/formatUtil'
|
||||
|
||||
const modelValue = defineModel<string>({ required: true })
|
||||
const props = defineProps<{
|
||||
|
||||
@@ -137,7 +137,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { formatVersionAnchor } from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import Button from 'primevue/button'
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import type { CSSProperties, Component } from 'vue'
|
||||
@@ -152,6 +151,7 @@ import type { ReleaseNote } from '@/platform/updates/common/releaseService'
|
||||
import { useReleaseStore } from '@/platform/updates/common/releaseStore'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { electronAPI, isElectron } from '@/utils/envUtil'
|
||||
import { formatVersionAnchor } from '@/utils/formatUtil'
|
||||
import { useConflictAcknowledgment } from '@/workbench/extensions/manager/composables/useConflictAcknowledgment'
|
||||
import { useManagerState } from '@/workbench/extensions/manager/composables/useManagerState'
|
||||
import { ManagerTab } from '@/workbench/extensions/manager/types/comfyManagerTypes'
|
||||
|
||||
@@ -47,10 +47,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
formatNumberWithSuffix,
|
||||
highlightQuery
|
||||
} from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import Chip from 'primevue/chip'
|
||||
import Tag from 'primevue/tag'
|
||||
import { computed } from 'vue'
|
||||
@@ -60,6 +56,7 @@ import { useNodeBookmarkStore } from '@/stores/nodeBookmarkStore'
|
||||
import type { ComfyNodeDefImpl } from '@/stores/nodeDefStore'
|
||||
import { useNodeFrequencyStore } from '@/stores/nodeDefStore'
|
||||
import { NodeSourceType } from '@/types/nodeSource'
|
||||
import { formatNumberWithSuffix, highlightQuery } from '@/utils/formatUtil'
|
||||
|
||||
const settingStore = useSettingStore()
|
||||
const showCategory = computed(() =>
|
||||
|
||||
@@ -100,7 +100,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { normalizeI18nKey } from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import type { MenuItem } from 'primevue/menuitem'
|
||||
import TieredMenu from 'primevue/tieredmenu'
|
||||
import type { TieredMenuMethods, TieredMenuState } from 'primevue/tieredmenu'
|
||||
@@ -119,6 +118,7 @@ import { useCommandStore } from '@/stores/commandStore'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
import { useMenuItemStore } from '@/stores/menuItemStore'
|
||||
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
|
||||
import { normalizeI18nKey } from '@/utils/formatUtil'
|
||||
import { whileMouseDown } from '@/utils/mouseDownUtil'
|
||||
import { useManagerState } from '@/workbench/extensions/manager/composables/useManagerState'
|
||||
import { ManagerTab } from '@/workbench/extensions/manager/types/comfyManagerTypes'
|
||||
|
||||
@@ -145,10 +145,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
formatDuration,
|
||||
getMediaTypeFromFilename
|
||||
} from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import { useDebounceFn, useElementHover } from '@vueuse/core'
|
||||
import ProgressSpinner from 'primevue/progressspinner'
|
||||
import { useToast } from 'primevue/usetoast'
|
||||
@@ -174,6 +170,7 @@ import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
import { ResultItemImpl } from '@/stores/queueStore'
|
||||
import { formatDuration, getMediaTypeFromFilename } from '@/utils/formatUtil'
|
||||
|
||||
import AssetsSidebarTemplate from './AssetSidebarTemplate.vue'
|
||||
|
||||
|
||||
@@ -128,7 +128,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { appendJsonExt } from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import Button from 'primevue/button'
|
||||
import ConfirmDialog from 'primevue/confirmdialog'
|
||||
import { computed, nextTick, onMounted, ref } from 'vue'
|
||||
@@ -151,6 +150,7 @@ import {
|
||||
} from '@/platform/workflow/management/stores/workflowStore'
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore'
|
||||
import type { TreeExplorerNode, TreeNode } from '@/types/treeExplorerTypes'
|
||||
import { appendJsonExt } from '@/utils/formatUtil'
|
||||
import { buildTree, sortedTree } from '@/utils/treeUtil'
|
||||
|
||||
const settingStore = useSettingStore()
|
||||
|
||||
@@ -11,7 +11,7 @@ import { useTelemetry } from '@/platform/telemetry'
|
||||
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
|
||||
import { usdToMicros } from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import { usdToMicros } from '@/utils/formatUtil'
|
||||
|
||||
/**
|
||||
* Service for Firebase Auth actions.
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
/**
|
||||
* Composable for managing widget value synchronization between Vue and LiteGraph
|
||||
* Provides consistent pattern for immediate UI updates and LiteGraph callbacks
|
||||
*/
|
||||
import { computed, toValue, ref, watch } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
import type { SimplifiedWidget, WidgetValue } from '@/types/simplifiedWidget'
|
||||
import type { MaybeRefOrGetter } from '@vueuse/core'
|
||||
|
||||
interface UseWidgetValueOptions<T extends WidgetValue = WidgetValue, U = T> {
|
||||
/** The widget configuration from LiteGraph */
|
||||
widget: SimplifiedWidget<T>
|
||||
/** The current value from parent component (can be a value or a getter function) */
|
||||
modelValue: MaybeRefOrGetter<T>
|
||||
/** Default value if modelValue is null/undefined */
|
||||
defaultValue: T
|
||||
/** Emit function from component setup */
|
||||
emit: (event: 'update:modelValue', value: T) => void
|
||||
/** Optional value transformer before sending to LiteGraph */
|
||||
transform?: (value: U) => T
|
||||
}
|
||||
|
||||
interface UseWidgetValueReturn<T extends WidgetValue = WidgetValue, U = T> {
|
||||
/** Local value for immediate UI updates */
|
||||
localValue: Ref<T>
|
||||
/** Handler for user interactions */
|
||||
onChange: (newValue: U) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages widget value synchronization with LiteGraph
|
||||
*
|
||||
* @example
|
||||
* ```vue
|
||||
* const { localValue, onChange } = useWidgetValue({
|
||||
* widget: props.widget,
|
||||
* modelValue: props.modelValue,
|
||||
* defaultValue: ''
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export function useWidgetValue<T extends WidgetValue = WidgetValue, U = T>({
|
||||
widget,
|
||||
modelValue,
|
||||
defaultValue,
|
||||
emit,
|
||||
transform
|
||||
}: UseWidgetValueOptions<T, U>): UseWidgetValueReturn<T, U> {
|
||||
// Ref for immediate UI feedback before value flows back through modelValue
|
||||
const newProcessedValue = ref<T | null>(null)
|
||||
|
||||
// Computed that prefers the immediately processed value, then falls back to modelValue
|
||||
const localValue = computed<T>(
|
||||
() => newProcessedValue.value ?? toValue(modelValue) ?? defaultValue
|
||||
)
|
||||
|
||||
// Clear newProcessedValue when modelValue updates (allowing external changes to flow through)
|
||||
watch(
|
||||
() => toValue(modelValue),
|
||||
() => {
|
||||
newProcessedValue.value = null
|
||||
}
|
||||
)
|
||||
|
||||
// Handle user changes
|
||||
const onChange = (newValue: U) => {
|
||||
// Handle different PrimeVue component signatures
|
||||
let processedValue: T
|
||||
if (transform) {
|
||||
processedValue = transform(newValue)
|
||||
} else {
|
||||
// Ensure type safety - only cast when types are compatible
|
||||
if (
|
||||
typeof newValue === typeof defaultValue ||
|
||||
newValue === null ||
|
||||
newValue === undefined
|
||||
) {
|
||||
processedValue = (newValue ?? defaultValue) as T
|
||||
} else {
|
||||
console.warn(
|
||||
`useWidgetValue: Type mismatch for widget ${widget.name}. Expected ${typeof defaultValue}, got ${typeof newValue}`
|
||||
)
|
||||
processedValue = defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
// Set for immediate UI feedback
|
||||
newProcessedValue.value = processedValue
|
||||
|
||||
// Emit to parent component
|
||||
emit('update:modelValue', processedValue)
|
||||
}
|
||||
|
||||
return {
|
||||
localValue: localValue as Ref<T>,
|
||||
onChange
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Type-specific helper for string widgets
|
||||
*/
|
||||
export function useStringWidgetValue(
|
||||
widget: SimplifiedWidget<string>,
|
||||
modelValue: string | (() => string),
|
||||
emit: (event: 'update:modelValue', value: string) => void
|
||||
) {
|
||||
return useWidgetValue({
|
||||
widget,
|
||||
modelValue,
|
||||
defaultValue: '',
|
||||
emit,
|
||||
transform: (value: string | undefined) => String(value || '') // Handle undefined from PrimeVue
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Type-specific helper for number widgets
|
||||
*/
|
||||
export function useNumberWidgetValue(
|
||||
widget: SimplifiedWidget<number>,
|
||||
modelValue: number | (() => number),
|
||||
emit: (event: 'update:modelValue', value: number) => void
|
||||
) {
|
||||
return useWidgetValue({
|
||||
widget,
|
||||
modelValue,
|
||||
defaultValue: 0,
|
||||
emit,
|
||||
transform: (value: number | number[]) => {
|
||||
// Handle PrimeVue Slider which can emit number | number[]
|
||||
if (Array.isArray(value)) {
|
||||
return value.length > 0 ? (value[0] ?? 0) : 0
|
||||
}
|
||||
return Number(value) || 0
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Type-specific helper for boolean widgets
|
||||
*/
|
||||
export function useBooleanWidgetValue(
|
||||
widget: SimplifiedWidget<boolean>,
|
||||
modelValue: boolean | (() => boolean),
|
||||
emit: (event: 'update:modelValue', value: boolean) => void
|
||||
) {
|
||||
return useWidgetValue({
|
||||
widget,
|
||||
modelValue,
|
||||
defaultValue: false,
|
||||
emit,
|
||||
transform: (value: boolean) => Boolean(value)
|
||||
})
|
||||
}
|
||||
@@ -1537,6 +1537,8 @@ const apiNodeCosts: Record<string, { displayPrice: string | PricingFunction }> =
|
||||
return '$0.00125/$0.01 per 1K tokens'
|
||||
} else if (model.includes('gemini-2.5-pro')) {
|
||||
return '$0.00125/$0.01 per 1K tokens'
|
||||
} else if (model.includes('gemini-3-pro-preview')) {
|
||||
return '$0.002/$0.012 per 1K tokens'
|
||||
}
|
||||
// For other Gemini models, show token-based pricing info
|
||||
return 'Token-based'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import QuickLRU from '@alloc/quick-lru'
|
||||
|
||||
import { paramsToCacheKey } from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import { paramsToCacheKey } from '@/utils/formatUtil'
|
||||
|
||||
const DEFAULT_MAX_SIZE = 50
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import type {
|
||||
} from '@/lib/litegraph/src/litegraph'
|
||||
import { LGraphCanvas, LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
import { app } from '@/scripts/app'
|
||||
import { normalizeI18nKey } from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import { normalizeI18nKey } from '@/utils/formatUtil'
|
||||
|
||||
/**
|
||||
* Add translation for litegraph context menu.
|
||||
|
||||
@@ -2,10 +2,7 @@ import { whenever } from '@vueuse/core'
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
import { useCivitaiModel } from '@/composables/useCivitaiModel'
|
||||
import {
|
||||
downloadUrlToHfRepoUrl,
|
||||
isCivitaiModelUrl
|
||||
} from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import { downloadUrlToHfRepoUrl, isCivitaiModelUrl } from '@/utils/formatUtil'
|
||||
|
||||
export function useDownload(url: string, fileName?: string) {
|
||||
const fileSize = ref<number | null>(null)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useExtensionService } from '@/services/extensionService'
|
||||
import { processDynamicPrompt } from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import { processDynamicPrompt } from '@/utils/formatUtil'
|
||||
|
||||
// Allows for simple dynamic prompt replacement
|
||||
// Inputs in the format {a|b} will have a random value of a or b chosen when the prompt is queued.
|
||||
|
||||
@@ -22,9 +22,12 @@ useExtensionService().registerExtension({
|
||||
'preview',
|
||||
['STRING', { multiline: true }],
|
||||
app
|
||||
).widget as DOMWidget<any, any>
|
||||
).widget as DOMWidget<HTMLTextAreaElement, string>
|
||||
|
||||
showValueWidget.options.read_only = true
|
||||
|
||||
showValueWidget.element.readOnly = true
|
||||
showValueWidget.element.disabled = true
|
||||
|
||||
showValueWidget.serialize = false
|
||||
}
|
||||
|
||||
@@ -155,13 +155,6 @@
|
||||
"duplicate": "تكرار",
|
||||
"enterNewName": "أدخل اسمًا جديدًا"
|
||||
},
|
||||
"chatHistory": {
|
||||
"cancelEdit": "إلغاء",
|
||||
"cancelEditTooltip": "إلغاء التعديل",
|
||||
"copiedTooltip": "تم النسخ",
|
||||
"copyTooltip": "نسخ الرسالة إلى الحافظة",
|
||||
"editTooltip": "تعديل الرسالة"
|
||||
},
|
||||
"clipboard": {
|
||||
"errorMessage": "فشل النسخ إلى الحافظة",
|
||||
"errorNotSupported": "API الحافظة غير مدعوم في متصفحك",
|
||||
@@ -965,13 +958,6 @@
|
||||
"title": "عارض ثلاثي الأبعاد (بيتا)"
|
||||
}
|
||||
},
|
||||
"loadWorkflowWarning": {
|
||||
"coreNodesFromVersion": "يتطلب ComfyUI {version}:",
|
||||
"missingNodesDescription": "عند تحميل الرسم البياني، لم يتم العثور على العقد التالية.\nقد يحدث هذا أيضًا إذا كانت إصدار التثبيت المثبت أقل وأن هذا النوع من العقد غير موجود.",
|
||||
"missingNodesTitle": "بعض العقد مفقودة",
|
||||
"outdatedVersion": "بعض العقد تتطلب إصدار أحدث من ComfyUI (الحالي: {version}). يرجى التحديث لاستخدام جميع العقد.",
|
||||
"outdatedVersionGeneric": "بعض العقد تتطلب إصدار أحدث من ComfyUI. يرجى التحديث لاستخدام جميع العقد."
|
||||
},
|
||||
"maintenance": {
|
||||
"None": "لا شيء",
|
||||
"OK": "حسنًا",
|
||||
@@ -1101,29 +1087,6 @@
|
||||
"version": "الإصدار"
|
||||
},
|
||||
"maskEditor": {
|
||||
"Apply to Whole Image": "تطبيق على كامل الصورة",
|
||||
"Brush Settings": "إعدادات الفرشاة",
|
||||
"Brush Shape": "شكل الفرشاة",
|
||||
"Clear": "مسح",
|
||||
"Color Select Settings": "إعدادات اختيار اللون",
|
||||
"Fill Opacity": "شفافية التعبئة",
|
||||
"Hardness": "الصلابة",
|
||||
"Image Layer": "طبقة الصورة",
|
||||
"Invert": "عكس",
|
||||
"Layers": "الطبقات",
|
||||
"Live Preview": "معاينة حية",
|
||||
"Mask Layer": "طبقة القناع",
|
||||
"Mask Opacity": "شفافية القناع",
|
||||
"Mask Tolerance": "تسامح القناع",
|
||||
"Method": "الطريقة",
|
||||
"Opacity": "الشفافية",
|
||||
"Paint Bucket Settings": "إعدادات دلو الطلاء",
|
||||
"Reset to Default": "إعادة إلى الافتراضي",
|
||||
"Selection Opacity": "شفافية التحديد",
|
||||
"Smoothing Precision": "دقة التنعيم",
|
||||
"Stop at mask": "التوقف عند القناع",
|
||||
"Thickness": "السماكة",
|
||||
"Tolerance": "التسامح"
|
||||
},
|
||||
"mediaAsset": {
|
||||
"assetDeletedSuccessfully": "تم حذف الأصل بنجاح",
|
||||
@@ -1189,7 +1152,6 @@
|
||||
"Canvas Performance": "أداء اللوحة",
|
||||
"Canvas Toggle Lock": "تبديل قفل اللوحة",
|
||||
"Check for Custom Node Updates": "التحقق من تحديثات العقد المخصصة",
|
||||
"Check for Updates": "التحقق من التحديثات",
|
||||
"Clear Pending Tasks": "مسح المهام المعلقة",
|
||||
"Clear Workflow": "مسح سير العمل",
|
||||
"Clipspace": "مساحة القص",
|
||||
@@ -1206,7 +1168,6 @@
|
||||
"Custom Nodes Manager": "مدير العقد المخصصة",
|
||||
"Decrease Brush Size in MaskEditor": "تقليل حجم الفرشاة في محرر القناع",
|
||||
"Delete Selected Items": "حذف العناصر المحددة",
|
||||
"Desktop User Guide": "دليل المستخدم لسطح المكتب",
|
||||
"Duplicate Current Workflow": "نسخ سير العمل الحالي",
|
||||
"Edit": "تحرير",
|
||||
"Edit Subgraph Widgets": "تحرير عناصر واجهة المستخدم للرسم البياني الفرعي",
|
||||
@@ -1243,15 +1204,8 @@
|
||||
"Node Links": "روابط العقد",
|
||||
"Open": "فتح",
|
||||
"Open 3D Viewer (Beta) for Selected Node": "فتح عارض ثلاثي الأبعاد (بيتا) للعقدة المحددة",
|
||||
"Open Custom Nodes Folder": "فتح مجلد العقد المخصصة",
|
||||
"Open DevTools": "فتح أدوات المطور",
|
||||
"Open Inputs Folder": "فتح مجلد المدخلات",
|
||||
"Open Logs Folder": "فتح مجلد السجلات",
|
||||
"Open Mask Editor for Selected Node": "فتح محرر القناع للعقدة المحددة",
|
||||
"Open Models Folder": "فتح مجلد النماذج",
|
||||
"Open Outputs Folder": "فتح مجلد المخرجات",
|
||||
"Open Sign In Dialog": "فتح نافذة تسجيل الدخول",
|
||||
"Open extra_model_paths_yaml": "فتح ملف extra_model_paths.yaml",
|
||||
"Pin/Unpin Selected Items": "تثبيت/إلغاء تثبيت العناصر المحددة",
|
||||
"Pin/Unpin Selected Nodes": "تثبيت/إلغاء تثبيت العقد المحددة",
|
||||
"Previous Opened Workflow": "سير العمل السابق المفتوح",
|
||||
@@ -1260,13 +1214,10 @@
|
||||
"Queue Prompt": "قائمة انتظار التعليمات",
|
||||
"Queue Prompt (Front)": "قائمة انتظار التعليمات (أمامي)",
|
||||
"Queue Selected Output Nodes": "قائمة انتظار عقد المخرجات المحددة",
|
||||
"Quit": "خروج",
|
||||
"Redo": "إعادة",
|
||||
"Refresh Node Definitions": "تحديث تعريفات العقد",
|
||||
"Reinstall": "إعادة التثبيت",
|
||||
"Reset View": "إعادة تعيين العرض",
|
||||
"Resize Selected Nodes": "تغيير حجم العقد المحددة",
|
||||
"Restart": "إعادة التشغيل",
|
||||
"Save": "حفظ",
|
||||
"Save As": "حفظ باسم",
|
||||
"Show Keybindings Dialog": "عرض مربع حوار اختصارات لوحة المفاتيح",
|
||||
|
||||
@@ -1,40 +1,4 @@
|
||||
{
|
||||
"Comfy-Desktop_CheckForUpdates": {
|
||||
"label": "Check for Updates"
|
||||
},
|
||||
"Comfy-Desktop_Folders_OpenCustomNodesFolder": {
|
||||
"label": "Open Custom Nodes Folder"
|
||||
},
|
||||
"Comfy-Desktop_Folders_OpenInputsFolder": {
|
||||
"label": "Open Inputs Folder"
|
||||
},
|
||||
"Comfy-Desktop_Folders_OpenLogsFolder": {
|
||||
"label": "Open Logs Folder"
|
||||
},
|
||||
"Comfy-Desktop_Folders_OpenModelConfig": {
|
||||
"label": "Open extra_model_paths.yaml"
|
||||
},
|
||||
"Comfy-Desktop_Folders_OpenModelsFolder": {
|
||||
"label": "Open Models Folder"
|
||||
},
|
||||
"Comfy-Desktop_Folders_OpenOutputsFolder": {
|
||||
"label": "Open Outputs Folder"
|
||||
},
|
||||
"Comfy-Desktop_OpenDevTools": {
|
||||
"label": "Open DevTools"
|
||||
},
|
||||
"Comfy-Desktop_OpenUserGuide": {
|
||||
"label": "Desktop User Guide"
|
||||
},
|
||||
"Comfy-Desktop_Quit": {
|
||||
"label": "Quit"
|
||||
},
|
||||
"Comfy-Desktop_Reinstall": {
|
||||
"label": "Reinstall"
|
||||
},
|
||||
"Comfy-Desktop_Restart": {
|
||||
"label": "Restart"
|
||||
},
|
||||
"Comfy_3DViewer_Open3DViewer": {
|
||||
"label": "Open 3D Viewer (Beta) for Selected Node"
|
||||
},
|
||||
|
||||
@@ -945,18 +945,6 @@
|
||||
"Edit": "Edit",
|
||||
"View": "View",
|
||||
"Help": "Help",
|
||||
"Check for Updates": "Check for Updates",
|
||||
"Open Custom Nodes Folder": "Open Custom Nodes Folder",
|
||||
"Open Inputs Folder": "Open Inputs Folder",
|
||||
"Open Logs Folder": "Open Logs Folder",
|
||||
"Open extra_model_paths_yaml": "Open extra_model_paths.yaml",
|
||||
"Open Models Folder": "Open Models Folder",
|
||||
"Open Outputs Folder": "Open Outputs Folder",
|
||||
"Open DevTools": "Open DevTools",
|
||||
"Desktop User Guide": "Desktop User Guide",
|
||||
"Quit": "Quit",
|
||||
"Reinstall": "Reinstall",
|
||||
"Restart": "Restart",
|
||||
"Open 3D Viewer (Beta) for Selected Node": "Open 3D Viewer (Beta) for Selected Node",
|
||||
"Experimental: Browse Model Assets": "Experimental: Browse Model Assets",
|
||||
"Browse Templates": "Browse Templates",
|
||||
@@ -1335,6 +1323,7 @@
|
||||
"Tripo": "Tripo",
|
||||
"Veo": "Veo",
|
||||
"Vidu": "Vidu",
|
||||
"": "",
|
||||
"camera": "camera",
|
||||
"Wan": "Wan"
|
||||
},
|
||||
@@ -2089,4 +2078,4 @@
|
||||
"replacementInstruction": "Install these nodes to run this workflow, or replace them with installed alternatives. Missing nodes are highlighted in red on the canvas."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2570,7 +2570,7 @@
|
||||
}
|
||||
},
|
||||
"GeminiImageNode": {
|
||||
"display_name": "Google Gemini Image",
|
||||
"display_name": "Nano Banana (Google Gemini Image)",
|
||||
"description": "Edit images synchronously via Google API.",
|
||||
"inputs": {
|
||||
"prompt": {
|
||||
@@ -12770,6 +12770,20 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"wanBlockSwap": {
|
||||
"display_name": "wanBlockSwap",
|
||||
"description": "NOP",
|
||||
"inputs": {
|
||||
"model": {
|
||||
"name": "model"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"WanCameraEmbedding": {
|
||||
"display_name": "WanCameraEmbedding",
|
||||
"inputs": {
|
||||
|
||||
@@ -1,30 +1,4 @@
|
||||
{
|
||||
"Comfy-Desktop_AutoUpdate": {
|
||||
"name": "Automatically check for updates"
|
||||
},
|
||||
"Comfy-Desktop_SendStatistics": {
|
||||
"name": "Send anonymous usage metrics"
|
||||
},
|
||||
"Comfy-Desktop_UV_PypiInstallMirror": {
|
||||
"name": "Pypi Install Mirror",
|
||||
"tooltip": "Default pip install mirror"
|
||||
},
|
||||
"Comfy-Desktop_UV_PythonInstallMirror": {
|
||||
"name": "Python Install Mirror",
|
||||
"tooltip": "Managed Python installations are downloaded from the Astral python-build-standalone project. This variable can be set to a mirror URL to use a different source for Python installations. The provided URL will replace https://github.com/astral-sh/python-build-standalone/releases/download in, e.g., https://github.com/astral-sh/python-build-standalone/releases/download/20240713/cpython-3.12.4%2B20240713-aarch64-apple-darwin-install_only.tar.gz. Distributions can be read from a local directory by using the file:// URL scheme."
|
||||
},
|
||||
"Comfy-Desktop_UV_TorchInstallMirror": {
|
||||
"name": "Torch Install Mirror",
|
||||
"tooltip": "Pip install mirror for pytorch"
|
||||
},
|
||||
"Comfy-Desktop_WindowStyle": {
|
||||
"name": "Window Style",
|
||||
"tooltip": "Custom: Replace the system title bar with ComfyUI's Top menu",
|
||||
"options": {
|
||||
"default": "default",
|
||||
"custom": "custom"
|
||||
}
|
||||
},
|
||||
"Comfy_Canvas_BackgroundImage": {
|
||||
"name": "Canvas background image",
|
||||
"tooltip": "Image URL for the canvas background. You can right-click an image in the outputs panel and select \"Set as Background\" to use it, or upload your own image using the upload button."
|
||||
|
||||
@@ -155,13 +155,6 @@
|
||||
"duplicate": "Duplicar",
|
||||
"enterNewName": "Ingrese un nuevo nombre"
|
||||
},
|
||||
"chatHistory": {
|
||||
"cancelEdit": "Cancelar",
|
||||
"cancelEditTooltip": "Cancelar edición",
|
||||
"copiedTooltip": "Copiado",
|
||||
"copyTooltip": "Copiar mensaje al portapapeles",
|
||||
"editTooltip": "Editar mensaje"
|
||||
},
|
||||
"clipboard": {
|
||||
"errorMessage": "Error al copiar al portapapeles",
|
||||
"errorNotSupported": "API del portapapeles no soportada en su navegador",
|
||||
@@ -965,13 +958,6 @@
|
||||
"title": "Visor 3D (Beta)"
|
||||
}
|
||||
},
|
||||
"loadWorkflowWarning": {
|
||||
"coreNodesFromVersion": "Requiere ComfyUI {version}:",
|
||||
"missingNodesDescription": "Al cargar el grafo, no se encontraron los siguientes tipos de nodos.\nEsto también puede ocurrir si tu versión instalada es anterior y ese tipo de nodo no se puede encontrar.",
|
||||
"missingNodesTitle": "Faltan Algunos Nodos",
|
||||
"outdatedVersion": "Algunos nodos requieren una versión más reciente de ComfyUI (actual: {version}). Por favor, actualiza para usar todos los nodos.",
|
||||
"outdatedVersionGeneric": "Algunos nodos requieren una versión más reciente de ComfyUI. Por favor, actualiza para usar todos los nodos."
|
||||
},
|
||||
"maintenance": {
|
||||
"None": "Ninguno",
|
||||
"OK": "OK",
|
||||
@@ -1101,29 +1087,6 @@
|
||||
"version": "Versión"
|
||||
},
|
||||
"maskEditor": {
|
||||
"Apply to Whole Image": "Aplicar a toda la imagen",
|
||||
"Brush Settings": "Configuración de pincel",
|
||||
"Brush Shape": "Forma de pincel",
|
||||
"Clear": "Borrar",
|
||||
"Color Select Settings": "Configuración de selección de color",
|
||||
"Fill Opacity": "Opacidad de relleno",
|
||||
"Hardness": "Dureza",
|
||||
"Image Layer": "Capa de imagen",
|
||||
"Invert": "Invertir",
|
||||
"Layers": "Capas",
|
||||
"Live Preview": "Vista previa en vivo",
|
||||
"Mask Layer": "Capa de máscara",
|
||||
"Mask Opacity": "Opacidad de máscara",
|
||||
"Mask Tolerance": "Tolerancia de máscara",
|
||||
"Method": "Método",
|
||||
"Opacity": "Opacidad",
|
||||
"Paint Bucket Settings": "Configuración de cubo de pintura",
|
||||
"Reset to Default": "Restablecer a predeterminado",
|
||||
"Selection Opacity": "Opacidad de selección",
|
||||
"Smoothing Precision": "Precisión de suavizado",
|
||||
"Stop at mask": "Detener en máscara",
|
||||
"Thickness": "Grosor",
|
||||
"Tolerance": "Tolerancia"
|
||||
},
|
||||
"mediaAsset": {
|
||||
"assetDeletedSuccessfully": "Recurso eliminado exitosamente",
|
||||
@@ -1189,7 +1152,6 @@
|
||||
"Canvas Performance": "Rendimiento del Lienzo",
|
||||
"Canvas Toggle Lock": "Alternar bloqueo en lienzo",
|
||||
"Check for Custom Node Updates": "Buscar actualizaciones de nodos personalizados",
|
||||
"Check for Updates": "Buscar actualizaciones",
|
||||
"Clear Pending Tasks": "Borrar tareas pendientes",
|
||||
"Clear Workflow": "Borrar flujo de trabajo",
|
||||
"Clipspace": "Espacio de clip",
|
||||
@@ -1206,7 +1168,6 @@
|
||||
"Custom Nodes Manager": "Administrador de Nodos Personalizados",
|
||||
"Decrease Brush Size in MaskEditor": "Disminuir tamaño del pincel en MaskEditor",
|
||||
"Delete Selected Items": "Eliminar elementos seleccionados",
|
||||
"Desktop User Guide": "Guía de usuario de escritorio",
|
||||
"Duplicate Current Workflow": "Duplicar flujo de trabajo actual",
|
||||
"Edit": "Editar",
|
||||
"Edit Subgraph Widgets": "Editar widgets de subgrafo",
|
||||
@@ -1243,15 +1204,8 @@
|
||||
"Node Links": "Enlaces de nodos",
|
||||
"Open": "Abrir",
|
||||
"Open 3D Viewer (Beta) for Selected Node": "Abrir Visor 3D (Beta) para Nodo Seleccionado",
|
||||
"Open Custom Nodes Folder": "Abrir carpeta de nodos personalizados",
|
||||
"Open DevTools": "Abrir DevTools",
|
||||
"Open Inputs Folder": "Abrir carpeta de entradas",
|
||||
"Open Logs Folder": "Abrir carpeta de registros",
|
||||
"Open Mask Editor for Selected Node": "Abrir el editor de mask para el nodo seleccionado",
|
||||
"Open Models Folder": "Abrir carpeta de modelos",
|
||||
"Open Outputs Folder": "Abrir carpeta de salidas",
|
||||
"Open Sign In Dialog": "Abrir diálogo de inicio de sesión",
|
||||
"Open extra_model_paths_yaml": "Abrir extra_model_paths.yaml",
|
||||
"Pin/Unpin Selected Items": "Anclar/Desanclar elementos seleccionados",
|
||||
"Pin/Unpin Selected Nodes": "Anclar/Desanclar nodos seleccionados",
|
||||
"Previous Opened Workflow": "Flujo de trabajo abierto anterior",
|
||||
@@ -1260,13 +1214,10 @@
|
||||
"Queue Prompt": "Indicador de cola",
|
||||
"Queue Prompt (Front)": "Indicador de cola (Frente)",
|
||||
"Queue Selected Output Nodes": "Encolar nodos de salida seleccionados",
|
||||
"Quit": "Salir",
|
||||
"Redo": "Rehacer",
|
||||
"Refresh Node Definitions": "Actualizar definiciones de nodo",
|
||||
"Reinstall": "Reinstalar",
|
||||
"Reset View": "Restablecer vista",
|
||||
"Resize Selected Nodes": "Redimensionar Nodos Seleccionados",
|
||||
"Restart": "Reiniciar",
|
||||
"Save": "Guardar",
|
||||
"Save As": "Guardar como",
|
||||
"Show Keybindings Dialog": "Mostrar diálogo de combinaciones de teclas",
|
||||
|
||||
@@ -155,13 +155,6 @@
|
||||
"duplicate": "Dupliquer",
|
||||
"enterNewName": "Entrez un nouveau nom"
|
||||
},
|
||||
"chatHistory": {
|
||||
"cancelEdit": "Annuler",
|
||||
"cancelEditTooltip": "Annuler la modification",
|
||||
"copiedTooltip": "Copié",
|
||||
"copyTooltip": "Copier le message dans le presse-papiers",
|
||||
"editTooltip": "Modifier le message"
|
||||
},
|
||||
"clipboard": {
|
||||
"errorMessage": "Échec de la copie dans le presse-papiers",
|
||||
"errorNotSupported": "L'API du presse-papiers n'est pas prise en charge par votre navigateur",
|
||||
@@ -965,13 +958,6 @@
|
||||
"title": "Visualiseur 3D (Bêta)"
|
||||
}
|
||||
},
|
||||
"loadWorkflowWarning": {
|
||||
"coreNodesFromVersion": "Nécessite ComfyUI {version} :",
|
||||
"missingNodesDescription": "Lors du chargement du graphe, les types de nœuds suivants n'ont pas été trouvés.\nCela peut également se produire si votre version installée est inférieure et que ce type de nœud ne peut pas être trouvé.",
|
||||
"missingNodesTitle": "Certains nœuds sont manquants",
|
||||
"outdatedVersion": "Certains nœuds nécessitent une version plus récente de ComfyUI (actuelle : {version}). Veuillez mettre à jour pour utiliser tous les nœuds.",
|
||||
"outdatedVersionGeneric": "Certains nœuds nécessitent une version plus récente de ComfyUI. Veuillez mettre à jour pour utiliser tous les nœuds."
|
||||
},
|
||||
"maintenance": {
|
||||
"None": "Aucun",
|
||||
"OK": "OK",
|
||||
@@ -1101,29 +1087,6 @@
|
||||
"version": "Version"
|
||||
},
|
||||
"maskEditor": {
|
||||
"Apply to Whole Image": "Appliquer à toute l'image",
|
||||
"Brush Settings": "Paramètres de brosse",
|
||||
"Brush Shape": "Forme de brosse",
|
||||
"Clear": "Effacer",
|
||||
"Color Select Settings": "Paramètres de sélection de couleur",
|
||||
"Fill Opacity": "Opacité de remplissage",
|
||||
"Hardness": "Dureté",
|
||||
"Image Layer": "Couche d'image",
|
||||
"Invert": "Inverser",
|
||||
"Layers": "Couches",
|
||||
"Live Preview": "Aperçu en direct",
|
||||
"Mask Layer": "Couche de masque",
|
||||
"Mask Opacity": "Opacité du masque",
|
||||
"Mask Tolerance": "Tolérance du masque",
|
||||
"Method": "Méthode",
|
||||
"Opacity": "Opacité",
|
||||
"Paint Bucket Settings": "Paramètres du seau de peinture",
|
||||
"Reset to Default": "Réinitialiser par défaut",
|
||||
"Selection Opacity": "Opacité de sélection",
|
||||
"Smoothing Precision": "Précision de lissage",
|
||||
"Stop at mask": "Arrêter au masque",
|
||||
"Thickness": "Épaisseur",
|
||||
"Tolerance": "Tolérance"
|
||||
},
|
||||
"mediaAsset": {
|
||||
"assetDeletedSuccessfully": "Élément supprimé avec succès",
|
||||
@@ -1189,7 +1152,6 @@
|
||||
"Canvas Performance": "Performances du canevas",
|
||||
"Canvas Toggle Lock": "Basculer le verrouillage de la toile",
|
||||
"Check for Custom Node Updates": "Vérifier les mises à jour des nœuds personnalisés",
|
||||
"Check for Updates": "Vérifier les mises à jour",
|
||||
"Clear Pending Tasks": "Effacer les tâches en attente",
|
||||
"Clear Workflow": "Effacer le flux de travail",
|
||||
"Clipspace": "Espace de clip",
|
||||
@@ -1206,7 +1168,6 @@
|
||||
"Custom Nodes Manager": "Gestionnaire de Nœuds Personnalisés",
|
||||
"Decrease Brush Size in MaskEditor": "Réduire la taille du pinceau dans MaskEditor",
|
||||
"Delete Selected Items": "Supprimer les éléments sélectionnés",
|
||||
"Desktop User Guide": "Guide de l'utilisateur de bureau",
|
||||
"Duplicate Current Workflow": "Dupliquer le flux de travail actuel",
|
||||
"Edit": "Éditer",
|
||||
"Edit Subgraph Widgets": "Modifier les widgets de sous-graphe",
|
||||
@@ -1243,15 +1204,8 @@
|
||||
"Node Links": "Liens de nœuds",
|
||||
"Open": "Ouvrir",
|
||||
"Open 3D Viewer (Beta) for Selected Node": "Ouvrir le visualiseur 3D (Bêta) pour le nœud sélectionné",
|
||||
"Open Custom Nodes Folder": "Ouvrir le dossier des nœuds personnalisés",
|
||||
"Open DevTools": "Ouvrir DevTools",
|
||||
"Open Inputs Folder": "Ouvrir le dossier des entrées",
|
||||
"Open Logs Folder": "Ouvrir le dossier des journaux",
|
||||
"Open Mask Editor for Selected Node": "Ouvrir l’éditeur de mask pour le nœud sélectionné",
|
||||
"Open Models Folder": "Ouvrir le dossier des modèles",
|
||||
"Open Outputs Folder": "Ouvrir le dossier des sorties",
|
||||
"Open Sign In Dialog": "Ouvrir la boîte de dialogue de connexion",
|
||||
"Open extra_model_paths_yaml": "Ouvrir extra_model_paths.yaml",
|
||||
"Pin/Unpin Selected Items": "Épingler/Désépingler les éléments sélectionnés",
|
||||
"Pin/Unpin Selected Nodes": "Épingler/Désépingler les nœuds sélectionnés",
|
||||
"Previous Opened Workflow": "Flux de travail ouvert précédent",
|
||||
@@ -1260,13 +1214,10 @@
|
||||
"Queue Prompt": "Invite de file d'attente",
|
||||
"Queue Prompt (Front)": "Invite de file d'attente (Front)",
|
||||
"Queue Selected Output Nodes": "Mettre en file d’attente les nœuds de sortie sélectionnés",
|
||||
"Quit": "Quitter",
|
||||
"Redo": "Refaire",
|
||||
"Refresh Node Definitions": "Actualiser les définitions de nœud",
|
||||
"Reinstall": "Réinstaller",
|
||||
"Reset View": "Réinitialiser la vue",
|
||||
"Resize Selected Nodes": "Redimensionner les nœuds sélectionnés",
|
||||
"Restart": "Redémarrer",
|
||||
"Save": "Enregistrer",
|
||||
"Save As": "Enregistrer sous",
|
||||
"Show Keybindings Dialog": "Afficher la boîte de dialogue des raccourcis clavier",
|
||||
|
||||
@@ -155,13 +155,6 @@
|
||||
"duplicate": "複製",
|
||||
"enterNewName": "新しい名前を入力"
|
||||
},
|
||||
"chatHistory": {
|
||||
"cancelEdit": "キャンセル",
|
||||
"cancelEditTooltip": "編集をキャンセル",
|
||||
"copiedTooltip": "コピーしました",
|
||||
"copyTooltip": "メッセージをクリップボードにコピー",
|
||||
"editTooltip": "メッセージを編集"
|
||||
},
|
||||
"clipboard": {
|
||||
"errorMessage": "クリップボードへのコピーに失敗しました",
|
||||
"errorNotSupported": "お使いのブラウザではクリップボードAPIがサポートされていません",
|
||||
@@ -965,13 +958,6 @@
|
||||
"title": "3Dビューア(ベータ)"
|
||||
}
|
||||
},
|
||||
"loadWorkflowWarning": {
|
||||
"coreNodesFromVersion": "ComfyUI {version} が必要です:",
|
||||
"missingNodesDescription": "グラフを読み込む際、以下のノードタイプが見つかりませんでした。\nインストールされているバージョンが古く、そのノードタイプが存在しない場合にも発生することがあります。",
|
||||
"missingNodesTitle": "ノードが見つかりません",
|
||||
"outdatedVersion": "一部のノードはより新しいバージョンのComfyUIが必要です(現在のバージョン:{version})。すべてのノードを使用するにはアップデートしてください。",
|
||||
"outdatedVersionGeneric": "一部のノードはより新しいバージョンのComfyUIが必要です。すべてのノードを使用するにはアップデートしてください。"
|
||||
},
|
||||
"maintenance": {
|
||||
"None": "なし",
|
||||
"OK": "OK",
|
||||
@@ -1101,29 +1087,6 @@
|
||||
"version": "バージョン"
|
||||
},
|
||||
"maskEditor": {
|
||||
"Apply to Whole Image": "全画像に適用",
|
||||
"Brush Settings": "ブラシ設定",
|
||||
"Brush Shape": "ブラシ形状",
|
||||
"Clear": "クリア",
|
||||
"Color Select Settings": "色選択設定",
|
||||
"Fill Opacity": "塗りつぶしの不透明度",
|
||||
"Hardness": "硬さ",
|
||||
"Image Layer": "画像レイヤー",
|
||||
"Invert": "反転",
|
||||
"Layers": "レイヤー",
|
||||
"Live Preview": "ライブプレビュー",
|
||||
"Mask Layer": "マスクレイヤー",
|
||||
"Mask Opacity": "マスクの不透明度",
|
||||
"Mask Tolerance": "マスクの許容範囲",
|
||||
"Method": "方法",
|
||||
"Opacity": "不透明度",
|
||||
"Paint Bucket Settings": "ペイントバケツ設定",
|
||||
"Reset to Default": "デフォルトにリセット",
|
||||
"Selection Opacity": "選択範囲の不透明度",
|
||||
"Smoothing Precision": "スムージング精度",
|
||||
"Stop at mask": "マスクで停止",
|
||||
"Thickness": "厚さ",
|
||||
"Tolerance": "許容範囲"
|
||||
},
|
||||
"mediaAsset": {
|
||||
"assetDeletedSuccessfully": "アセットが正常に削除されました",
|
||||
@@ -1189,7 +1152,6 @@
|
||||
"Canvas Performance": "キャンバスのパフォーマンス",
|
||||
"Canvas Toggle Lock": "キャンバスのロックを切り替え",
|
||||
"Check for Custom Node Updates": "カスタムノードのアップデートを確認",
|
||||
"Check for Updates": "更新を確認する",
|
||||
"Clear Pending Tasks": "保留中のタスクをクリア",
|
||||
"Clear Workflow": "ワークフローをクリア",
|
||||
"Clipspace": "クリップスペース",
|
||||
@@ -1206,7 +1168,6 @@
|
||||
"Custom Nodes Manager": "カスタムノードマネージャ",
|
||||
"Decrease Brush Size in MaskEditor": "マスクエディタでブラシサイズを小さくする",
|
||||
"Delete Selected Items": "選択したアイテムを削除",
|
||||
"Desktop User Guide": "デスクトップユーザーガイド",
|
||||
"Duplicate Current Workflow": "現在のワークフローを複製",
|
||||
"Edit": "編集",
|
||||
"Edit Subgraph Widgets": "サブグラフウィジェットを編集",
|
||||
@@ -1243,15 +1204,8 @@
|
||||
"Node Links": "ノードリンク",
|
||||
"Open": "開く",
|
||||
"Open 3D Viewer (Beta) for Selected Node": "選択したノードの3Dビューア(ベータ版)を開く",
|
||||
"Open Custom Nodes Folder": "カスタムノードフォルダを開く",
|
||||
"Open DevTools": "DevToolsを開く",
|
||||
"Open Inputs Folder": "入力フォルダを開く",
|
||||
"Open Logs Folder": "ログフォルダを開く",
|
||||
"Open Mask Editor for Selected Node": "選択したノードのマスクエディタを開く",
|
||||
"Open Models Folder": "モデルフォルダを開く",
|
||||
"Open Outputs Folder": "出力フォルダを開く",
|
||||
"Open Sign In Dialog": "サインインダイアログを開く",
|
||||
"Open extra_model_paths_yaml": "extra_model_paths.yamlを開く",
|
||||
"Pin/Unpin Selected Items": "選択したアイテムのピン留め/ピン留め解除",
|
||||
"Pin/Unpin Selected Nodes": "選択したノードのピン留め/ピン留め解除",
|
||||
"Previous Opened Workflow": "前に開いたワークフロー",
|
||||
@@ -1260,13 +1214,10 @@
|
||||
"Queue Prompt": "キューのプロンプト",
|
||||
"Queue Prompt (Front)": "キューのプロンプト (前面)",
|
||||
"Queue Selected Output Nodes": "選択した出力ノードをキューに追加",
|
||||
"Quit": "終了",
|
||||
"Redo": "やり直す",
|
||||
"Refresh Node Definitions": "ノード定義を更新",
|
||||
"Reinstall": "再インストール",
|
||||
"Reset View": "ビューをリセット",
|
||||
"Resize Selected Nodes": "選択したノードのサイズ変更",
|
||||
"Restart": "再起動",
|
||||
"Save": "保存",
|
||||
"Save As": "名前を付けて保存",
|
||||
"Show Keybindings Dialog": "キーバインドダイアログを表示",
|
||||
|
||||
@@ -155,13 +155,6 @@
|
||||
"duplicate": "복제",
|
||||
"enterNewName": "새 이름 입력"
|
||||
},
|
||||
"chatHistory": {
|
||||
"cancelEdit": "취소",
|
||||
"cancelEditTooltip": "편집 취소",
|
||||
"copiedTooltip": "복사됨",
|
||||
"copyTooltip": "메시지를 클립보드에 복사",
|
||||
"editTooltip": "메시지 편집"
|
||||
},
|
||||
"clipboard": {
|
||||
"errorMessage": "클립보드에 복사하지 못했습니다",
|
||||
"errorNotSupported": "브라우저가 클립보드 API를 지원하지 않습니다.",
|
||||
@@ -965,13 +958,6 @@
|
||||
"title": "3D 뷰어 (베타)"
|
||||
}
|
||||
},
|
||||
"loadWorkflowWarning": {
|
||||
"coreNodesFromVersion": "ComfyUI {version} 이상 필요:",
|
||||
"missingNodesDescription": "그래프를 로드할 때 다음 노드 유형을 찾을 수 없습니다.\n설치된 버전이 낮아 해당 노드 유형을 찾을 수 없는 경우에도 이런 일이 발생할 수 있습니다.",
|
||||
"missingNodesTitle": "일부 노드가 누락되었습니다",
|
||||
"outdatedVersion": "일부 노드는 더 최신 버전의 ComfyUI가 필요합니다 (현재: {version}). 모든 노드를 사용하려면 업데이트해 주세요.",
|
||||
"outdatedVersionGeneric": "일부 노드는 더 최신 버전의 ComfyUI가 필요합니다. 모든 노드를 사용하려면 업데이트해 주세요."
|
||||
},
|
||||
"maintenance": {
|
||||
"None": "없음",
|
||||
"OK": "확인",
|
||||
@@ -1101,29 +1087,6 @@
|
||||
"version": "버전"
|
||||
},
|
||||
"maskEditor": {
|
||||
"Apply to Whole Image": "전체 이미지에 적용",
|
||||
"Brush Settings": "브러시 설정",
|
||||
"Brush Shape": "브러시 모양",
|
||||
"Clear": "지우기",
|
||||
"Color Select Settings": "색상 선택 설정",
|
||||
"Fill Opacity": "채우기 투명도",
|
||||
"Hardness": "경도",
|
||||
"Image Layer": "이미지 레이어",
|
||||
"Invert": "반전",
|
||||
"Layers": "레이어",
|
||||
"Live Preview": "실시간 미리보기",
|
||||
"Mask Layer": "마스크 레이어",
|
||||
"Mask Opacity": "마스크 투명도",
|
||||
"Mask Tolerance": "마스크 허용 오차",
|
||||
"Method": "방법",
|
||||
"Opacity": "투명도",
|
||||
"Paint Bucket Settings": "페인트 버킷 설정",
|
||||
"Reset to Default": "기본값으로 재설정",
|
||||
"Selection Opacity": "선택 투명도",
|
||||
"Smoothing Precision": "스무딩 정밀도",
|
||||
"Stop at mask": "마스크에서 중지",
|
||||
"Thickness": "두께",
|
||||
"Tolerance": "허용 오차"
|
||||
},
|
||||
"mediaAsset": {
|
||||
"assetDeletedSuccessfully": "에셋이 성공적으로 삭제되었습니다",
|
||||
@@ -1189,7 +1152,6 @@
|
||||
"Canvas Performance": "캔버스 성능",
|
||||
"Canvas Toggle Lock": "캔버스 토글 잠금",
|
||||
"Check for Custom Node Updates": "커스텀 노드 업데이트 확인",
|
||||
"Check for Updates": "업데이트 확인",
|
||||
"Clear Pending Tasks": "보류 중인 작업 제거하기",
|
||||
"Clear Workflow": "워크플로 지우기",
|
||||
"Clipspace": "클립스페이스",
|
||||
@@ -1206,7 +1168,6 @@
|
||||
"Custom Nodes Manager": "커스텀 노드 관리자",
|
||||
"Decrease Brush Size in MaskEditor": "마스크 편집기에서 브러시 크기 줄이기",
|
||||
"Delete Selected Items": "선택한 항목 삭제",
|
||||
"Desktop User Guide": "데스크톱 사용자 가이드",
|
||||
"Duplicate Current Workflow": "현재 워크플로 복제",
|
||||
"Edit": "편집",
|
||||
"Edit Subgraph Widgets": "하위 그래프 위젯 편집",
|
||||
@@ -1243,15 +1204,8 @@
|
||||
"Node Links": "노드 링크",
|
||||
"Open": "열기",
|
||||
"Open 3D Viewer (Beta) for Selected Node": "선택한 노드에 대한 3D 뷰어 (베타) 열기",
|
||||
"Open Custom Nodes Folder": "커스텀 노드 폴더 열기",
|
||||
"Open DevTools": "개발자 도구 열기",
|
||||
"Open Inputs Folder": "입력 폴더 열기",
|
||||
"Open Logs Folder": "로그 폴더 열기",
|
||||
"Open Mask Editor for Selected Node": "선택한 노드의 마스크 에디터 열기",
|
||||
"Open Models Folder": "모델 폴더 열기",
|
||||
"Open Outputs Folder": "출력 폴더 열기",
|
||||
"Open Sign In Dialog": "로그인 대화 상자 열기",
|
||||
"Open extra_model_paths_yaml": "extra_model_paths.yaml 열기",
|
||||
"Pin/Unpin Selected Items": "선택한 항목 고정/고정 해제",
|
||||
"Pin/Unpin Selected Nodes": "선택한 노드 고정/고정 해제",
|
||||
"Previous Opened Workflow": "이전 열린 워크플로",
|
||||
@@ -1260,13 +1214,10 @@
|
||||
"Queue Prompt": "실행 대기열에 프롬프트 추가",
|
||||
"Queue Prompt (Front)": "실행 대기열 맨 앞에 프롬프트 추가",
|
||||
"Queue Selected Output Nodes": "선택한 출력 노드 대기열에 추가",
|
||||
"Quit": "종료",
|
||||
"Redo": "다시 실행",
|
||||
"Refresh Node Definitions": "노드 정의 새로 고침",
|
||||
"Reinstall": "재설치",
|
||||
"Reset View": "보기 초기화",
|
||||
"Resize Selected Nodes": "선택된 노드 크기 조정",
|
||||
"Restart": "재시작",
|
||||
"Save": "저장",
|
||||
"Save As": "다른 이름으로 저장",
|
||||
"Show Keybindings Dialog": "단축키 대화상자 표시",
|
||||
|
||||
@@ -155,13 +155,6 @@
|
||||
"duplicate": "Дублировать",
|
||||
"enterNewName": "Введите новое имя"
|
||||
},
|
||||
"chatHistory": {
|
||||
"cancelEdit": "Отмена",
|
||||
"cancelEditTooltip": "Отменить редактирование",
|
||||
"copiedTooltip": "Скопировано",
|
||||
"copyTooltip": "Скопировать сообщение в буфер",
|
||||
"editTooltip": "Редактировать сообщение"
|
||||
},
|
||||
"clipboard": {
|
||||
"errorMessage": "Не удалось скопировать в буфер обмена",
|
||||
"errorNotSupported": "API буфера обмена не поддерживается в вашем браузере",
|
||||
@@ -965,13 +958,6 @@
|
||||
"title": "3D Просмотрщик (Бета)"
|
||||
}
|
||||
},
|
||||
"loadWorkflowWarning": {
|
||||
"coreNodesFromVersion": "Требуется ComfyUI {version}:",
|
||||
"missingNodesDescription": "При загрузке графа следующие типы нод не были найдены.\nЭто также может произойти, если ваша установленная версия ниже и этот тип ноды не может быть найден.",
|
||||
"missingNodesTitle": "Некоторые ноды отсутствуют",
|
||||
"outdatedVersion": "Некоторые узлы требуют более новой версии ComfyUI (текущая: {version}). Пожалуйста, обновите, чтобы использовать все узлы.",
|
||||
"outdatedVersionGeneric": "Некоторые узлы требуют более новой версии ComfyUI. Пожалуйста, обновите, чтобы использовать все узлы."
|
||||
},
|
||||
"maintenance": {
|
||||
"None": "Нет",
|
||||
"OK": "OK",
|
||||
@@ -1101,29 +1087,6 @@
|
||||
"version": "Версия"
|
||||
},
|
||||
"maskEditor": {
|
||||
"Apply to Whole Image": "Применить ко всему изображению",
|
||||
"Brush Settings": "Настройки кисти",
|
||||
"Brush Shape": "Форма кисти",
|
||||
"Clear": "Очистить",
|
||||
"Color Select Settings": "Настройки выбора цвета",
|
||||
"Fill Opacity": "Прозрачность заливки",
|
||||
"Hardness": "Жесткость",
|
||||
"Image Layer": "Слой изображения",
|
||||
"Invert": "Инвертировать",
|
||||
"Layers": "Слои",
|
||||
"Live Preview": "Предварительный просмотр",
|
||||
"Mask Layer": "Слой маски",
|
||||
"Mask Opacity": "Прозрачность маски",
|
||||
"Mask Tolerance": "Толерантность маски",
|
||||
"Method": "Метод",
|
||||
"Opacity": "Прозрачность",
|
||||
"Paint Bucket Settings": "Настройки заливки",
|
||||
"Reset to Default": "Сбросить до стандартных",
|
||||
"Selection Opacity": "Прозрачность выбора",
|
||||
"Smoothing Precision": "Точность сглаживания",
|
||||
"Stop at mask": "Остановиться на маске",
|
||||
"Thickness": "Толщина",
|
||||
"Tolerance": "Толерантность"
|
||||
},
|
||||
"mediaAsset": {
|
||||
"assetDeletedSuccessfully": "Ресурс успешно удален",
|
||||
@@ -1189,7 +1152,6 @@
|
||||
"Canvas Performance": "Производительность холста",
|
||||
"Canvas Toggle Lock": "Переключение блокировки холста",
|
||||
"Check for Custom Node Updates": "Проверить обновления пользовательских узлов",
|
||||
"Check for Updates": "Проверить наличие обновлений",
|
||||
"Clear Pending Tasks": "Очистить ожидающие задачи",
|
||||
"Clear Workflow": "Очистить рабочий процесс",
|
||||
"Clipspace": "Клиппространство",
|
||||
@@ -1206,7 +1168,6 @@
|
||||
"Custom Nodes Manager": "Менеджер Пользовательских Узлов",
|
||||
"Decrease Brush Size in MaskEditor": "Уменьшить размер кисти в MaskEditor",
|
||||
"Delete Selected Items": "Удалить выбранные элементы",
|
||||
"Desktop User Guide": "Руководство пользователя для настольных ПК",
|
||||
"Duplicate Current Workflow": "Дублировать текущий рабочий процесс",
|
||||
"Edit": "Редактировать",
|
||||
"Edit Subgraph Widgets": "Редактировать виджеты подграфа",
|
||||
@@ -1243,15 +1204,8 @@
|
||||
"Node Links": "Связи узлов",
|
||||
"Open": "Открыть",
|
||||
"Open 3D Viewer (Beta) for Selected Node": "Открыть 3D просмотрщик (Бета) для выбранного узла",
|
||||
"Open Custom Nodes Folder": "Открыть папку пользовательских нод",
|
||||
"Open DevTools": "Открыть инструменты разработчика",
|
||||
"Open Inputs Folder": "Открыть папку входных данных",
|
||||
"Open Logs Folder": "Открыть папку журналов",
|
||||
"Open Mask Editor for Selected Node": "Открыть редактор масок для выбранного узла",
|
||||
"Open Models Folder": "Открыть папку моделей",
|
||||
"Open Outputs Folder": "Открыть папку выходных данных",
|
||||
"Open Sign In Dialog": "Открыть окно входа",
|
||||
"Open extra_model_paths_yaml": "Открыть extra_model_paths.yaml",
|
||||
"Pin/Unpin Selected Items": "Закрепить/открепить выбранные элементы",
|
||||
"Pin/Unpin Selected Nodes": "Закрепить/открепить выбранные ноды",
|
||||
"Previous Opened Workflow": "Предыдущий открытый рабочий процесс",
|
||||
@@ -1260,13 +1214,10 @@
|
||||
"Queue Prompt": "Запрос в очереди",
|
||||
"Queue Prompt (Front)": "Запрос в очереди (спереди)",
|
||||
"Queue Selected Output Nodes": "Добавить выбранные выходные узлы в очередь",
|
||||
"Quit": "Выйти",
|
||||
"Redo": "Повторить",
|
||||
"Refresh Node Definitions": "Обновить определения нод",
|
||||
"Reinstall": "Переустановить",
|
||||
"Reset View": "Сбросить вид",
|
||||
"Resize Selected Nodes": "Изменить размер выбранных узлов",
|
||||
"Restart": "Перезапустить",
|
||||
"Save": "Сохранить",
|
||||
"Save As": "Сохранить как",
|
||||
"Show Keybindings Dialog": "Показать диалог клавиш быстрого доступа",
|
||||
|
||||
@@ -155,13 +155,6 @@
|
||||
"duplicate": "Çoğalt",
|
||||
"enterNewName": "Yeni isim girin"
|
||||
},
|
||||
"chatHistory": {
|
||||
"cancelEdit": "İptal",
|
||||
"cancelEditTooltip": "Düzenlemeyi iptal et",
|
||||
"copiedTooltip": "Kopyalandı",
|
||||
"copyTooltip": "Mesajı panoya kopyala",
|
||||
"editTooltip": "Mesajı düzenle"
|
||||
},
|
||||
"clipboard": {
|
||||
"errorMessage": "Panoya kopyalanamadı",
|
||||
"errorNotSupported": "Pano API'si tarayıcınızda desteklenmiyor",
|
||||
@@ -965,13 +958,6 @@
|
||||
"title": "3D Görüntüleyici (Beta)"
|
||||
}
|
||||
},
|
||||
"loadWorkflowWarning": {
|
||||
"coreNodesFromVersion": "ComfyUI {version} gerektirir:",
|
||||
"missingNodesDescription": "Grafik yüklenirken aşağıdaki düğüm türleri bulunamadı.\nBu, yüklü sürümünüz daha düşükse ve bu düğüm türü bulunamazsa da olabilir.",
|
||||
"missingNodesTitle": "Bazı Düğümler Eksik",
|
||||
"outdatedVersion": "Bazı düğümler ComfyUI'nin daha yeni bir sürümünü gerektirir (mevcut: {version}). Tüm düğümleri kullanmak için lütfen güncelleyin.",
|
||||
"outdatedVersionGeneric": "Bazı düğümler ComfyUI'nin daha yeni bir sürümünü gerektirir. Tüm düğümleri kullanmak için lütfen güncelleyin."
|
||||
},
|
||||
"maintenance": {
|
||||
"None": "Yok",
|
||||
"OK": "Tamam",
|
||||
@@ -1101,29 +1087,6 @@
|
||||
"version": "Sürüm"
|
||||
},
|
||||
"maskEditor": {
|
||||
"Apply to Whole Image": "Tüm Görüntüye Uygula",
|
||||
"Brush Settings": "Fırça Ayarları",
|
||||
"Brush Shape": "Fırça Şekli",
|
||||
"Clear": "Temizle",
|
||||
"Color Select Settings": "Renk Seçim Ayarları",
|
||||
"Fill Opacity": "Dolgu Opaklığı",
|
||||
"Hardness": "Sertlik",
|
||||
"Image Layer": "Görüntü Katmanı",
|
||||
"Invert": "Ters Çevir",
|
||||
"Layers": "Katmanlar",
|
||||
"Live Preview": "Canlı Önizleme",
|
||||
"Mask Layer": "Maske Katmanı",
|
||||
"Mask Opacity": "Maske Opaklığı",
|
||||
"Mask Tolerance": "Maske Toleransı",
|
||||
"Method": "Yöntem",
|
||||
"Opacity": "Opaklık",
|
||||
"Paint Bucket Settings": "Boya Kovası Ayarları",
|
||||
"Reset to Default": "Varsayılana Sıfırla",
|
||||
"Selection Opacity": "Seçim Opaklığı",
|
||||
"Smoothing Precision": "Yumuşatma Hassasiyeti",
|
||||
"Stop at mask": "Maskede dur",
|
||||
"Thickness": "Kalınlık",
|
||||
"Tolerance": "Tolerans"
|
||||
},
|
||||
"mediaAsset": {
|
||||
"assetDeletedSuccessfully": "Varlık başarıyla silindi",
|
||||
@@ -1189,7 +1152,6 @@
|
||||
"Canvas Performance": "Tuval Performansı",
|
||||
"Canvas Toggle Lock": "Tuval Kilidini Aç/Kapat",
|
||||
"Check for Custom Node Updates": "Özel Düğüm Güncellemelerini Kontrol Et",
|
||||
"Check for Updates": "Güncellemeleri Kontrol Et",
|
||||
"Clear Pending Tasks": "Bekleyen Görevleri Temizle",
|
||||
"Clear Workflow": "İş Akışını Temizle",
|
||||
"Clipspace": "Clipspace",
|
||||
@@ -1206,7 +1168,6 @@
|
||||
"Custom Nodes Manager": "Özel Düğüm Yöneticisi",
|
||||
"Decrease Brush Size in MaskEditor": "MaskEditor'da Fırça Boyutunu Azalt",
|
||||
"Delete Selected Items": "Seçili Öğeleri Sil",
|
||||
"Desktop User Guide": "Masaüstü Kullanıcı Kılavuzu",
|
||||
"Duplicate Current Workflow": "Mevcut İş Akışını Çoğalt",
|
||||
"Edit": "Düzenle",
|
||||
"Edit Subgraph Widgets": "Alt Grafik Widget'larını Düzenle",
|
||||
@@ -1243,15 +1204,8 @@
|
||||
"Node Links": "Düğüm Bağlantıları",
|
||||
"Open": "Aç",
|
||||
"Open 3D Viewer (Beta) for Selected Node": "Seçili Düğüm için 3D Görüntüleyiciyi (Beta) Aç",
|
||||
"Open Custom Nodes Folder": "Özel Düğümler Klasörünü Aç",
|
||||
"Open DevTools": "Geliştirici Araçlarını Aç",
|
||||
"Open Inputs Folder": "Girişler Klasörünü Aç",
|
||||
"Open Logs Folder": "Kayıtlar Klasörünü Aç",
|
||||
"Open Mask Editor for Selected Node": "Seçili Düğüm için Maske Düzenleyiciyi Aç",
|
||||
"Open Models Folder": "Modeller Klasörünü Aç",
|
||||
"Open Outputs Folder": "Çıktılar Klasörünü Aç",
|
||||
"Open Sign In Dialog": "Giriş Yapma İletişim Kutusunu Aç",
|
||||
"Open extra_model_paths_yaml": "extra_model_paths.yaml dosyasını aç",
|
||||
"Pin/Unpin Selected Items": "Seçili Öğeleri Sabitle/Kaldır",
|
||||
"Pin/Unpin Selected Nodes": "Seçili Düğümleri Sabitle/Kaldır",
|
||||
"Previous Opened Workflow": "Önceki Açılan İş Akışı",
|
||||
@@ -1260,13 +1214,10 @@
|
||||
"Queue Prompt": "İstemi Kuyruğa Al",
|
||||
"Queue Prompt (Front)": "İstemi Kuyruğa Al (Ön)",
|
||||
"Queue Selected Output Nodes": "Seçili Çıktı Düğümlerini Kuyruğa Al",
|
||||
"Quit": "Çık",
|
||||
"Redo": "Yinele",
|
||||
"Refresh Node Definitions": "Düğüm Tanımlarını Yenile",
|
||||
"Reinstall": "Yeniden Yükle",
|
||||
"Reset View": "Görünümü Sıfırla",
|
||||
"Resize Selected Nodes": "Seçili Düğümleri Yeniden Boyutlandır",
|
||||
"Restart": "Yeniden Başlat",
|
||||
"Save": "Kaydet",
|
||||
"Save As": "Farklı Kaydet",
|
||||
"Show Keybindings Dialog": "Tuş Atamaları İletişim Kutusunu Göster",
|
||||
|
||||
@@ -155,13 +155,6 @@
|
||||
"duplicate": "複製",
|
||||
"enterNewName": "輸入新名稱"
|
||||
},
|
||||
"chatHistory": {
|
||||
"cancelEdit": "取消",
|
||||
"cancelEditTooltip": "取消編輯",
|
||||
"copiedTooltip": "已複製",
|
||||
"copyTooltip": "複製訊息到剪貼簿",
|
||||
"editTooltip": "編輯訊息"
|
||||
},
|
||||
"clipboard": {
|
||||
"errorMessage": "複製到剪貼簿失敗",
|
||||
"errorNotSupported": "您的瀏覽器不支援剪貼簿 API",
|
||||
@@ -965,13 +958,6 @@
|
||||
"title": "3D 檢視器(測試版)"
|
||||
}
|
||||
},
|
||||
"loadWorkflowWarning": {
|
||||
"coreNodesFromVersion": "需要 ComfyUI {version}:",
|
||||
"missingNodesDescription": "載入圖形時,找不到以下節點類型。\n如果您安裝的版本較舊,找不到該節點類型,也可能發生這種情況。",
|
||||
"missingNodesTitle": "部分節點缺少",
|
||||
"outdatedVersion": "部分節點需要較新版本的 ComfyUI(目前版本:{version})。請更新以使用所有節點。",
|
||||
"outdatedVersionGeneric": "部分節點需要較新版本的 ComfyUI。請更新以使用所有節點。"
|
||||
},
|
||||
"maintenance": {
|
||||
"None": "無",
|
||||
"OK": "正常",
|
||||
@@ -1101,29 +1087,6 @@
|
||||
"version": "版本"
|
||||
},
|
||||
"maskEditor": {
|
||||
"Apply to Whole Image": "套用至整張圖片",
|
||||
"Brush Settings": "筆刷設定",
|
||||
"Brush Shape": "筆刷形狀",
|
||||
"Clear": "清除",
|
||||
"Color Select Settings": "顏色選取設定",
|
||||
"Fill Opacity": "填充不透明度",
|
||||
"Hardness": "硬度",
|
||||
"Image Layer": "圖像圖層",
|
||||
"Invert": "反轉",
|
||||
"Layers": "圖層",
|
||||
"Live Preview": "即時預覽",
|
||||
"Mask Layer": "遮罩圖層",
|
||||
"Mask Opacity": "遮罩不透明度",
|
||||
"Mask Tolerance": "遮罩容差",
|
||||
"Method": "方法",
|
||||
"Opacity": "不透明度",
|
||||
"Paint Bucket Settings": "油漆桶設定",
|
||||
"Reset to Default": "重設為預設值",
|
||||
"Selection Opacity": "選取不透明度",
|
||||
"Smoothing Precision": "平滑精度",
|
||||
"Stop at mask": "停在遮罩",
|
||||
"Thickness": "粗細",
|
||||
"Tolerance": "容差"
|
||||
},
|
||||
"mediaAsset": {
|
||||
"assetDeletedSuccessfully": "資源刪除成功",
|
||||
@@ -1189,7 +1152,6 @@
|
||||
"Canvas Performance": "畫布效能",
|
||||
"Canvas Toggle Lock": "切換畫布鎖定",
|
||||
"Check for Custom Node Updates": "檢查自訂節點更新",
|
||||
"Check for Updates": "檢查更新",
|
||||
"Clear Pending Tasks": "清除待處理任務",
|
||||
"Clear Workflow": "清除工作流程",
|
||||
"Clipspace": "Clipspace",
|
||||
@@ -1206,7 +1168,6 @@
|
||||
"Custom Nodes Manager": "自訂節點管理員",
|
||||
"Decrease Brush Size in MaskEditor": "在 MaskEditor 中減小筆刷大小",
|
||||
"Delete Selected Items": "刪除選取項目",
|
||||
"Desktop User Guide": "桌面應用程式使用指南",
|
||||
"Duplicate Current Workflow": "複製目前工作流程",
|
||||
"Edit": "編輯",
|
||||
"Edit Subgraph Widgets": "編輯子圖小工具",
|
||||
@@ -1243,15 +1204,8 @@
|
||||
"Node Links": "節點連結",
|
||||
"Open": "開啟",
|
||||
"Open 3D Viewer (Beta) for Selected Node": "為選取節點開啟 3D 檢視器(測試版)",
|
||||
"Open Custom Nodes Folder": "開啟自訂節點資料夾",
|
||||
"Open DevTools": "開啟開發者工具",
|
||||
"Open Inputs Folder": "開啟輸入資料夾",
|
||||
"Open Logs Folder": "開啟日誌資料夾",
|
||||
"Open Mask Editor for Selected Node": "為選取節點開啟遮罩編輯器",
|
||||
"Open Models Folder": "開啟模型資料夾",
|
||||
"Open Outputs Folder": "開啟輸出資料夾",
|
||||
"Open Sign In Dialog": "開啟登入對話框",
|
||||
"Open extra_model_paths_yaml": "開啟 extra_model_paths.yaml",
|
||||
"Pin/Unpin Selected Items": "釘選/取消釘選選取項目",
|
||||
"Pin/Unpin Selected Nodes": "釘選/取消釘選選取節點",
|
||||
"Previous Opened Workflow": "上一個已開啟的工作流程",
|
||||
@@ -1260,13 +1214,10 @@
|
||||
"Queue Prompt": "加入提示至佇列",
|
||||
"Queue Prompt (Front)": "將提示加入佇列前端",
|
||||
"Queue Selected Output Nodes": "將選取的輸出節點加入佇列",
|
||||
"Quit": "離開",
|
||||
"Redo": "重做",
|
||||
"Refresh Node Definitions": "重新整理節點定義",
|
||||
"Reinstall": "重新安裝",
|
||||
"Reset View": "重設視圖",
|
||||
"Resize Selected Nodes": "調整選取節點大小",
|
||||
"Restart": "重新啟動",
|
||||
"Save": "儲存",
|
||||
"Save As": "另存新檔",
|
||||
"Show Keybindings Dialog": "顯示快捷鍵對話框",
|
||||
|
||||
@@ -155,13 +155,6 @@
|
||||
"duplicate": "复制",
|
||||
"enterNewName": "输入新名称"
|
||||
},
|
||||
"chatHistory": {
|
||||
"cancelEdit": "取消",
|
||||
"cancelEditTooltip": "取消编辑",
|
||||
"copiedTooltip": "已复制",
|
||||
"copyTooltip": "复制消息到剪贴板",
|
||||
"editTooltip": "编辑消息"
|
||||
},
|
||||
"clipboard": {
|
||||
"errorMessage": "复制到剪贴板失败",
|
||||
"errorNotSupported": "您的浏览器不支持剪贴板API",
|
||||
@@ -965,13 +958,6 @@
|
||||
"title": "3D 查看器(测试版)"
|
||||
}
|
||||
},
|
||||
"loadWorkflowWarning": {
|
||||
"coreNodesFromVersion": "需要 ComfyUI {version}:",
|
||||
"missingNodesDescription": "在加载工作流时,以下节点未找到。\n这也可能是因为你的ComfyUI版本过低,无法找到新的核心节点。",
|
||||
"missingNodesTitle": "某些节点缺失",
|
||||
"outdatedVersion": "某些节点需要更高版本的 ComfyUI(当前版本:{version})。请更新以使用所有节点。",
|
||||
"outdatedVersionGeneric": "某些节点需要更高版本的 ComfyUI。请更新以使用所有节点。"
|
||||
},
|
||||
"maintenance": {
|
||||
"None": "无",
|
||||
"OK": "确定",
|
||||
@@ -1101,29 +1087,6 @@
|
||||
"version": "版本"
|
||||
},
|
||||
"maskEditor": {
|
||||
"Apply to Whole Image": "应用到整个图像",
|
||||
"Brush Settings": "笔刷设置",
|
||||
"Brush Shape": "笔刷形状",
|
||||
"Clear": "清空",
|
||||
"Color Select Settings": "颜色选择设置",
|
||||
"Fill Opacity": "填充不透明度",
|
||||
"Hardness": "硬度",
|
||||
"Image Layer": "图像图层",
|
||||
"Invert": "反转",
|
||||
"Layers": "图层",
|
||||
"Live Preview": "实时预览",
|
||||
"Mask Layer": "遮罩图层",
|
||||
"Mask Opacity": "遮罩不透明度",
|
||||
"Mask Tolerance": "遮罩容差",
|
||||
"Method": "方法",
|
||||
"Opacity": "不透明度",
|
||||
"Paint Bucket Settings": "油漆桶设置",
|
||||
"Reset to Default": "恢复默认",
|
||||
"Selection Opacity": "选区不透明度",
|
||||
"Smoothing Precision": "平滑精度",
|
||||
"Stop at mask": "在遮罩处停止",
|
||||
"Thickness": "粗细",
|
||||
"Tolerance": "容差"
|
||||
},
|
||||
"mediaAsset": {
|
||||
"assetDeletedSuccessfully": "资产删除成功",
|
||||
@@ -1189,7 +1152,6 @@
|
||||
"Canvas Performance": "画布性能",
|
||||
"Canvas Toggle Lock": "切换视图锁定",
|
||||
"Check for Custom Node Updates": "检查自定义节点更新",
|
||||
"Check for Updates": "检查更新",
|
||||
"Clear Pending Tasks": "清除待处理任务",
|
||||
"Clear Workflow": "清除工作流",
|
||||
"Clipspace": "剪贴空间",
|
||||
@@ -1206,7 +1168,6 @@
|
||||
"Custom Nodes Manager": "自定义节点管理器",
|
||||
"Decrease Brush Size in MaskEditor": "在 MaskEditor 中减小笔刷大小",
|
||||
"Delete Selected Items": "删除选定的项目",
|
||||
"Desktop User Guide": "桌面端用户指南",
|
||||
"Duplicate Current Workflow": "复制当前工作流",
|
||||
"Edit": "编辑",
|
||||
"Edit Subgraph Widgets": "编辑子图组件",
|
||||
@@ -1243,15 +1204,8 @@
|
||||
"Node Links": "节点连接",
|
||||
"Open": "打开",
|
||||
"Open 3D Viewer (Beta) for Selected Node": "为选中节点打开3D查看器(测试版)",
|
||||
"Open Custom Nodes Folder": "打开自定义节点文件夹",
|
||||
"Open DevTools": "打开开发者工具",
|
||||
"Open Inputs Folder": "打开输入文件夹",
|
||||
"Open Logs Folder": "打开日志文件夹",
|
||||
"Open Mask Editor for Selected Node": "为选中节点打开 Mask 编辑器",
|
||||
"Open Models Folder": "打开模型文件夹",
|
||||
"Open Outputs Folder": "打开输出文件夹",
|
||||
"Open Sign In Dialog": "打开登录对话框",
|
||||
"Open extra_model_paths_yaml": "打开 extra_model_paths.yaml",
|
||||
"Pin/Unpin Selected Items": "固定/取消固定选定项目",
|
||||
"Pin/Unpin Selected Nodes": "固定/取消固定选定节点",
|
||||
"Previous Opened Workflow": "上一个打开的工作流",
|
||||
@@ -1260,13 +1214,10 @@
|
||||
"Queue Prompt": "执行提示词",
|
||||
"Queue Prompt (Front)": "执行提示词 (优先执行)",
|
||||
"Queue Selected Output Nodes": "将所选输出节点加入队列",
|
||||
"Quit": "退出",
|
||||
"Redo": "重做",
|
||||
"Refresh Node Definitions": "刷新节点定义",
|
||||
"Reinstall": "重新安装",
|
||||
"Reset View": "重置视图",
|
||||
"Resize Selected Nodes": "调整选定节点的大小",
|
||||
"Restart": "重启",
|
||||
"Save": "保存",
|
||||
"Save As": "另存为",
|
||||
"Show Keybindings Dialog": "显示快捷键对话框",
|
||||
|
||||
@@ -12,12 +12,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
formatSize,
|
||||
getFilenameDetails
|
||||
} from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { formatSize, getFilenameDetails } from '@/utils/formatUtil'
|
||||
|
||||
import type { AssetMeta } from '../schemas/mediaAssetSchema'
|
||||
import MediaTitle from './MediaTitle.vue'
|
||||
|
||||
|
||||
@@ -132,10 +132,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
formatDuration,
|
||||
getMediaTypeFromFilename
|
||||
} from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import { useElementHover } from '@vueuse/core'
|
||||
import { computed, defineAsyncComponent, provide, ref, toRef } from 'vue'
|
||||
|
||||
@@ -147,6 +143,7 @@ import CardBottom from '@/components/card/CardBottom.vue'
|
||||
import CardContainer from '@/components/card/CardContainer.vue'
|
||||
import CardTop from '@/components/card/CardTop.vue'
|
||||
import SquareChip from '@/components/chip/SquareChip.vue'
|
||||
import { formatDuration, getMediaTypeFromFilename } from '@/utils/formatUtil'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
import { getAssetType } from '../composables/media/assetMappers'
|
||||
|
||||
@@ -12,12 +12,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
formatSize,
|
||||
getFilenameDetails
|
||||
} from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { formatSize, getFilenameDetails } from '@/utils/formatUtil'
|
||||
|
||||
import type { AssetContext, AssetMeta } from '../schemas/mediaAssetSchema'
|
||||
import MediaTitle from './MediaTitle.vue'
|
||||
|
||||
|
||||
@@ -10,9 +10,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getFilenameDetails } from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { getFilenameDetails } from '@/utils/formatUtil'
|
||||
|
||||
import type { AssetContext, AssetMeta } from '../schemas/mediaAssetSchema'
|
||||
import MediaTitle from './MediaTitle.vue'
|
||||
|
||||
|
||||
@@ -8,9 +8,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { truncateFilename } from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { truncateFilename } from '@/utils/formatUtil'
|
||||
|
||||
const props = defineProps<{
|
||||
fileName: string
|
||||
}>()
|
||||
|
||||
@@ -9,12 +9,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
formatSize,
|
||||
getFilenameDetails
|
||||
} from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { formatSize, getFilenameDetails } from '@/utils/formatUtil'
|
||||
|
||||
import type { AssetContext, AssetMeta } from '../schemas/mediaAssetSchema'
|
||||
import MediaTitle from './MediaTitle.vue'
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { computed, ref } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
|
||||
import { getMediaTypeFromFilename } from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import { getMediaTypeFromFilename } from '@/utils/formatUtil'
|
||||
|
||||
type SortOption = 'newest' | 'oldest' | 'longest' | 'fastest'
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
|
||||
import { formatMetronomeCurrency } from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import { formatMetronomeCurrency } from '@/utils/formatUtil'
|
||||
|
||||
/**
|
||||
* Composable for handling subscription credit calculations and formatting
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { normalizeI18nKey } from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import Divider from 'primevue/divider'
|
||||
|
||||
import SettingItem from '@/platform/settings/components/SettingItem.vue'
|
||||
import type { SettingParams } from '@/platform/settings/types'
|
||||
import { normalizeI18nKey } from '@/utils/formatUtil'
|
||||
|
||||
defineProps<{
|
||||
group: {
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { normalizeI18nKey } from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import Tag from 'primevue/tag'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
@@ -34,6 +33,7 @@ import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import type { SettingOption, SettingParams } from '@/platform/settings/types'
|
||||
import { useTelemetry } from '@/platform/telemetry'
|
||||
import type { Settings } from '@/schemas/apiSchema'
|
||||
import { normalizeI18nKey } from '@/utils/formatUtil'
|
||||
|
||||
const props = defineProps<{
|
||||
setting: SettingParams
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
useSettingStore
|
||||
} from '@/platform/settings/settingStore'
|
||||
import type { ISettingGroup, SettingParams } from '@/platform/settings/types'
|
||||
import { normalizeI18nKey } from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import { normalizeI18nKey } from '@/utils/formatUtil'
|
||||
|
||||
export function useSettingSearch() {
|
||||
const settingStore = useSettingStore()
|
||||
|
||||
@@ -8,7 +8,7 @@ import type { SettingTreeNode } from '@/platform/settings/settingStore'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import type { SettingParams } from '@/platform/settings/types'
|
||||
import { isElectron } from '@/utils/envUtil'
|
||||
import { normalizeI18nKey } from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import { normalizeI18nKey } from '@/utils/formatUtil'
|
||||
import { buildTree } from '@/utils/treeUtil'
|
||||
|
||||
interface SettingPanelItem {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { isCloud } from '@/platform/distribution/types'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { useSystemStatsStore } from '@/stores/systemStatsStore'
|
||||
import { isElectron } from '@/utils/envUtil'
|
||||
import { stringToLocale } from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import { stringToLocale } from '@/utils/formatUtil'
|
||||
|
||||
import { useReleaseService } from './releaseService'
|
||||
import type { ReleaseNote } from './releaseService'
|
||||
|
||||
@@ -45,10 +45,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { formatVersionAnchor } from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
|
||||
import { useExternalLink } from '@/composables/useExternalLink'
|
||||
import { formatVersionAnchor } from '@/utils/formatUtil'
|
||||
|
||||
import type { ReleaseNote } from '../common/releaseService'
|
||||
import { useReleaseStore } from '../common/releaseStore'
|
||||
|
||||
@@ -64,11 +64,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { formatVersionAnchor } from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { useExternalLink } from '@/composables/useExternalLink'
|
||||
import { formatVersionAnchor } from '@/utils/formatUtil'
|
||||
import { renderMarkdownToHtml } from '@/utils/markdownRendererUtil'
|
||||
|
||||
import type { ReleaseNote } from '../common/releaseService'
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useWorkflowStore } from '@/platform/workflow/management/stores/workflow
|
||||
import { useWorkflowService } from '@/platform/workflow/core/services/workflowService'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import { appendJsonExt } from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import { appendJsonExt } from '@/utils/formatUtil'
|
||||
import { t } from '@/i18n'
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,7 +17,7 @@ import { blankGraph, defaultGraph } from '@/scripts/defaultGraph'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import { useDomWidgetStore } from '@/stores/domWidgetStore'
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore'
|
||||
import { appendJsonExt } from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import { appendJsonExt } from '@/utils/formatUtil'
|
||||
|
||||
export const useWorkflowService = () => {
|
||||
const settingStore = useSettingStore()
|
||||
|
||||
@@ -27,10 +27,7 @@ import {
|
||||
parseNodeExecutionId,
|
||||
parseNodeLocatorId
|
||||
} from '@/types/nodeIdentification'
|
||||
import {
|
||||
generateUUID,
|
||||
getPathDetails
|
||||
} from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import { generateUUID, getPathDetails } from '@/utils/formatUtil'
|
||||
import { syncEntities } from '@/utils/syncUtil'
|
||||
import { isSubgraph } from '@/utils/typeGuardUtil'
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { isCloud } from '@/platform/distribution/types'
|
||||
import { api } from '@/scripts/api'
|
||||
import type { NavGroupData, NavItemData } from '@/types/navTypes'
|
||||
import { getCategoryIcon } from '@/utils/categoryIcons'
|
||||
import { normalizeI18nKey } from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import { normalizeI18nKey } from '@/utils/formatUtil'
|
||||
|
||||
import type {
|
||||
TemplateGroup,
|
||||
|
||||
@@ -11,12 +11,12 @@
|
||||
>
|
||||
<!-- Video Wrapper -->
|
||||
<div
|
||||
class="relative h-88 w-full grow overflow-hidden rounded-[5px] bg-node-component-surface"
|
||||
class="relative h-full w-full grow overflow-hidden rounded-[5px] bg-node-component-surface"
|
||||
>
|
||||
<!-- Error State -->
|
||||
<div
|
||||
v-if="videoError"
|
||||
class="flex size-full flex-col items-center justify-center bg-smoke-800/50 text-center text-white"
|
||||
class="flex size-full flex-col items-center justify-center bg-smoke-800/50 text-center text-white py-8"
|
||||
>
|
||||
<i class="mb-2 icon-[lucide--video-off] h-12 w-12 text-smoke-400" />
|
||||
<p class="text-sm text-smoke-300">{{ $t('g.videoFailedToLoad') }}</p>
|
||||
|
||||
@@ -11,12 +11,12 @@
|
||||
>
|
||||
<!-- Image Wrapper -->
|
||||
<div
|
||||
class="min-h-88 w-full overflow-hidden rounded-[5px] bg-node-component-surface"
|
||||
class="h-full w-full overflow-hidden rounded-[5px] bg-node-component-surface"
|
||||
>
|
||||
<!-- Error State -->
|
||||
<div
|
||||
v-if="imageError"
|
||||
class="flex size-full flex-col items-center justify-center bg-smoke-800/50 text-center text-white"
|
||||
class="flex size-full flex-col items-center justify-center bg-smoke-800/50 text-center text-white py-8"
|
||||
>
|
||||
<i class="mb-2 icon-[lucide--image-off] h-12 w-12 text-smoke-400" />
|
||||
<p class="text-sm text-smoke-300">{{ $t('g.imageFailedToLoad') }}</p>
|
||||
|
||||
@@ -10,12 +10,10 @@
|
||||
/>
|
||||
|
||||
<!-- Slot Name -->
|
||||
<div class="relative h-full flex items-center">
|
||||
<div class="relative h-full flex items-center min-w-0">
|
||||
<span
|
||||
v-if="!dotOnly"
|
||||
:class="
|
||||
cn('whitespace-nowrap text-xs font-normal lod-toggle', labelClasses)
|
||||
"
|
||||
:class="cn('truncate text-xs font-normal lod-toggle', labelClasses)"
|
||||
>
|
||||
{{ slotData.localized_name || slotData.name || `Input ${index}` }}
|
||||
</span>
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
:data-node-id="nodeData.id"
|
||||
:class="
|
||||
cn(
|
||||
'bg-component-node-background lg-node absolute',
|
||||
'h-min w-min contain-style contain-layout min-h-(--node-height) min-w-(--node-width)',
|
||||
'bg-component-node-background lg-node absolute pb-1',
|
||||
|
||||
'contain-style contain-layout min-w-[225px] min-h-(--node-height) w-(--node-width)',
|
||||
'rounded-2xl touch-none flex flex-col',
|
||||
'border-1 border-solid border-component-node-border',
|
||||
// hover (only when node should handle events)
|
||||
@@ -100,7 +101,7 @@
|
||||
|
||||
<!-- Node Body - rendered based on LOD level and collapsed state -->
|
||||
<div
|
||||
class="flex min-h-min min-w-min flex-1 flex-col gap-1 pb-2"
|
||||
class="flex flex-1 flex-col gap-1 pb-2"
|
||||
:data-testid="`node-body-${nodeData.id}`"
|
||||
>
|
||||
<!-- Slots only rendered at full detail -->
|
||||
@@ -343,12 +344,17 @@ const cornerResizeHandles: CornerResizeHandle[] = [
|
||||
}
|
||||
]
|
||||
|
||||
const MIN_NODE_WIDTH = 225
|
||||
|
||||
const { startResize } = useNodeResize(
|
||||
(result, element) => {
|
||||
if (isCollapsed.value) return
|
||||
|
||||
// Clamp width to minimum to avoid conflicts with CSS min-width
|
||||
const clampedWidth = Math.max(result.size.width, MIN_NODE_WIDTH)
|
||||
|
||||
// Apply size directly to DOM element - ResizeObserver will pick this up
|
||||
element.style.setProperty('--node-width', `${result.size.width}px`)
|
||||
element.style.setProperty('--node-width', `${clampedWidth}px`)
|
||||
element.style.setProperty('--node-height', `${result.size.height}px`)
|
||||
|
||||
const currentPosition = position.value
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
v-else
|
||||
:class="
|
||||
cn(
|
||||
'lg-node-header py-2 pl-2 pr-3 text-sm rounded-t-2xl w-full min-w-50',
|
||||
'lg-node-header py-2 pl-2 pr-3 text-sm rounded-t-2xl w-full min-w-0',
|
||||
'text-node-component-header bg-node-component-header-surface',
|
||||
collapsed && 'rounded-2xl'
|
||||
)
|
||||
@@ -15,9 +15,9 @@
|
||||
:data-testid="`node-header-${nodeData?.id || ''}`"
|
||||
@dblclick="handleDoubleClick"
|
||||
>
|
||||
<div class="flex items-center justify-between gap-2.5">
|
||||
<div class="flex items-center justify-between gap-2.5 min-w-0">
|
||||
<!-- Collapse/Expand Button -->
|
||||
<div class="relative grow-1 flex items-center gap-2.5">
|
||||
<div class="relative grow-1 flex items-center gap-2.5 min-w-0 flex-1">
|
||||
<div class="lod-toggle flex shrink-0 items-center px-0.5">
|
||||
<IconButton
|
||||
size="fit-content"
|
||||
@@ -44,16 +44,18 @@
|
||||
<!-- Node Title -->
|
||||
<div
|
||||
v-tooltip.top="tooltipConfig"
|
||||
class="lod-toggle grow-1 items-center gap-2 truncate text-sm font-bold w-15"
|
||||
class="lod-toggle flex min-w-0 flex-1 items-center gap-2 text-sm font-bold"
|
||||
data-testid="node-title"
|
||||
>
|
||||
<EditableText
|
||||
:model-value="displayTitle"
|
||||
:is-editing="isEditing"
|
||||
:input-attrs="{ 'data-testid': 'node-title-input' }"
|
||||
@edit="handleTitleEdit"
|
||||
@cancel="handleTitleCancel"
|
||||
/>
|
||||
<div class="truncate min-w-0 flex-1">
|
||||
<EditableText
|
||||
:model-value="displayTitle"
|
||||
:is-editing="isEditing"
|
||||
:input-attrs="{ 'data-testid': 'node-title-input' }"
|
||||
@edit="handleTitleEdit"
|
||||
@cancel="handleTitleCancel"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<LODFallback />
|
||||
</div>
|
||||
@@ -89,7 +91,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { normalizeI18nKey } from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import { computed, onErrorCaptured, ref, toValue, watch } from 'vue'
|
||||
|
||||
import IconButton from '@/components/button/IconButton.vue'
|
||||
@@ -104,6 +105,7 @@ import { useNodeTooltips } from '@/renderer/extensions/vueNodes/composables/useN
|
||||
import { applyLightThemeColor } from '@/renderer/extensions/vueNodes/utils/nodeStyleUtils'
|
||||
import { app } from '@/scripts/app'
|
||||
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
|
||||
import { normalizeI18nKey } from '@/utils/formatUtil'
|
||||
import {
|
||||
getLocatorIdFromNodeData,
|
||||
getNodeByLocatorId
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
<div v-if="renderError" class="node-error p-2 text-sm text-red-500">
|
||||
{{ st('nodeErrors.slots', 'Node Slots Error') }}
|
||||
</div>
|
||||
<div v-else :class="cn('flex justify-between', unifiedWrapperClass)">
|
||||
<div v-else :class="cn('flex justify-between min-w-0', unifiedWrapperClass)">
|
||||
<div
|
||||
v-if="filteredInputs.length"
|
||||
:class="cn('flex flex-col', unifiedDotsClass)"
|
||||
:class="cn('flex flex-col min-w-0', unifiedDotsClass)"
|
||||
>
|
||||
<InputSlot
|
||||
v-for="(input, index) in filteredInputs"
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
<div
|
||||
v-if="nodeData?.outputs?.length"
|
||||
:class="cn('ml-auto flex flex-col', unifiedDotsClass)"
|
||||
:class="cn('ml-auto flex flex-col min-w-0', unifiedDotsClass)"
|
||||
>
|
||||
<OutputSlot
|
||||
v-for="(output, index) in nodeData.outputs"
|
||||
|
||||
@@ -59,10 +59,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { TooltipOptions } from 'primevue'
|
||||
import { computed, onErrorCaptured, ref } from 'vue'
|
||||
import type { Component } from 'vue'
|
||||
|
||||
import type {
|
||||
SafeWidgetData,
|
||||
VueNodeData,
|
||||
WidgetSlotMetadata
|
||||
} from '@/composables/graph/useGraphNodeManager'
|
||||
@@ -115,18 +116,18 @@ const { getWidgetTooltip, createTooltipConfig } = useNodeTooltips(
|
||||
interface ProcessedWidget {
|
||||
name: string
|
||||
type: string
|
||||
vueComponent: any
|
||||
vueComponent: Component
|
||||
simplified: SimplifiedWidget
|
||||
value: WidgetValue
|
||||
updateHandler: (value: unknown) => void
|
||||
tooltipConfig: any
|
||||
updateHandler: (value: WidgetValue) => void
|
||||
tooltipConfig: TooltipOptions
|
||||
slotMetadata?: WidgetSlotMetadata
|
||||
}
|
||||
|
||||
const processedWidgets = computed((): ProcessedWidget[] => {
|
||||
if (!nodeData?.widgets) return []
|
||||
|
||||
const widgets = nodeData.widgets as SafeWidgetData[]
|
||||
const { widgets } = nodeData
|
||||
const result: ProcessedWidget[] = []
|
||||
|
||||
for (const widget of widgets) {
|
||||
@@ -160,14 +161,14 @@ const processedWidgets = computed((): ProcessedWidget[] => {
|
||||
spec: widget.spec
|
||||
}
|
||||
|
||||
const updateHandler = (value: unknown) => {
|
||||
const updateHandler = (value: WidgetValue) => {
|
||||
// Update the widget value directly
|
||||
widget.value = value as WidgetValue
|
||||
widget.value = value
|
||||
|
||||
// Skip callback for asset widgets - their callback opens the modal,
|
||||
// but Vue asset mode handles selection through the dropdown
|
||||
if (widget.callback && widget.type !== 'asset') {
|
||||
widget.callback(value)
|
||||
if (widget.type !== 'asset') {
|
||||
widget.callback?.(value)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<div v-if="renderError" class="node-error p-1 text-xs text-red-500">⚠️</div>
|
||||
<div v-else v-tooltip.right="tooltipConfig" :class="slotWrapperClass">
|
||||
<div class="relative h-full flex items-center">
|
||||
<div class="relative h-full flex items-center min-w-0">
|
||||
<!-- Slot Name -->
|
||||
<span
|
||||
v-if="!dotOnly"
|
||||
class="lod-toggle text-xs font-normal whitespace-nowrap text-node-component-slot-text"
|
||||
class="lod-toggle text-xs font-normal truncate text-node-component-slot-text"
|
||||
>
|
||||
{{ slotData.localized_name || slotData.name || `Output ${index}` }}
|
||||
</span>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type {
|
||||
TooltipDirectivePassThroughOptions,
|
||||
TooltipOptions,
|
||||
TooltipPassThroughMethodOptions
|
||||
} from 'primevue/tooltip'
|
||||
import { computed, ref, unref } from 'vue'
|
||||
@@ -9,7 +9,7 @@ import type { SafeWidgetData } from '@/composables/graph/useGraphNodeManager'
|
||||
import { st } from '@/i18n'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { useNodeDefStore } from '@/stores/nodeDefStore'
|
||||
import { normalizeI18nKey } from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
import { normalizeI18nKey } from '@/utils/formatUtil'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
/**
|
||||
@@ -148,7 +148,7 @@ export function useNodeTooltips(nodeType: MaybeRef<string>) {
|
||||
* Create tooltip configuration object for v-tooltip directive
|
||||
* Components wrap this in computed() for reactivity
|
||||
*/
|
||||
const createTooltipConfig = (text: string) => {
|
||||
const createTooltipConfig = (text: string): TooltipOptions => {
|
||||
const tooltipDelay = settingsStore.get('LiteGraph.Node.TooltipDelay')
|
||||
const tooltipText = text || ''
|
||||
|
||||
@@ -174,7 +174,7 @@ export function useNodeTooltips(nodeType: MaybeRef<string>) {
|
||||
context.right && 'border-r-node-component-tooltip-border'
|
||||
)
|
||||
})
|
||||
} as TooltipDirectivePassThroughOptions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { app as comfyApp } from '@/scripts/app'
|
||||
import type { SubgraphInputNode } from '@/lib/litegraph/src/subgraph/SubgraphInputNode'
|
||||
import type { SubgraphOutputNode } from '@/lib/litegraph/src/subgraph/SubgraphOutputNode'
|
||||
|
||||
const SCALE_FACTOR = 1.75
|
||||
const SCALE_FACTOR = 1.2
|
||||
|
||||
export function ensureCorrectLayoutScale(
|
||||
renderer?: rendererType,
|
||||
@@ -72,23 +72,32 @@ export function ensureCorrectLayoutScale(
|
||||
? 1 / SCALE_FACTOR
|
||||
: 1
|
||||
|
||||
//TODO: once we remove the need for LiteGraph.NODE_TITLE_HEIGHT in vue nodes we nned to remove everything here.
|
||||
for (const node of graph.nodes) {
|
||||
const lgNode = lgNodesById.get(node.id)
|
||||
if (!lgNode) continue
|
||||
|
||||
const lgBodyY = lgNode.pos[1]
|
||||
|
||||
const adjustedY = needsDownscale
|
||||
? lgBodyY - LiteGraph.NODE_TITLE_HEIGHT / 2
|
||||
: lgBodyY
|
||||
|
||||
const relativeX = lgNode.pos[0] - originX
|
||||
const relativeY = lgBodyY - originY
|
||||
const relativeY = adjustedY - originY
|
||||
const newX = originX + relativeX * scaleFactor
|
||||
const newY = originY + relativeY * scaleFactor
|
||||
const scaledY = originY + relativeY * scaleFactor
|
||||
const newWidth = lgNode.width * scaleFactor
|
||||
const newHeight = lgNode.height * scaleFactor
|
||||
|
||||
const finalY = needsUpscale
|
||||
? scaledY + LiteGraph.NODE_TITLE_HEIGHT / 2
|
||||
: scaledY
|
||||
|
||||
// Directly update LiteGraph node to ensure immediate consistency
|
||||
// Dont need to reference vue directly because the pos and dims are already in yjs
|
||||
lgNode.pos[0] = newX
|
||||
lgNode.pos[1] = newY
|
||||
lgNode.pos[1] = finalY
|
||||
lgNode.size[0] = newWidth
|
||||
lgNode.size[1] =
|
||||
newHeight - (needsDownscale ? LiteGraph.NODE_TITLE_HEIGHT : 0)
|
||||
@@ -99,7 +108,7 @@ export function ensureCorrectLayoutScale(
|
||||
nodeId: String(lgNode.id),
|
||||
bounds: {
|
||||
x: newX,
|
||||
y: newY,
|
||||
y: finalY,
|
||||
width: newWidth,
|
||||
height: newHeight - (needsDownscale ? LiteGraph.NODE_TITLE_HEIGHT : 0)
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ import WidgetSelect from './WidgetSelect.vue'
|
||||
import AudioPreviewPlayer from './audio/AudioPreviewPlayer.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
widget: SimplifiedWidget<string | number | undefined>
|
||||
widget: SimplifiedWidget<string | undefined>
|
||||
readonly?: boolean
|
||||
nodeId: string
|
||||
}>()
|
||||
|
||||
@@ -5,13 +5,12 @@ import PrimeVue from 'primevue/config'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
|
||||
import { createMockWidget } from '../testUtils'
|
||||
|
||||
import WidgetColorPicker from './WidgetColorPicker.vue'
|
||||
import WidgetLayoutField from './layout/WidgetLayoutField.vue'
|
||||
|
||||
describe('WidgetColorPicker Value Binding', () => {
|
||||
const createLocalMockWidget = (
|
||||
const createMockWidget = (
|
||||
value: string = '#000000',
|
||||
options: Partial<ColorPickerProps> = {},
|
||||
callback?: (value: string) => void
|
||||
@@ -55,7 +54,7 @@ describe('WidgetColorPicker Value Binding', () => {
|
||||
|
||||
describe('Vue Event Emission', () => {
|
||||
it('emits Vue event when color changes', async () => {
|
||||
const widget = createLocalMockWidget('#ff0000')
|
||||
const widget = createMockWidget('#ff0000')
|
||||
const wrapper = mountComponent(widget, '#ff0000')
|
||||
|
||||
const emitted = await setColorPickerValue(wrapper, '#00ff00')
|
||||
@@ -65,7 +64,7 @@ describe('WidgetColorPicker Value Binding', () => {
|
||||
})
|
||||
|
||||
it('handles different color formats', async () => {
|
||||
const widget = createLocalMockWidget('#ffffff')
|
||||
const widget = createMockWidget('#ffffff')
|
||||
const wrapper = mountComponent(widget, '#ffffff')
|
||||
|
||||
const emitted = await setColorPickerValue(wrapper, '#123abc')
|
||||
@@ -75,7 +74,7 @@ describe('WidgetColorPicker Value Binding', () => {
|
||||
})
|
||||
|
||||
it('handles missing callback gracefully', async () => {
|
||||
const widget = createLocalMockWidget('#000000', {}, undefined)
|
||||
const widget = createMockWidget('#000000', {}, undefined)
|
||||
const wrapper = mountComponent(widget, '#000000')
|
||||
|
||||
const emitted = await setColorPickerValue(wrapper, '#ff00ff')
|
||||
@@ -86,7 +85,7 @@ describe('WidgetColorPicker Value Binding', () => {
|
||||
})
|
||||
|
||||
it('normalizes bare hex without # to #hex on emit', async () => {
|
||||
const widget = createLocalMockWidget('ff0000')
|
||||
const widget = createMockWidget('ff0000')
|
||||
const wrapper = mountComponent(widget, 'ff0000')
|
||||
|
||||
const emitted = await setColorPickerValue(wrapper, '00ff00')
|
||||
@@ -96,7 +95,7 @@ describe('WidgetColorPicker Value Binding', () => {
|
||||
|
||||
it('normalizes rgb() strings to #hex on emit', async (context) => {
|
||||
context.skip('needs diagnosis')
|
||||
const widget = createLocalMockWidget('#000000')
|
||||
const widget = createMockWidget('#000000')
|
||||
const wrapper = mountComponent(widget, '#000000')
|
||||
|
||||
const emitted = await setColorPickerValue(wrapper, 'rgb(255, 0, 0)')
|
||||
@@ -105,20 +104,7 @@ describe('WidgetColorPicker Value Binding', () => {
|
||||
})
|
||||
|
||||
it('normalizes hsb() strings to #hex on emit', async () => {
|
||||
const widget = createMockWidget<string>(
|
||||
'#000000',
|
||||
{},
|
||||
undefined,
|
||||
{
|
||||
name: 'test_color',
|
||||
type: 'color'
|
||||
},
|
||||
{
|
||||
type: 'COLOR',
|
||||
name: 'test_color',
|
||||
options: { format: 'hsb' }
|
||||
}
|
||||
)
|
||||
const widget = createMockWidget('#000000', { format: 'hsb' })
|
||||
const wrapper = mountComponent(widget, '#000000')
|
||||
|
||||
const emitted = await setColorPickerValue(wrapper, 'hsb(120, 100, 100)')
|
||||
@@ -126,22 +112,8 @@ describe('WidgetColorPicker Value Binding', () => {
|
||||
expect(emitted![0]).toContain('#00ff00')
|
||||
})
|
||||
|
||||
it('normalizes HSB object values to #hex on emit', async (context) => {
|
||||
context.skip('PrimeVue ColorPicker has issues with HSB object values')
|
||||
const widget = createMockWidget<string>(
|
||||
'#000000',
|
||||
{},
|
||||
undefined,
|
||||
{
|
||||
name: 'test_color',
|
||||
type: 'color'
|
||||
},
|
||||
{
|
||||
type: 'COLOR',
|
||||
name: 'test_color',
|
||||
options: { format: 'hsb' }
|
||||
}
|
||||
)
|
||||
it('normalizes HSB object values to #hex on emit', async () => {
|
||||
const widget = createMockWidget('#000000', { format: 'hsb' })
|
||||
const wrapper = mountComponent(widget, '#000000')
|
||||
|
||||
const emitted = await setColorPickerValue(wrapper, {
|
||||
@@ -156,7 +128,7 @@ describe('WidgetColorPicker Value Binding', () => {
|
||||
|
||||
describe('Component Rendering', () => {
|
||||
it('renders color picker component', () => {
|
||||
const widget = createLocalMockWidget('#ff0000')
|
||||
const widget = createMockWidget('#ff0000')
|
||||
const wrapper = mountComponent(widget, '#ff0000')
|
||||
|
||||
const colorPicker = wrapper.findComponent({ name: 'ColorPicker' })
|
||||
@@ -165,20 +137,20 @@ describe('WidgetColorPicker Value Binding', () => {
|
||||
|
||||
it('normalizes display to a single leading #', () => {
|
||||
// Case 1: model value already includes '#'
|
||||
let widget = createLocalMockWidget('#ff0000')
|
||||
let widget = createMockWidget('#ff0000')
|
||||
let wrapper = mountComponent(widget, '#ff0000')
|
||||
let colorText = wrapper.find('[data-testid="widget-color-text"]')
|
||||
expect.soft(colorText.text()).toBe('#ff0000')
|
||||
|
||||
// Case 2: model value missing '#'
|
||||
widget = createLocalMockWidget('ff0000')
|
||||
widget = createMockWidget('ff0000')
|
||||
wrapper = mountComponent(widget, 'ff0000')
|
||||
colorText = wrapper.find('[data-testid="widget-color-text"]')
|
||||
expect.soft(colorText.text()).toBe('#ff0000')
|
||||
})
|
||||
|
||||
it('renders layout field wrapper', () => {
|
||||
const widget = createLocalMockWidget('#ff0000')
|
||||
const widget = createMockWidget('#ff0000')
|
||||
const wrapper = mountComponent(widget, '#ff0000')
|
||||
|
||||
const layoutField = wrapper.findComponent({ name: 'WidgetLayoutField' })
|
||||
@@ -186,7 +158,7 @@ describe('WidgetColorPicker Value Binding', () => {
|
||||
})
|
||||
|
||||
it('displays current color value as text', () => {
|
||||
const widget = createLocalMockWidget('#ff0000')
|
||||
const widget = createMockWidget('#ff0000')
|
||||
const wrapper = mountComponent(widget, '#ff0000')
|
||||
|
||||
const colorText = wrapper.find('[data-testid="widget-color-text"]')
|
||||
@@ -194,7 +166,7 @@ describe('WidgetColorPicker Value Binding', () => {
|
||||
})
|
||||
|
||||
it('updates color text when value changes', async () => {
|
||||
const widget = createLocalMockWidget('#ff0000')
|
||||
const widget = createMockWidget('#ff0000')
|
||||
const wrapper = mountComponent(widget, '#ff0000')
|
||||
|
||||
await setColorPickerValue(wrapper, '#00ff00')
|
||||
@@ -206,7 +178,7 @@ describe('WidgetColorPicker Value Binding', () => {
|
||||
})
|
||||
|
||||
it('uses default color when no value provided', () => {
|
||||
const widget = createLocalMockWidget('')
|
||||
const widget = createMockWidget('')
|
||||
const wrapper = mountComponent(widget, '')
|
||||
|
||||
const colorPicker = wrapper.findComponent({ name: 'ColorPicker' })
|
||||
@@ -227,7 +199,7 @@ describe('WidgetColorPicker Value Binding', () => {
|
||||
]
|
||||
|
||||
for (const color of validHexColors) {
|
||||
const widget = createLocalMockWidget(color)
|
||||
const widget = createMockWidget(color)
|
||||
const wrapper = mountComponent(widget, color)
|
||||
|
||||
const colorText = wrapper.find('[data-testid="widget-color-text"]')
|
||||
@@ -236,7 +208,7 @@ describe('WidgetColorPicker Value Binding', () => {
|
||||
})
|
||||
|
||||
it('handles short hex colors', () => {
|
||||
const widget = createLocalMockWidget('#fff')
|
||||
const widget = createMockWidget('#fff')
|
||||
const wrapper = mountComponent(widget, '#fff')
|
||||
|
||||
const colorText = wrapper.find('[data-testid="widget-color-text"]')
|
||||
@@ -248,7 +220,7 @@ describe('WidgetColorPicker Value Binding', () => {
|
||||
format: 'hex' as const,
|
||||
inline: true
|
||||
}
|
||||
const widget = createLocalMockWidget('#ff0000', colorOptions)
|
||||
const widget = createMockWidget('#ff0000', colorOptions)
|
||||
const wrapper = mountComponent(widget, '#ff0000')
|
||||
|
||||
const colorPicker = wrapper.findComponent({ name: 'ColorPicker' })
|
||||
@@ -259,7 +231,7 @@ describe('WidgetColorPicker Value Binding', () => {
|
||||
|
||||
describe('Widget Layout Integration', () => {
|
||||
it('passes widget to layout field', () => {
|
||||
const widget = createLocalMockWidget('#ff0000')
|
||||
const widget = createMockWidget('#ff0000')
|
||||
const wrapper = mountComponent(widget, '#ff0000')
|
||||
|
||||
const layoutField = wrapper.findComponent({ name: 'WidgetLayoutField' })
|
||||
@@ -267,7 +239,7 @@ describe('WidgetColorPicker Value Binding', () => {
|
||||
})
|
||||
|
||||
it('maintains proper component structure', () => {
|
||||
const widget = createLocalMockWidget('#ff0000')
|
||||
const widget = createMockWidget('#ff0000')
|
||||
const wrapper = mountComponent(widget, '#ff0000')
|
||||
|
||||
// Should have layout field containing label with color picker and text
|
||||
@@ -285,7 +257,7 @@ describe('WidgetColorPicker Value Binding', () => {
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
it('handles empty color value', () => {
|
||||
const widget = createLocalMockWidget('')
|
||||
const widget = createMockWidget('')
|
||||
const wrapper = mountComponent(widget, '')
|
||||
|
||||
const colorPicker = wrapper.findComponent({ name: 'ColorPicker' })
|
||||
@@ -293,7 +265,7 @@ describe('WidgetColorPicker Value Binding', () => {
|
||||
})
|
||||
|
||||
it('handles invalid color formats gracefully', async () => {
|
||||
const widget = createLocalMockWidget('invalid-color')
|
||||
const widget = createMockWidget('invalid-color')
|
||||
const wrapper = mountComponent(widget, 'invalid-color')
|
||||
|
||||
const colorText = wrapper.find('[data-testid="widget-color-text"]')
|
||||
@@ -305,7 +277,7 @@ describe('WidgetColorPicker Value Binding', () => {
|
||||
})
|
||||
|
||||
it('handles widget with no options', () => {
|
||||
const widget = createLocalMockWidget('#ff0000')
|
||||
const widget = createMockWidget('#ff0000')
|
||||
const wrapper = mountComponent(widget, '#ff0000')
|
||||
|
||||
const colorPicker = wrapper.findComponent({ name: 'ColorPicker' })
|
||||
|
||||
@@ -16,9 +16,11 @@
|
||||
}"
|
||||
@update:model-value="onPickerUpdate"
|
||||
/>
|
||||
<span class="text-xs" data-testid="widget-color-text">{{
|
||||
toHexFromFormat(localValue, format)
|
||||
}}</span>
|
||||
<span
|
||||
class="text-xs truncate min-w-[4ch]"
|
||||
data-testid="widget-color-text"
|
||||
>{{ toHexFromFormat(localValue, format) }}</span
|
||||
>
|
||||
</label>
|
||||
</WidgetLayoutField>
|
||||
</template>
|
||||
@@ -27,7 +29,6 @@
|
||||
import ColorPicker from 'primevue/colorpicker'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
import { isColorInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
|
||||
import { isColorFormat, toHexFromFormat } from '@/utils/colorUtil'
|
||||
import type { ColorFormat, HSB } from '@/utils/colorUtil'
|
||||
@@ -52,18 +53,18 @@ const emit = defineEmits<{
|
||||
}>()
|
||||
|
||||
const format = computed<ColorFormat>(() => {
|
||||
const spec = props.widget.spec
|
||||
if (!spec || !isColorInputSpec(spec)) {
|
||||
return 'hex'
|
||||
}
|
||||
|
||||
const optionFormat = spec.options?.format
|
||||
const optionFormat = props.widget.options?.format
|
||||
return isColorFormat(optionFormat) ? optionFormat : 'hex'
|
||||
})
|
||||
|
||||
type PickerValue = string | HSB
|
||||
const localValue = ref<PickerValue>(
|
||||
toHexFromFormat(props.modelValue || '#000000', format.value)
|
||||
toHexFromFormat(
|
||||
props.modelValue || '#000000',
|
||||
isColorFormat(props.widget.options?.format)
|
||||
? props.widget.options.format
|
||||
: 'hex'
|
||||
)
|
||||
)
|
||||
|
||||
watch(
|
||||
|
||||
@@ -1,507 +0,0 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import Button from 'primevue/button'
|
||||
import PrimeVue from 'primevue/config'
|
||||
import Select from 'primevue/select'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
import enMessages from '@/locales/en/main.json'
|
||||
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
|
||||
|
||||
import { createMockFile, createMockWidget } from '../testUtils'
|
||||
import WidgetFileUpload from './WidgetFileUpload.vue'
|
||||
|
||||
describe('WidgetFileUpload File Handling', () => {
|
||||
const mountComponent = (
|
||||
widget: SimplifiedWidget<File[] | null>,
|
||||
modelValue: File[] | null,
|
||||
readonly = false
|
||||
) => {
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
messages: {
|
||||
en: {
|
||||
...enMessages,
|
||||
widgetFileUpload: {
|
||||
dropPrompt: 'Drop your file or',
|
||||
browseFiles: 'Browse Files'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return mount(WidgetFileUpload, {
|
||||
global: {
|
||||
plugins: [PrimeVue, i18n],
|
||||
components: { Button, Select }
|
||||
},
|
||||
props: {
|
||||
widget,
|
||||
modelValue,
|
||||
readonly
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const mockObjectURL = 'blob:mock-url'
|
||||
|
||||
beforeEach(() => {
|
||||
// Mock URL.createObjectURL and revokeObjectURL
|
||||
global.URL.createObjectURL = vi.fn(() => mockObjectURL)
|
||||
global.URL.revokeObjectURL = vi.fn()
|
||||
})
|
||||
|
||||
describe('Initial States', () => {
|
||||
it('shows upload UI when no file is selected', () => {
|
||||
const widget = createMockWidget<File[] | null>(null, {}, undefined, {
|
||||
name: 'test_file_upload',
|
||||
type: 'file'
|
||||
})
|
||||
const wrapper = mountComponent(widget, null)
|
||||
|
||||
expect(wrapper.text()).toContain('Drop your file or')
|
||||
expect(wrapper.text()).toContain('Browse Files')
|
||||
expect(wrapper.find('button').text()).toBe('Browse Files')
|
||||
})
|
||||
|
||||
it('renders file input with correct attributes', () => {
|
||||
const widget = createMockWidget<File[] | null>(
|
||||
null,
|
||||
{},
|
||||
undefined,
|
||||
{
|
||||
name: 'test_file_upload',
|
||||
type: 'file'
|
||||
},
|
||||
{
|
||||
type: 'FILEUPLOAD',
|
||||
name: 'test_file_upload',
|
||||
options: {
|
||||
accept: 'image/*'
|
||||
}
|
||||
}
|
||||
)
|
||||
const wrapper = mountComponent(widget, null)
|
||||
|
||||
const fileInput = wrapper.find('input[type="file"]')
|
||||
expect(fileInput.exists()).toBe(true)
|
||||
expect(fileInput.attributes('accept')).toBe('image/*')
|
||||
expect(fileInput.classes()).toContain('hidden')
|
||||
})
|
||||
})
|
||||
|
||||
describe('File Selection', () => {
|
||||
it('triggers file input when browse button is clicked', async () => {
|
||||
const widget = createMockWidget<File[] | null>(null, {}, undefined, {
|
||||
name: 'test_file_upload',
|
||||
type: 'file'
|
||||
})
|
||||
const wrapper = mountComponent(widget, null)
|
||||
|
||||
const fileInput = wrapper.find('input[type="file"]')
|
||||
const inputElement = fileInput.element
|
||||
if (!(inputElement instanceof HTMLInputElement)) {
|
||||
throw new Error('Expected HTMLInputElement')
|
||||
}
|
||||
const clickSpy = vi.spyOn(inputElement, 'click')
|
||||
|
||||
const browseButton = wrapper.find('button')
|
||||
await browseButton.trigger('click')
|
||||
|
||||
expect(clickSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('handles file selection', async () => {
|
||||
const mockCallback = vi.fn()
|
||||
const widget = createMockWidget<File[] | null>(null, {}, mockCallback, {
|
||||
name: 'test_file_upload',
|
||||
type: 'file'
|
||||
})
|
||||
const wrapper = mountComponent(widget, null)
|
||||
|
||||
const file = createMockFile('test.jpg', 'image/jpeg')
|
||||
const fileInput = wrapper.find('input[type="file"]')
|
||||
|
||||
Object.defineProperty(fileInput.element, 'files', {
|
||||
value: [file],
|
||||
writable: false
|
||||
})
|
||||
|
||||
await fileInput.trigger('change')
|
||||
|
||||
const emitted = wrapper.emitted('update:modelValue')
|
||||
expect(emitted).toBeDefined()
|
||||
expect(emitted![0]).toEqual([[file]])
|
||||
})
|
||||
|
||||
it('resets file input after selection', async () => {
|
||||
const widget = createMockWidget<File[] | null>(null, {}, undefined, {
|
||||
name: 'test_file_upload',
|
||||
type: 'file'
|
||||
})
|
||||
const wrapper = mountComponent(widget, null)
|
||||
|
||||
const file = createMockFile('test.jpg', 'image/jpeg')
|
||||
const fileInput = wrapper.find('input[type="file"]')
|
||||
|
||||
Object.defineProperty(fileInput.element, 'files', {
|
||||
value: [file],
|
||||
writable: false
|
||||
})
|
||||
|
||||
await fileInput.trigger('change')
|
||||
|
||||
const inputElement = fileInput.element
|
||||
if (!(inputElement instanceof HTMLInputElement)) {
|
||||
throw new Error('Expected HTMLInputElement')
|
||||
}
|
||||
expect(inputElement.value).toBe('')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Image File Display', () => {
|
||||
it('shows image preview for image files', () => {
|
||||
const imageFile = createMockFile('test.jpg', 'image/jpeg')
|
||||
const widget = createMockWidget<File[] | null>(
|
||||
[imageFile],
|
||||
{},
|
||||
undefined,
|
||||
{
|
||||
name: 'test_file_upload',
|
||||
type: 'file'
|
||||
}
|
||||
)
|
||||
const wrapper = mountComponent(widget, [imageFile])
|
||||
|
||||
const img = wrapper.find('img')
|
||||
expect(img.exists()).toBe(true)
|
||||
expect(img.attributes('src')).toBe(mockObjectURL)
|
||||
expect(img.attributes('alt')).toBe('test.jpg')
|
||||
})
|
||||
|
||||
it('shows select dropdown with filename for images', () => {
|
||||
const imageFile = createMockFile('test.jpg', 'image/jpeg')
|
||||
const widget = createMockWidget<File[] | null>(
|
||||
[imageFile],
|
||||
{},
|
||||
undefined,
|
||||
{
|
||||
name: 'test_file_upload',
|
||||
type: 'file'
|
||||
}
|
||||
)
|
||||
const wrapper = mountComponent(widget, [imageFile])
|
||||
|
||||
const select = wrapper.getComponent({ name: 'Select' })
|
||||
expect(select.props('modelValue')).toBe('test.jpg')
|
||||
expect(select.props('options')).toEqual(['test.jpg'])
|
||||
expect(select.props('disabled')).toBe(true)
|
||||
})
|
||||
|
||||
it('shows edit and delete buttons on hover for images', () => {
|
||||
const imageFile = createMockFile('test.jpg', 'image/jpeg')
|
||||
const widget = createMockWidget<File[] | null>(
|
||||
[imageFile],
|
||||
{},
|
||||
undefined,
|
||||
{
|
||||
name: 'test_file_upload',
|
||||
type: 'file'
|
||||
}
|
||||
)
|
||||
const wrapper = mountComponent(widget, [imageFile])
|
||||
|
||||
// The pi-pencil and pi-times classes are on the <i> elements inside the buttons
|
||||
const editIcon = wrapper.find('i.pi-pencil')
|
||||
const deleteIcon = wrapper.find('i.pi-times')
|
||||
|
||||
expect(editIcon.exists()).toBe(true)
|
||||
expect(deleteIcon.exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Audio File Display', () => {
|
||||
it('shows audio player for audio files', () => {
|
||||
const audioFile = createMockFile('test.mp3', 'audio/mpeg')
|
||||
const widget = createMockWidget<File[] | null>(
|
||||
[audioFile],
|
||||
{},
|
||||
undefined,
|
||||
{
|
||||
name: 'test_file_upload',
|
||||
type: 'file'
|
||||
}
|
||||
)
|
||||
const wrapper = mountComponent(widget, [audioFile])
|
||||
|
||||
expect(wrapper.find('.pi-volume-up').exists()).toBe(true)
|
||||
expect(wrapper.text()).toContain('test.mp3')
|
||||
expect(wrapper.text()).toContain('1.0 KB')
|
||||
})
|
||||
|
||||
it('shows file size for audio files', () => {
|
||||
const audioFile = createMockFile('test.mp3', 'audio/mpeg', 2048)
|
||||
const widget = createMockWidget<File[] | null>(
|
||||
[audioFile],
|
||||
{},
|
||||
undefined,
|
||||
{
|
||||
name: 'test_file_upload',
|
||||
type: 'file'
|
||||
}
|
||||
)
|
||||
const wrapper = mountComponent(widget, [audioFile])
|
||||
|
||||
expect(wrapper.text()).toContain('2.0 KB')
|
||||
})
|
||||
|
||||
it('shows delete button for audio files', () => {
|
||||
const audioFile = createMockFile('test.mp3', 'audio/mpeg')
|
||||
const widget = createMockWidget<File[] | null>(
|
||||
[audioFile],
|
||||
{},
|
||||
undefined,
|
||||
{
|
||||
name: 'test_file_upload',
|
||||
type: 'file'
|
||||
}
|
||||
)
|
||||
const wrapper = mountComponent(widget, [audioFile])
|
||||
|
||||
const deleteIcon = wrapper.find('i.pi-times')
|
||||
expect(deleteIcon.exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('File Type Detection', () => {
|
||||
const imageFiles = [
|
||||
{ name: 'image.jpg', type: 'image/jpeg' },
|
||||
{ name: 'image.png', type: 'image/png' }
|
||||
]
|
||||
|
||||
const audioFiles = [
|
||||
{ name: 'audio.mp3', type: 'audio/mpeg' },
|
||||
{ name: 'audio.wav', type: 'audio/wav' }
|
||||
]
|
||||
|
||||
const normalFiles = [
|
||||
{ name: 'video.mp4', type: 'video/mp4' },
|
||||
{ name: 'document.pdf', type: 'application/pdf' }
|
||||
]
|
||||
|
||||
it.for(imageFiles)(
|
||||
'shows image preview for $type files',
|
||||
({ name, type }) => {
|
||||
const file = createMockFile(name, type)
|
||||
const widget = createMockWidget<File[] | null>([file], {}, undefined, {
|
||||
name: 'test_file_upload',
|
||||
type: 'file'
|
||||
})
|
||||
const wrapper = mountComponent(widget, [file])
|
||||
|
||||
expect(wrapper.find('img').exists()).toBe(true)
|
||||
expect(wrapper.find('.pi-volume-up').exists()).toBe(false)
|
||||
}
|
||||
)
|
||||
|
||||
it.for(audioFiles)(
|
||||
'shows audio player for $type files',
|
||||
({ name, type }) => {
|
||||
const file = createMockFile(name, type)
|
||||
const widget = createMockWidget<File[] | null>([file], {}, undefined, {
|
||||
name: 'test_file_upload',
|
||||
type: 'file'
|
||||
})
|
||||
const wrapper = mountComponent(widget, [file])
|
||||
|
||||
expect(wrapper.find('.pi-volume-up').exists()).toBe(true)
|
||||
expect(wrapper.find('img').exists()).toBe(false)
|
||||
}
|
||||
)
|
||||
|
||||
it.for(normalFiles)('shows normal UI for $type files', ({ name, type }) => {
|
||||
const file = createMockFile(name, type)
|
||||
const widget = createMockWidget<File[] | null>([file], {}, undefined, {
|
||||
name: 'test_file_upload',
|
||||
type: 'file'
|
||||
})
|
||||
const wrapper = mountComponent(widget, [file])
|
||||
|
||||
expect(wrapper.find('img').exists()).toBe(false)
|
||||
expect(wrapper.find('.pi-volume-up').exists()).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('File Actions', () => {
|
||||
it('clears file when delete button is clicked', async () => {
|
||||
const imageFile = createMockFile('test.jpg', 'image/jpeg')
|
||||
const widget = createMockWidget<File[] | null>(
|
||||
[imageFile],
|
||||
{},
|
||||
undefined,
|
||||
{
|
||||
name: 'test_file_upload',
|
||||
type: 'file'
|
||||
}
|
||||
)
|
||||
const wrapper = mountComponent(widget, [imageFile])
|
||||
|
||||
// Find button that contains the times icon
|
||||
const buttons = wrapper.findAll('button')
|
||||
const deleteButton = buttons.find((button) =>
|
||||
button.find('i.pi-times').exists()
|
||||
)
|
||||
|
||||
if (!deleteButton) {
|
||||
throw new Error('Delete button with times icon not found')
|
||||
}
|
||||
|
||||
await deleteButton.trigger('click')
|
||||
|
||||
const emitted = wrapper.emitted('update:modelValue')
|
||||
expect(emitted).toBeDefined()
|
||||
expect(emitted![emitted!.length - 1]).toEqual([null])
|
||||
})
|
||||
|
||||
it('handles edit button click', async () => {
|
||||
const imageFile = createMockFile('test.jpg', 'image/jpeg')
|
||||
const widget = createMockWidget<File[] | null>(
|
||||
[imageFile],
|
||||
{},
|
||||
undefined,
|
||||
{
|
||||
name: 'test_file_upload',
|
||||
type: 'file'
|
||||
}
|
||||
)
|
||||
const wrapper = mountComponent(widget, [imageFile])
|
||||
|
||||
// Find button that contains the pencil icon
|
||||
const buttons = wrapper.findAll('button')
|
||||
const editButton = buttons.find((button) =>
|
||||
button.find('i.pi-pencil').exists()
|
||||
)
|
||||
|
||||
if (!editButton) {
|
||||
throw new Error('Edit button with pencil icon not found')
|
||||
}
|
||||
|
||||
// Should not throw error when clicked (TODO: implement edit functionality)
|
||||
await expect(editButton.trigger('click')).resolves.not.toThrow()
|
||||
})
|
||||
|
||||
it('triggers file input when folder button is clicked', async () => {
|
||||
const imageFile = createMockFile('test.jpg', 'image/jpeg')
|
||||
const widget = createMockWidget<File[] | null>(
|
||||
[imageFile],
|
||||
{},
|
||||
undefined,
|
||||
{
|
||||
name: 'test_file_upload',
|
||||
type: 'file'
|
||||
}
|
||||
)
|
||||
const wrapper = mountComponent(widget, [imageFile])
|
||||
|
||||
const fileInput = wrapper.find('input[type="file"]')
|
||||
const inputElement = fileInput.element
|
||||
if (!(inputElement instanceof HTMLInputElement)) {
|
||||
throw new Error('Expected HTMLInputElement')
|
||||
}
|
||||
const clickSpy = vi.spyOn(inputElement, 'click')
|
||||
|
||||
// Find PrimeVue Button component with folder icon
|
||||
const folderButton = wrapper.getComponent(Button)
|
||||
|
||||
await folderButton.trigger('click')
|
||||
|
||||
expect(clickSpy).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
it('handles empty file selection gracefully', async () => {
|
||||
const widget = createMockWidget<File[] | null>(null, {}, undefined, {
|
||||
name: 'test_file_upload',
|
||||
type: 'file'
|
||||
})
|
||||
const wrapper = mountComponent(widget, null)
|
||||
|
||||
const fileInput = wrapper.find('input[type="file"]')
|
||||
|
||||
Object.defineProperty(fileInput.element, 'files', {
|
||||
value: [],
|
||||
writable: false
|
||||
})
|
||||
|
||||
await fileInput.trigger('change')
|
||||
|
||||
const emitted = wrapper.emitted('update:modelValue')
|
||||
expect(emitted).toBeUndefined()
|
||||
})
|
||||
|
||||
it('handles missing file input gracefully', () => {
|
||||
const widget = createMockWidget<File[] | null>(null, {}, undefined, {
|
||||
name: 'test_file_upload',
|
||||
type: 'file'
|
||||
})
|
||||
const wrapper = mountComponent(widget, null)
|
||||
|
||||
// Remove file input ref to simulate missing element
|
||||
wrapper.vm.$refs.fileInputRef = null
|
||||
|
||||
// Should not throw error when method exists
|
||||
const vm = wrapper.vm as any
|
||||
expect(() => vm.triggerFileInput?.()).not.toThrow()
|
||||
})
|
||||
|
||||
it('handles clearing file when no file input exists', async () => {
|
||||
const imageFile = createMockFile('test.jpg', 'image/jpeg')
|
||||
const widget = createMockWidget<File[] | null>(
|
||||
[imageFile],
|
||||
{},
|
||||
undefined,
|
||||
{
|
||||
name: 'test_file_upload',
|
||||
type: 'file'
|
||||
}
|
||||
)
|
||||
const wrapper = mountComponent(widget, [imageFile])
|
||||
|
||||
// Remove file input ref to simulate missing element
|
||||
wrapper.vm.$refs.fileInputRef = null
|
||||
|
||||
// Find button that contains the times icon
|
||||
const buttons = wrapper.findAll('button')
|
||||
const deleteButton = buttons.find((button) =>
|
||||
button.find('i.pi-times').exists()
|
||||
)
|
||||
|
||||
if (!deleteButton) {
|
||||
throw new Error('Delete button with times icon not found')
|
||||
}
|
||||
|
||||
// Should not throw error
|
||||
await expect(deleteButton.trigger('click')).resolves.not.toThrow()
|
||||
})
|
||||
|
||||
it('cleans up object URLs on unmount', () => {
|
||||
const imageFile = createMockFile('test.jpg', 'image/jpeg')
|
||||
const widget = createMockWidget<File[] | null>(
|
||||
[imageFile],
|
||||
{},
|
||||
undefined,
|
||||
{
|
||||
name: 'test_file_upload',
|
||||
type: 'file'
|
||||
}
|
||||
)
|
||||
const wrapper = mountComponent(widget, [imageFile])
|
||||
|
||||
wrapper.unmount()
|
||||
|
||||
expect(global.URL.revokeObjectURL).toHaveBeenCalledWith(mockObjectURL)
|
||||
})
|
||||
})
|
||||
})
|
||||