diff --git a/browser_tests/browser_tests/tests/graphCanvasMenu.spec.ts-snapshots/canvas-with-hidden-links-chromium-linux.png b/browser_tests/browser_tests/tests/graphCanvasMenu.spec.ts-snapshots/canvas-with-hidden-links-chromium-linux.png new file mode 100644 index 0000000000..4dc0b3f43d Binary files /dev/null and b/browser_tests/browser_tests/tests/graphCanvasMenu.spec.ts-snapshots/canvas-with-hidden-links-chromium-linux.png differ diff --git a/browser_tests/browser_tests/tests/graphCanvasMenu.spec.ts-snapshots/canvas-with-visible-links-chromium-linux.png b/browser_tests/browser_tests/tests/graphCanvasMenu.spec.ts-snapshots/canvas-with-visible-links-chromium-linux.png new file mode 100644 index 0000000000..2676f31eb3 Binary files /dev/null and b/browser_tests/browser_tests/tests/graphCanvasMenu.spec.ts-snapshots/canvas-with-visible-links-chromium-linux.png differ diff --git a/browser_tests/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png b/browser_tests/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png new file mode 100644 index 0000000000..cea542a4f0 Binary files /dev/null and b/browser_tests/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png differ diff --git a/browser_tests/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-fit-to-contents-chromium-linux.png b/browser_tests/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-fit-to-contents-chromium-linux.png new file mode 100644 index 0000000000..1b4771e319 Binary files /dev/null and b/browser_tests/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-fit-to-contents-chromium-linux.png differ diff --git a/browser_tests/browser_tests/tests/vueNodes/interactions/canvas/pan.spec.ts-snapshots/vue-nodes-paned-with-touch-mobile-chrome-linux.png b/browser_tests/browser_tests/tests/vueNodes/interactions/canvas/pan.spec.ts-snapshots/vue-nodes-paned-with-touch-mobile-chrome-linux.png new file mode 100644 index 0000000000..ed2e63d047 Binary files /dev/null and b/browser_tests/browser_tests/tests/vueNodes/interactions/canvas/pan.spec.ts-snapshots/vue-nodes-paned-with-touch-mobile-chrome-linux.png differ diff --git a/browser_tests/browser_tests/tests/vueNodes/interactions/canvas/zoom.spec.ts-snapshots/zoomed-in-ctrl-shift-chromium-linux.png b/browser_tests/browser_tests/tests/vueNodes/interactions/canvas/zoom.spec.ts-snapshots/zoomed-in-ctrl-shift-chromium-linux.png new file mode 100644 index 0000000000..391dd76372 Binary files /dev/null and b/browser_tests/browser_tests/tests/vueNodes/interactions/canvas/zoom.spec.ts-snapshots/zoomed-in-ctrl-shift-chromium-linux.png differ diff --git a/browser_tests/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png b/browser_tests/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png new file mode 100644 index 0000000000..4ed230307b Binary files /dev/null and b/browser_tests/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png differ diff --git a/browser_tests/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-ctrl-alt-chromium-linux.png b/browser_tests/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-ctrl-alt-chromium-linux.png new file mode 100644 index 0000000000..077e34405e Binary files /dev/null and b/browser_tests/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-ctrl-alt-chromium-linux.png differ diff --git a/browser_tests/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-reuses-origin-chromium-linux.png b/browser_tests/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-reuses-origin-chromium-linux.png new file mode 100644 index 0000000000..d0235f54bb Binary files /dev/null and b/browser_tests/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-reuses-origin-chromium-linux.png differ diff --git a/browser_tests/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png b/browser_tests/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png new file mode 100644 index 0000000000..e4155c01ff Binary files /dev/null and b/browser_tests/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png differ diff --git a/browser_tests/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png b/browser_tests/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png new file mode 100644 index 0000000000..c5d0151f1c Binary files /dev/null and b/browser_tests/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png differ diff --git a/browser_tests/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-shift-output-multi-link-chromium-linux.png b/browser_tests/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-shift-output-multi-link-chromium-linux.png new file mode 100644 index 0000000000..7b1d91b3d2 Binary files /dev/null and b/browser_tests/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-shift-output-multi-link-chromium-linux.png differ diff --git a/browser_tests/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-node-chromium-linux.png b/browser_tests/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-node-chromium-linux.png new file mode 100644 index 0000000000..26eb49e5ce Binary files /dev/null and b/browser_tests/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-node-chromium-linux.png differ diff --git a/browser_tests/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-slot-chromium-linux.png b/browser_tests/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-slot-chromium-linux.png new file mode 100644 index 0000000000..94d12be43b Binary files /dev/null and b/browser_tests/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-slot-chromium-linux.png differ diff --git a/browser_tests/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-chromium-linux.png b/browser_tests/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-chromium-linux.png new file mode 100644 index 0000000000..e91d3a5fd9 Binary files /dev/null and b/browser_tests/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-chromium-linux.png differ diff --git a/browser_tests/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-touch-mobile-chrome-linux.png b/browser_tests/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-touch-mobile-chrome-linux.png new file mode 100644 index 0000000000..088ec907c6 Binary files /dev/null and b/browser_tests/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-touch-mobile-chrome-linux.png differ diff --git a/browser_tests/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png b/browser_tests/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png new file mode 100644 index 0000000000..6dabeb42ec Binary files /dev/null and b/browser_tests/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png differ diff --git a/browser_tests/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-dark-all-colors-chromium-linux.png b/browser_tests/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-dark-all-colors-chromium-linux.png new file mode 100644 index 0000000000..e04cb92a72 Binary files /dev/null and b/browser_tests/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-dark-all-colors-chromium-linux.png differ diff --git a/browser_tests/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-light-all-colors-chromium-linux.png b/browser_tests/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-light-all-colors-chromium-linux.png new file mode 100644 index 0000000000..6f5e35a1cc Binary files /dev/null and b/browser_tests/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-light-all-colors-chromium-linux.png differ diff --git a/browser_tests/browser_tests/tests/vueNodes/nodeStates/mute.spec.ts-snapshots/vue-node-muted-state-chromium-linux.png b/browser_tests/browser_tests/tests/vueNodes/nodeStates/mute.spec.ts-snapshots/vue-node-muted-state-chromium-linux.png new file mode 100644 index 0000000000..9adde1602a Binary files /dev/null and b/browser_tests/browser_tests/tests/vueNodes/nodeStates/mute.spec.ts-snapshots/vue-node-muted-state-chromium-linux.png differ diff --git a/browser_tests/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png b/browser_tests/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png new file mode 100644 index 0000000000..4cc3ebe2c6 Binary files /dev/null and b/browser_tests/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png differ diff --git a/packages/design-system/src/icons/image-ai-edit.svg b/packages/design-system/src/icons/image-ai-edit.svg new file mode 100644 index 0000000000..437669d6da --- /dev/null +++ b/packages/design-system/src/icons/image-ai-edit.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/shared-frontend-utils/src/formatUtil.ts b/packages/shared-frontend-utils/src/formatUtil.ts index 658498f1fd..cb1a0d1a34 100644 --- a/packages/shared-frontend-utils/src/formatUtil.ts +++ b/packages/shared-frontend-utils/src/formatUtil.ts @@ -474,3 +474,68 @@ export function formatDuration(milliseconds: number): string { return parts.join(' ') } + +const IMAGE_EXTENSIONS = ['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp'] as const +const VIDEO_EXTENSIONS = ['mp4', 'webm', 'mov', 'avi'] as const +const AUDIO_EXTENSIONS = ['mp3', 'wav', 'ogg', 'flac'] as const +const THREE_D_EXTENSIONS = ['obj', 'fbx', 'gltf', 'glb'] as const + +const MEDIA_TYPES = ['image', 'video', 'audio', '3D'] as const +type MediaType = (typeof MEDIA_TYPES)[number] + +// Type guard helper for checking array membership +type ImageExtension = (typeof IMAGE_EXTENSIONS)[number] +type VideoExtension = (typeof VIDEO_EXTENSIONS)[number] +type AudioExtension = (typeof AUDIO_EXTENSIONS)[number] +type ThreeDExtension = (typeof THREE_D_EXTENSIONS)[number] + +/** + * Truncates a filename while preserving the extension + * @param filename The filename to truncate + * @param maxLength Maximum length for the filename without extension + * @returns Truncated filename with extension preserved + */ +export function truncateFilename( + filename: string, + maxLength: number = 20 +): string { + if (!filename || filename.length <= maxLength) { + return filename + } + + const lastDotIndex = filename.lastIndexOf('.') + const nameWithoutExt = + lastDotIndex > -1 ? filename.substring(0, lastDotIndex) : filename + const extension = lastDotIndex > -1 ? filename.substring(lastDotIndex) : '' + + // If the name without extension is short enough, return as is + if (nameWithoutExt.length <= maxLength) { + return filename + } + + // Calculate how to split the truncation + const halfLength = Math.floor((maxLength - 3) / 2) // -3 for '...' + const start = nameWithoutExt.substring(0, halfLength) + const end = nameWithoutExt.substring(nameWithoutExt.length - halfLength) + + return `${start}...${end}${extension}` +} + +/** + * Determines the media type from a filename's extension (singular form) + * @param filename The filename to analyze + * @returns The media type: 'image', 'video', 'audio', or '3D' + */ +export function getMediaTypeFromFilename(filename: string): MediaType { + if (!filename) return 'image' + const ext = filename.split('.').pop()?.toLowerCase() + if (!ext) return 'image' + + // Type-safe array includes check using type assertion + if (IMAGE_EXTENSIONS.includes(ext as ImageExtension)) return 'image' + if (VIDEO_EXTENSIONS.includes(ext as VideoExtension)) return 'video' + if (AUDIO_EXTENSIONS.includes(ext as AudioExtension)) return 'audio' + if (THREE_D_EXTENSIONS.includes(ext as ThreeDExtension)) return '3D' + + return 'image' +} diff --git a/src/components/card/CardTop.vue b/src/components/card/CardTop.vue index 6d26d5336b..fb3a528b6c 100644 --- a/src/components/card/CardTop.vue +++ b/src/components/card/CardTop.vue @@ -54,7 +54,7 @@ const { }>() const topStyle = computed(() => { - const baseClasses = 'relative p-0' + const baseClasses = 'relative p-0 overflow-hidden' const ratioClasses = { square: 'aspect-square', diff --git a/src/components/sidebar/tabs/AssetSidebarTemplate.vue b/src/components/sidebar/tabs/AssetSidebarTemplate.vue new file mode 100644 index 0000000000..d387994253 --- /dev/null +++ b/src/components/sidebar/tabs/AssetSidebarTemplate.vue @@ -0,0 +1,33 @@ + + + diff --git a/src/components/sidebar/tabs/AssetsSidebarTab.vue b/src/components/sidebar/tabs/AssetsSidebarTab.vue new file mode 100644 index 0000000000..cdf29b469a --- /dev/null +++ b/src/components/sidebar/tabs/AssetsSidebarTab.vue @@ -0,0 +1,297 @@ + + + diff --git a/src/components/tab/Tab.vue b/src/components/tab/Tab.vue new file mode 100644 index 0000000000..ecdc24e2a4 --- /dev/null +++ b/src/components/tab/Tab.vue @@ -0,0 +1,48 @@ + + + diff --git a/src/components/tab/TabList.stories.ts b/src/components/tab/TabList.stories.ts new file mode 100644 index 0000000000..96ae14cc4c --- /dev/null +++ b/src/components/tab/TabList.stories.ts @@ -0,0 +1,153 @@ +import type { Meta, StoryObj } from '@storybook/vue3-vite' +import { ref } from 'vue' + +import Tab from './Tab.vue' +import TabList from './TabList.vue' + +const meta: Meta = { + title: 'Components/Tab/TabList', + component: TabList, + tags: ['autodocs'], + argTypes: { + modelValue: { + control: 'text', + description: 'The currently selected tab value' + }, + 'onUpdate:modelValue': { action: 'update:modelValue' } + } +} + +export default meta +type Story = StoryObj + +export const Default: Story = { + render: (args) => ({ + components: { TabList, Tab }, + setup() { + const activeTab = ref(args.modelValue || 'tab1') + return { activeTab } + }, + template: ` + + Tab 1 + Tab 2 + Tab 3 + +
+ Selected tab: {{ activeTab }} +
+ ` + }), + args: { + modelValue: 'tab1' + } +} + +export const ManyTabs: Story = { + render: () => ({ + components: { TabList, Tab }, + setup() { + const activeTab = ref('tab1') + return { activeTab } + }, + template: ` + + Dashboard + Analytics + Reports + Settings + Profile + +
+ Selected tab: {{ activeTab }} +
+ ` + }) +} + +export const WithIcons: Story = { + render: () => ({ + components: { TabList, Tab }, + setup() { + const activeTab = ref('home') + return { activeTab } + }, + template: ` + + + + Home + + + + Users + + + + Settings + + +
+ Selected tab: {{ activeTab }} +
+ ` + }) +} + +export const LongLabels: Story = { + render: () => ({ + components: { TabList, Tab }, + setup() { + const activeTab = ref('overview') + return { activeTab } + }, + template: ` + + Project Overview + Documentation & Guides + Deployment Settings + Monitoring & Analytics + +
+ Selected tab: {{ activeTab }} +
+ ` + }) +} + +export const Interactive: Story = { + render: () => ({ + components: { TabList, Tab }, + setup() { + const activeTab = ref('input') + const handleTabChange = (value: string) => { + console.log('Tab changed to:', value) + } + return { activeTab, handleTabChange } + }, + template: ` +
+
+

Example: Media Assets

+ + Imported + Generated + +
+ +
+
+

Showing imported assets...

+
+
+

Showing generated assets...

+
+
+ +
+ Current tab value: {{ activeTab }} +
+
+ ` + }) +} diff --git a/src/components/tab/TabList.vue b/src/components/tab/TabList.vue new file mode 100644 index 0000000000..9ef559acbf --- /dev/null +++ b/src/components/tab/TabList.vue @@ -0,0 +1,17 @@ + + + diff --git a/src/composables/node/useNodeImageUpload.ts b/src/composables/node/useNodeImageUpload.ts index 21a4d7f728..96d450c1d9 100644 --- a/src/composables/node/useNodeImageUpload.ts +++ b/src/composables/node/useNodeImageUpload.ts @@ -5,6 +5,7 @@ import type { LGraphNode } from '@/lib/litegraph/src/litegraph' import { useToastStore } from '@/platform/updates/common/toastStore' import type { ResultItemType } from '@/schemas/apiSchema' import { api } from '@/scripts/api' +import { useAssetsStore } from '@/stores/assetsStore' const PASTED_IMAGE_EXPIRY_MS = 2000 @@ -37,6 +38,13 @@ const uploadFile = async ( } const data = await resp.json() + + // Update AssetsStore input assets when files are uploaded to input folder + if (formFields.type === 'input' || (!formFields.type && !isPasted)) { + const assetsStore = useAssetsStore() + await assetsStore.updateInputs() + } + return data.subfolder ? `${data.subfolder}/${data.name}` : data.name } diff --git a/src/composables/sidebarTabs/useAssetsSidebarTab.ts b/src/composables/sidebarTabs/useAssetsSidebarTab.ts new file mode 100644 index 0000000000..ce37531733 --- /dev/null +++ b/src/composables/sidebarTabs/useAssetsSidebarTab.ts @@ -0,0 +1,16 @@ +import { markRaw } from 'vue' + +import AssetsSidebarTab from '@/components/sidebar/tabs/AssetsSidebarTab.vue' +import type { SidebarTabExtension } from '@/types/extensionTypes' + +export const useAssetsSidebarTab = (): SidebarTabExtension => { + return { + id: 'assets', + icon: 'icon-[comfy--image-ai-edit]', + title: 'sideToolbar.assets', + tooltip: 'sideToolbar.assets', + label: 'sideToolbar.labels.assets', + component: markRaw(AssetsSidebarTab), + type: 'vue' + } +} diff --git a/src/locales/en/main.json b/src/locales/en/main.json index 75eab3749d..f95421968d 100644 --- a/src/locales/en/main.json +++ b/src/locales/en/main.json @@ -599,6 +599,9 @@ "nodeLibrary": "Node Library", "workflows": "Workflows", "templates": "Templates", + "assets": "Assets", + "mediaAssets": "Media Assets", + "backToAssets": "Back to all assets", "labels": { "queue": "Queue", "nodes": "Nodes", @@ -606,8 +609,15 @@ "workflows": "Workflows", "templates": "Templates", "console": "Console", - "menu": "Menu" - }, + "menu": "Menu", + "assets": "Assets", + "imported": "Imported", + "generated": "Generated" + }, + "noFilesFound": "No files found", + "noImportedFiles": "No imported files found", + "noGeneratedFiles": "No generated files found", + "noFilesFoundMessage": "Upload files or generate content to see them here", "browseTemplates": "Browse example templates", "openWorkflow": "Open workflow in local file system", "newBlankWorkflow": "Create a new blank workflow", @@ -1760,7 +1770,24 @@ "sortAZ": "A-Z", "sortZA": "Z-A", "sortRecent": "Recent", - "sortPopular": "Popular" + "sortPopular": "Popular", + "ariaLabel": { + "assetCard": "{name} - {type} asset", + "loadingAsset": "Loading asset" + } + }, + "mediaAsset": { + "deleteAssetTitle": "Delete this asset?", + "deleteAssetDescription": "This asset will be permanently removed.", + "assetDeletedSuccessfully": "Asset deleted successfully", + "deletingImportedFilesCloudOnly": "Deleting imported files is only supported in cloud version", + "failedToDeleteAsset": "Failed to delete asset", + "jobIdToast": { + "jobIdCopied": "Job ID copied to clipboard", + "jobIdCopyFailed": "Failed to copy Job ID", + "copied": "Copied", + "error": "Error" + } }, "actionbar": { "dockToTop": "Dock to top" diff --git a/src/platform/assets/components/Media3DBottom.vue b/src/platform/assets/components/Media3DBottom.vue index 4d3c15c0b1..44f043da0f 100644 --- a/src/platform/assets/components/Media3DBottom.vue +++ b/src/platform/assets/components/Media3DBottom.vue @@ -1,11 +1,6 @@ -