From 76dd935b35db6164ada30513650f68b5a8bc014a Mon Sep 17 00:00:00 2001 From: Arjan Singh <1598641+arjansingh@users.noreply.github.com> Date: Tue, 23 Sep 2025 16:04:54 -0700 Subject: [PATCH 01/52] [fix] use object-contain for image preview (#5739) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Change css so preview images are fully contained within the image preview component ## Screenshots (if applicable) ### Before Screenshot 2025-09-23 at 2 55 56 PM ### After Screenshot 2025-09-23 at 2 55 26 PM ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-5739-fix-use-object-contain-for-image-preview-2776d73d365081f3b77ce498bd8799ec) by [Unito](https://www.unito.io) --- src/renderer/extensions/vueNodes/components/ImagePreview.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/extensions/vueNodes/components/ImagePreview.vue b/src/renderer/extensions/vueNodes/components/ImagePreview.vue index d7219c926d..26ff64e1ee 100644 --- a/src/renderer/extensions/vueNodes/components/ImagePreview.vue +++ b/src/renderer/extensions/vueNodes/components/ImagePreview.vue @@ -35,7 +35,7 @@ v-else :src="currentImageUrl" :alt="imageAltText" - class="w-full h-[352px] object-cover block" + class="w-full h-[352px] object-contain block" @load="handleImageLoad" @error="handleImageError" /> From 80cabc61ee8585aec63df24d7a53d3117fbf6c6f Mon Sep 17 00:00:00 2001 From: Tristan Sommer <43797146+trsommer@users.noreply.github.com> Date: Wed, 24 Sep 2025 01:27:16 +0200 Subject: [PATCH 02/52] fix: maskeditor - fixed color select and paint bucket settings not showing up (#5733) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary color select settings and paint bucket settings were not showing up - fixed that ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-5733-fix-maskeditor-fixed-color-select-and-paint-bucket-settings-not-showing-up-2776d73d365081e6be2ddc7784ab8535) by [Unito](https://www.unito.io) --- src/extensions/core/maskeditor.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/extensions/core/maskeditor.ts b/src/extensions/core/maskeditor.ts index 238056f74a..2497fd2872 100644 --- a/src/extensions/core/maskeditor.ts +++ b/src/extensions/core/maskeditor.ts @@ -3901,6 +3901,19 @@ class UIManager { this.paintBucketSettingsHTML.style.display = 'none' } } + if (tool === Tools.MaskColorFill) { + this.brushSettingsHTML.style.display = 'none' + this.colorSelectSettingsHTML.style.display = 'flex' + this.paintBucketSettingsHTML.style.display = 'none' + } else if (tool === Tools.MaskBucket) { + this.brushSettingsHTML.style.display = 'none' + this.colorSelectSettingsHTML.style.display = 'none' + this.paintBucketSettingsHTML.style.display = 'flex' + } else { + this.brushSettingsHTML.style.display = 'flex' + this.colorSelectSettingsHTML.style.display = 'none' + this.paintBucketSettingsHTML.style.display = 'none' + } this.messageBroker.publish('setTool', tool) this.onToolChange() const newActiveLayer = this.toolSettings[tool].newActiveLayerOnSet From 6449d26cee895414e63b26eede98623c0183f3cd Mon Sep 17 00:00:00 2001 From: Alexander Brown Date: Tue, 23 Sep 2025 21:36:29 -0700 Subject: [PATCH 03/52] cleanup: remove useCanvasTransformSync composables. (#5742) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No I am not proud of the new placeholder arguments. ## Summary Small change to unify the two composables before updating the logic. Edit: Now unifies them both into the **void**. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-5742-cleanup-unify-useCanvasTransformSync-composables-2776d73d36508147ad39d11de8588b2e) by [Unito](https://www.unito.io) --- .../graph/selectionToolbox/MoreOptions.vue | 6 +- .../canvas/useCanvasTransformSync.ts | 136 ---------- .../canvas/useSelectionToolboxPosition.ts | 6 +- .../core/layout/transform/TransformPane.vue | 19 +- .../transform/useCanvasTransformSync.ts | 115 --------- .../minimap/composables/useMinimapViewport.ts | 7 +- .../canvas/useCanvasTransformSync.test.ts | 129 ---------- .../graph/useCanvasTransformSync.test.ts | 240 ------------------ .../composables/useMinimapViewport.test.ts | 20 +- 9 files changed, 29 insertions(+), 649 deletions(-) delete mode 100644 src/composables/canvas/useCanvasTransformSync.ts delete mode 100644 src/renderer/core/layout/transform/useCanvasTransformSync.ts delete mode 100644 tests-ui/tests/composables/canvas/useCanvasTransformSync.test.ts delete mode 100644 tests-ui/tests/composables/graph/useCanvasTransformSync.test.ts diff --git a/src/components/graph/selectionToolbox/MoreOptions.vue b/src/components/graph/selectionToolbox/MoreOptions.vue index f40a49b603..08d240ddae 100644 --- a/src/components/graph/selectionToolbox/MoreOptions.vue +++ b/src/components/graph/selectionToolbox/MoreOptions.vue @@ -49,11 +49,11 @@ diff --git a/src/components/common/StartupDisplay.vue b/src/components/common/StartupDisplay.vue new file mode 100644 index 0000000000..bd42c14e4f --- /dev/null +++ b/src/components/common/StartupDisplay.vue @@ -0,0 +1,71 @@ + + + diff --git a/src/components/install/DesktopSettingsConfiguration.vue b/src/components/install/DesktopSettingsConfiguration.vue index 2868d11a76..f1caa62faa 100644 --- a/src/components/install/DesktopSettingsConfiguration.vue +++ b/src/components/install/DesktopSettingsConfiguration.vue @@ -10,14 +10,14 @@

-
+

{{ $t('install.settings.autoUpdate') }}

-

+

{{ $t('install.settings.autoUpdateDescription') }}

@@ -32,14 +32,10 @@

{{ $t('install.settings.allowMetrics') }}

-

+

{{ $t('install.settings.allowMetricsDescription') }}

- + {{ $t('install.settings.learnMoreAboutData') }}
@@ -51,7 +47,9 @@

@@ -110,11 +108,7 @@ diff --git a/src/components/install/GpuPicker.vue b/src/components/install/GpuPicker.vue index 75916b4805..e95d3204c3 100644 --- a/src/components/install/GpuPicker.vue +++ b/src/components/install/GpuPicker.vue @@ -1,126 +1,66 @@ @@ -128,20 +68,12 @@ - - diff --git a/src/components/install/HardwareOption.stories.ts b/src/components/install/HardwareOption.stories.ts new file mode 100644 index 0000000000..d830af49fa --- /dev/null +++ b/src/components/install/HardwareOption.stories.ts @@ -0,0 +1,73 @@ +// eslint-disable-next-line storybook/no-renderer-packages +import type { Meta, StoryObj } from '@storybook/vue3' + +import HardwareOption from './HardwareOption.vue' + +const meta: Meta = { + title: 'Desktop/Components/HardwareOption', + component: HardwareOption, + parameters: { + layout: 'centered', + backgrounds: { + default: 'dark', + values: [{ name: 'dark', value: '#1a1a1a' }] + } + }, + argTypes: { + selected: { control: 'boolean' }, + imagePath: { control: 'text' }, + placeholderText: { control: 'text' }, + subtitle: { control: 'text' } + } +} + +export default meta +type Story = StoryObj + +export const AppleMetalSelected: Story = { + args: { + imagePath: '/assets/images/apple-mps-logo.png', + placeholderText: 'Apple Metal', + subtitle: 'Apple Metal', + value: 'mps', + selected: true + } +} + +export const AppleMetalUnselected: Story = { + args: { + imagePath: '/assets/images/apple-mps-logo.png', + placeholderText: 'Apple Metal', + subtitle: 'Apple Metal', + value: 'mps', + selected: false + } +} + +export const CPUOption: Story = { + args: { + placeholderText: 'CPU', + subtitle: 'Subtitle', + value: 'cpu', + selected: false + } +} + +export const ManualInstall: Story = { + args: { + placeholderText: 'Manual Install', + subtitle: 'Subtitle', + value: 'unsupported', + selected: false + } +} + +export const NvidiaSelected: Story = { + args: { + imagePath: '/assets/images/nvidia-logo-square.jpg', + placeholderText: 'NVIDIA', + subtitle: 'NVIDIA', + value: 'nvidia', + selected: true + } +} diff --git a/src/components/install/HardwareOption.vue b/src/components/install/HardwareOption.vue new file mode 100644 index 0000000000..ae254fd8f3 --- /dev/null +++ b/src/components/install/HardwareOption.vue @@ -0,0 +1,55 @@ + + + diff --git a/src/components/install/InstallFooter.vue b/src/components/install/InstallFooter.vue new file mode 100644 index 0000000000..ef9ab698c9 --- /dev/null +++ b/src/components/install/InstallFooter.vue @@ -0,0 +1,79 @@ + + + diff --git a/src/components/install/InstallLocationPicker.stories.ts b/src/components/install/InstallLocationPicker.stories.ts new file mode 100644 index 0000000000..e6ef924ae0 --- /dev/null +++ b/src/components/install/InstallLocationPicker.stories.ts @@ -0,0 +1,148 @@ +// eslint-disable-next-line storybook/no-renderer-packages +import type { Meta, StoryObj } from '@storybook/vue3' +import { ref } from 'vue' + +import InstallLocationPicker from './InstallLocationPicker.vue' + +const meta: Meta = { + title: 'Desktop/Components/InstallLocationPicker', + component: InstallLocationPicker, + parameters: { + layout: 'padded', + backgrounds: { + default: 'dark', + values: [ + { name: 'dark', value: '#0a0a0a' }, + { name: 'neutral-900', value: '#171717' }, + { name: 'neutral-950', value: '#0a0a0a' } + ] + } + }, + decorators: [ + () => { + // Mock electron API + ;(window as any).electronAPI = { + getSystemPaths: () => + Promise.resolve({ + defaultInstallPath: '/Users/username/ComfyUI' + }), + validateInstallPath: () => + Promise.resolve({ + isValid: true, + exists: false, + canWrite: true, + freeSpace: 100000000000, + requiredSpace: 10000000000, + isNonDefaultDrive: false + }), + validateComfyUISource: () => + Promise.resolve({ + isValid: true + }), + showDirectoryPicker: () => Promise.resolve('/Users/username/ComfyUI') + } + return { template: '' } + } + ] +} + +export default meta +type Story = StoryObj + +// Default story with accordion expanded +export const Default: Story = { + render: (args) => ({ + components: { InstallLocationPicker }, + setup() { + const installPath = ref('/Users/username/ComfyUI') + const pathError = ref('') + const migrationSourcePath = ref('/Users/username/ComfyUI-old') + const migrationItemIds = ref(['models', 'custom_nodes']) + + return { + args, + installPath, + pathError, + migrationSourcePath, + migrationItemIds + } + }, + template: ` +
+ +
+ ` + }) +} + +// Story with different background to test transparency +export const OnNeutral900: Story = { + render: (args) => ({ + components: { InstallLocationPicker }, + setup() { + const installPath = ref('/Users/username/ComfyUI') + const pathError = ref('') + const migrationSourcePath = ref('/Users/username/ComfyUI-old') + const migrationItemIds = ref(['models', 'custom_nodes']) + + return { + args, + installPath, + pathError, + migrationSourcePath, + migrationItemIds + } + }, + template: ` +
+ +
+ ` + }) +} + +// Story with debug overlay showing background colors +export const DebugBackgrounds: Story = { + render: (args) => ({ + components: { InstallLocationPicker }, + setup() { + const installPath = ref('/Users/username/ComfyUI') + const pathError = ref('') + const migrationSourcePath = ref('/Users/username/ComfyUI-old') + const migrationItemIds = ref(['models', 'custom_nodes']) + + return { + args, + installPath, + pathError, + migrationSourcePath, + migrationItemIds + } + }, + template: ` +
+
+
Parent bg: neutral-950 (#0a0a0a)
+
Accordion content: bg-transparent
+
Migration options: bg-transparent + p-4 rounded-lg
+
+ +
+ ` + }) +} diff --git a/src/components/install/InstallLocationPicker.vue b/src/components/install/InstallLocationPicker.vue index 33b32a5f98..0e22f34a96 100644 --- a/src/components/install/InstallLocationPicker.vue +++ b/src/components/install/InstallLocationPicker.vue @@ -1,103 +1,215 @@ + + diff --git a/src/components/install/MigrationPicker.stories.ts b/src/components/install/MigrationPicker.stories.ts new file mode 100644 index 0000000000..ad09e1871b --- /dev/null +++ b/src/components/install/MigrationPicker.stories.ts @@ -0,0 +1,45 @@ +// eslint-disable-next-line storybook/no-renderer-packages +import type { Meta, StoryObj } from '@storybook/vue3' +import { ref } from 'vue' + +import MigrationPicker from './MigrationPicker.vue' + +const meta: Meta = { + title: 'Desktop/Components/MigrationPicker', + component: MigrationPicker, + parameters: { + backgrounds: { + default: 'dark', + values: [ + { name: 'dark', value: '#0a0a0a' }, + { name: 'neutral-900', value: '#171717' } + ] + } + }, + decorators: [ + () => { + ;(window as any).electronAPI = { + validateComfyUISource: () => Promise.resolve({ isValid: true }), + showDirectoryPicker: () => Promise.resolve('/Users/username/ComfyUI') + } + + return { template: '' } + } + ] +} + +export default meta +type Story = StoryObj + +export const Default: Story = { + render: () => ({ + components: { MigrationPicker }, + setup() { + const sourcePath = ref('') + const migrationItemIds = ref([]) + return { sourcePath, migrationItemIds } + }, + template: + '' + }) +} diff --git a/src/components/install/MigrationPicker.vue b/src/components/install/MigrationPicker.vue index 934ffc2f32..ba542ca697 100644 --- a/src/components/install/MigrationPicker.vue +++ b/src/components/install/MigrationPicker.vue @@ -2,10 +2,6 @@
-

- {{ $t('install.migrateFromExistingInstallation') }} -

-

{{ $t('install.migrationSourcePathDescription') }}

@@ -13,7 +9,7 @@
-
+

{{ $t('install.selectItemsToMigrate') }}

diff --git a/src/components/install/MirrorsConfiguration.vue b/src/components/install/MirrorsConfiguration.vue deleted file mode 100644 index c4c3565d69..0000000000 --- a/src/components/install/MirrorsConfiguration.vue +++ /dev/null @@ -1,121 +0,0 @@ - - - diff --git a/src/components/install/mirror/MirrorItem.vue b/src/components/install/mirror/MirrorItem.vue index a4c5b563c4..3665d66c9f 100644 --- a/src/components/install/mirror/MirrorItem.vue +++ b/src/components/install/mirror/MirrorItem.vue @@ -1,10 +1,10 @@ diff --git a/src/views/InstallView.stories.ts b/src/views/InstallView.stories.ts new file mode 100644 index 0000000000..9a5c65e799 --- /dev/null +++ b/src/views/InstallView.stories.ts @@ -0,0 +1,423 @@ +// eslint-disable-next-line storybook/no-renderer-packages +import type { Meta, StoryObj } from '@storybook/vue3' +import { nextTick, provide } from 'vue' +import { createMemoryHistory, createRouter } from 'vue-router' + +import InstallView from './InstallView.vue' + +// Create a mock router for stories +const createMockRouter = () => + createRouter({ + history: createMemoryHistory(), + routes: [ + { path: '/', component: { template: '
Home
' } }, + { + path: '/server-start', + component: { template: '
Server Start
' } + }, + { + path: '/manual-configuration', + component: { template: '
Manual Configuration
' } + } + ] + }) + +const meta: Meta = { + title: 'Desktop/Views/InstallView', + component: InstallView, + parameters: { + layout: 'fullscreen', + backgrounds: { + default: 'dark', + values: [ + { name: 'dark', value: '#0a0a0a' }, + { name: 'neutral-900', value: '#171717' }, + { name: 'neutral-950', value: '#0a0a0a' } + ] + } + }, + decorators: [ + (story) => { + // Create router for this story + const router = createMockRouter() + + // Mock electron API + ;(window as any).electronAPI = { + getPlatform: () => 'darwin', + Config: { + getDetectedGpu: () => Promise.resolve('mps') + }, + Events: { + trackEvent: (eventName: string, data?: any) => { + console.log('Track event:', eventName, data) + } + }, + installComfyUI: (options: any) => { + console.log('Install ComfyUI with options:', options) + }, + changeTheme: (theme: any) => { + console.log('Change theme:', theme) + }, + getSystemPaths: () => + Promise.resolve({ + defaultInstallPath: '/Users/username/ComfyUI' + }), + validateInstallPath: () => + Promise.resolve({ + isValid: true, + exists: false, + canWrite: true, + freeSpace: 100000000000, + requiredSpace: 10000000000, + isNonDefaultDrive: false + }), + validateComfyUISource: () => + Promise.resolve({ + isValid: true + }), + showDirectoryPicker: () => Promise.resolve('/Users/username/ComfyUI') + } + + return { + setup() { + // Provide router for all child components + provide('router', router) + return { + story + } + }, + template: '
' + } + } + ] +} + +export default meta +type Story = StoryObj + +// Default story - start at GPU selection +export const GpuSelection: Story = { + render: () => ({ + components: { InstallView }, + setup() { + // The component will automatically start at step 1 + return {} + }, + template: '' + }) +} + +// Story showing the install location step +export const InstallLocation: Story = { + render: () => ({ + components: { InstallView }, + setup() { + return {} + }, + async mounted() { + // Wait for component to be fully mounted + await nextTick() + + // Select Apple Metal option to enable navigation + const hardwareOptions = this.$el.querySelectorAll( + '.p-selectbutton-option' + ) + if (hardwareOptions.length > 0) { + hardwareOptions[0].click() // Click Apple Metal (first option) + } + + await nextTick() + + // Click Next to go to step 2 + const buttons = Array.from( + this.$el.querySelectorAll('button') + ) as HTMLButtonElement[] + const nextBtn = buttons.find((btn) => btn.textContent?.includes('Next')) + if (nextBtn) { + nextBtn.click() + } + }, + template: '' + }) +} + +// Story showing the migration step (currently empty) +export const MigrationStep: Story = { + render: () => ({ + components: { InstallView }, + setup() { + return {} + }, + async mounted() { + // Wait for component to be fully mounted + await nextTick() + + // Select Apple Metal option to enable navigation + const hardwareOptions = this.$el.querySelectorAll( + '.p-selectbutton-option' + ) + if (hardwareOptions.length > 0) { + hardwareOptions[0].click() // Click Apple Metal (first option) + } + + await nextTick() + + // Click Next to go to step 2 + const buttons1 = Array.from( + this.$el.querySelectorAll('button') + ) as HTMLButtonElement[] + const nextBtn1 = buttons1.find((btn) => btn.textContent?.includes('Next')) + if (nextBtn1) { + nextBtn1.click() + } + + await nextTick() + + // Click Next again to go to step 3 + const buttons2 = Array.from( + this.$el.querySelectorAll('button') + ) as HTMLButtonElement[] + const nextBtn2 = buttons2.find((btn) => btn.textContent?.includes('Next')) + if (nextBtn2) { + nextBtn2.click() + } + }, + template: '' + }) +} + +// Story showing the desktop settings configuration +export const DesktopSettings: Story = { + render: () => ({ + components: { InstallView }, + setup() { + return {} + }, + async mounted() { + // Wait for component to be fully mounted + await nextTick() + + // Select Apple Metal option to enable navigation + const hardwareOptions = this.$el.querySelectorAll( + '.p-selectbutton-option' + ) + if (hardwareOptions.length > 0) { + hardwareOptions[0].click() // Click Apple Metal (first option) + } + + await nextTick() + + // Click Next to go to step 2 + const buttons1 = Array.from( + this.$el.querySelectorAll('button') + ) as HTMLButtonElement[] + const nextBtn1 = buttons1.find((btn) => btn.textContent?.includes('Next')) + if (nextBtn1) { + nextBtn1.click() + } + + await nextTick() + + // Click Next again to go to step 3 + const buttons2 = Array.from( + this.$el.querySelectorAll('button') + ) as HTMLButtonElement[] + const nextBtn2 = buttons2.find((btn) => btn.textContent?.includes('Next')) + if (nextBtn2) { + nextBtn2.click() + } + + await nextTick() + + // Click Next again to go to step 4 + const buttons3 = Array.from( + this.$el.querySelectorAll('button') + ) as HTMLButtonElement[] + const nextBtn3 = buttons3.find((btn) => btn.textContent?.includes('Next')) + if (nextBtn3) { + nextBtn3.click() + } + }, + template: '' + }) +} + +// Story with Windows platform (no Apple Metal option) +export const WindowsPlatform: Story = { + render: () => { + // Override the platform to Windows + ;(window as any).electronAPI.getPlatform = () => 'win32' + ;(window as any).electronAPI.Config.getDetectedGpu = () => + Promise.resolve('nvidia') + + return { + components: { InstallView }, + setup() { + return {} + }, + template: '' + } + } +} + +// Story with macOS platform (Apple Metal option) +export const MacOSPlatform: Story = { + name: 'macOS Platform', + render: () => { + // Override the platform to macOS + ;(window as any).electronAPI.getPlatform = () => 'darwin' + ;(window as any).electronAPI.Config.getDetectedGpu = () => + Promise.resolve('mps') + + return { + components: { InstallView }, + setup() { + return {} + }, + template: '' + } + } +} + +// Story with CPU selected +export const CpuSelected: Story = { + render: () => ({ + components: { InstallView }, + setup() { + return {} + }, + async mounted() { + // Wait for component to be fully mounted + await nextTick() + + // Find and click the CPU hardware option + const hardwareButtons = this.$el.querySelectorAll('.hardware-option') + // CPU is the button with "CPU" text + for (const button of hardwareButtons) { + if (button.textContent?.includes('CPU')) { + button.click() + break + } + } + }, + template: '' + }) +} + +// Story with manual install selected +export const ManualInstall: Story = { + render: () => ({ + components: { InstallView }, + setup() { + return {} + }, + async mounted() { + // Wait for component to be fully mounted + await nextTick() + + // Find and click the Manual Install hardware option + const hardwareButtons = this.$el.querySelectorAll('.hardware-option') + // Manual Install is the button with "Manual Install" text + for (const button of hardwareButtons) { + if (button.textContent?.includes('Manual Install')) { + button.click() + break + } + } + }, + template: '' + }) +} + +// Story with error state (invalid install path) +export const ErrorState: Story = { + render: () => { + // Override validation to return an error + ;(window as any).electronAPI.validateInstallPath = () => + Promise.resolve({ + isValid: false, + exists: false, + canWrite: false, + freeSpace: 100000000000, + requiredSpace: 10000000000, + isNonDefaultDrive: false, + error: 'Story mock: Example error state' + }) + + return { + components: { InstallView }, + setup() { + return {} + }, + async mounted() { + // Wait for component to be fully mounted + await nextTick() + + // Select Apple Metal option to enable navigation + const hardwareOptions = this.$el.querySelectorAll( + '.p-selectbutton-option' + ) + if (hardwareOptions.length > 0) { + hardwareOptions[0].click() // Click Apple Metal (first option) + } + + await nextTick() + + // Click Next to go to step 2 where error will be shown + const buttons = Array.from( + this.$el.querySelectorAll('button') + ) as HTMLButtonElement[] + const nextBtn = buttons.find((btn) => btn.textContent?.includes('Next')) + if (nextBtn) { + nextBtn.click() + } + }, + template: '' + } + } +} + +// Story with warning state (non-default drive) +export const WarningState: Story = { + render: () => { + // Override validation to return a warning about non-default drive + ;(window as any).electronAPI.validateInstallPath = () => + Promise.resolve({ + isValid: true, + exists: false, + canWrite: true, + freeSpace: 500_000_000_000, + requiredSpace: 10_000_000_000, + isNonDefaultDrive: true + }) + + return { + components: { InstallView }, + setup() { + return {} + }, + async mounted() { + // Wait for component to be fully mounted + await nextTick() + + // Select Apple Metal option to enable navigation + const hardwareOptions = this.$el.querySelectorAll('.hardware-option') + if (hardwareOptions.length > 0) { + hardwareOptions[0].click() // Click Apple Metal (first option) + } + + await nextTick() + + // Click Next to go to step 2 where warning will be shown + const buttons = Array.from( + this.$el.querySelectorAll('button') + ) as HTMLButtonElement[] + const nextBtn = buttons.find((btn) => btn.textContent?.includes('Next')) + if (nextBtn) { + nextBtn.click() + } + }, + template: '' + } + } +} diff --git a/src/views/InstallView.vue b/src/views/InstallView.vue index ed3da0a8ac..6b170937cb 100644 --- a/src/views/InstallView.vue +++ b/src/views/InstallView.vue @@ -1,111 +1,54 @@ @@ -114,9 +57,6 @@ import type { InstallOptions, TorchDeviceType } from '@comfyorg/comfyui-electron-types' -import Button from 'primevue/button' -import Step from 'primevue/step' -import StepList from 'primevue/steplist' import StepPanel from 'primevue/steppanel' import StepPanels from 'primevue/steppanels' import Stepper from 'primevue/stepper' @@ -125,9 +65,8 @@ import { useRouter } from 'vue-router' import DesktopSettingsConfiguration from '@/components/install/DesktopSettingsConfiguration.vue' import GpuPicker from '@/components/install/GpuPicker.vue' +import InstallFooter from '@/components/install/InstallFooter.vue' import InstallLocationPicker from '@/components/install/InstallLocationPicker.vue' -import MigrationPicker from '@/components/install/MigrationPicker.vue' -import MirrorsConfiguration from '@/components/install/MirrorsConfiguration.vue' import { electronAPI } from '@/utils/envUtil' import BaseViewTemplate from '@/views/templates/BaseViewTemplate.vue' @@ -145,6 +84,9 @@ const pythonMirror = ref('') const pypiMirror = ref('') const torchMirror = ref('') +/** Current step in the stepper */ +const currentStep = ref('1') + /** Forces each install step to be visited at least once. */ const highestStep = ref(0) @@ -164,6 +106,40 @@ const setHighestStep = (value: string | number) => { const hasError = computed(() => pathError.value !== '') const noGpu = computed(() => typeof device.value !== 'string') +// Computed property to determine if user can proceed to next step +const regex = /^Insufficient space - minimum free space: \d+ GB$/ + +const canProceed = computed(() => { + switch (currentStep.value) { + case '1': + return typeof device.value === 'string' + case '2': + return pathError.value === '' || regex.test(pathError.value) + case '3': + return !hasError.value + default: + return false + } +}) + +// Navigation methods +const goToNextStep = () => { + const nextStep = (parseInt(currentStep.value) + 1).toString() + currentStep.value = nextStep + setHighestStep(nextStep) + electronAPI().Events.trackEvent('install_stepper_change', { + step: nextStep + }) +} + +const goToPreviousStep = () => { + const prevStep = (parseInt(currentStep.value) - 1).toString() + currentStep.value = prevStep + electronAPI().Events.trackEvent('install_stepper_change', { + step: prevStep + }) +} + const electron = electronAPI() const router = useRouter() const install = async () => { @@ -195,7 +171,7 @@ onMounted(async () => { } electronAPI().Events.trackEvent('install_stepper_change', { - step: '0', + step: currentStep.value, gpu: detectedGpu }) }) @@ -205,6 +181,30 @@ onMounted(async () => { @reference '../assets/css/style.css'; :deep(.p-steppanel) { + @apply mt-8 flex justify-center bg-transparent; +} + +/* Remove default padding/margin from StepPanels to make scrollbar flush */ +:deep(.p-steppanels) { + @apply p-0 m-0; +} + +/* Ensure StepPanel content container has no top/bottom padding */ +:deep(.p-steppanel-content) { + @apply p-0; +} + +/* Custom overlay scrollbar for WebKit browsers (Electron, Chrome) */ +:deep(.p-steppanels::-webkit-scrollbar) { + @apply w-4; +} + +:deep(.p-steppanels::-webkit-scrollbar-track) { @apply bg-transparent; } + +:deep(.p-steppanels::-webkit-scrollbar-thumb) { + @apply bg-white/20 rounded-lg border-[4px] border-transparent; + background-clip: content-box; +} diff --git a/src/views/ServerStartView.vue b/src/views/ServerStartView.vue index c74e3a967a..dd6409991e 100644 --- a/src/views/ServerStartView.vue +++ b/src/views/ServerStartView.vue @@ -1,76 +1,205 @@ + + diff --git a/src/views/WelcomeView.vue b/src/views/WelcomeView.vue index 597bc5a6b8..ad653e6a8e 100644 --- a/src/views/WelcomeView.vue +++ b/src/views/WelcomeView.vue @@ -1,21 +1,27 @@ @@ -31,49 +37,3 @@ const navigateTo = async (path: string) => { await router.push(path) } - - From 0919856a0537b3162502aba6f44087b85a928500 Mon Sep 17 00:00:00 2001 From: Simula_r <18093452+simula-r@users.noreply.github.com> Date: Wed, 24 Sep 2025 13:02:47 -0700 Subject: [PATCH 05/52] Feat/vue nodes preview (#5747) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Create a LGraphNodePreview.vue component to use Vue Nodes for preview when hovering over search results / sidebar tree list. ## Screenshots (if applicable) image image (1) ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-5747-Feat-vue-nodes-preview-2786d73d3650817dbf9af458bd5dda8c) by [Unito](https://www.unito.io) --------- Co-authored-by: JakeSchroeder Co-authored-by: AustinMroz --- src/components/node/NodePreview.vue | 8 +- .../vueNodes/components/LGraphNodePreview.vue | 119 ++++++++++++++++++ .../content/manager/packCard/PackCard.test.ts | 3 +- tests-ui/tests/store/releaseStore.test.ts | 3 +- 4 files changed, 129 insertions(+), 4 deletions(-) create mode 100644 src/renderer/extensions/vueNodes/components/LGraphNodePreview.vue diff --git a/src/components/node/NodePreview.vue b/src/components/node/NodePreview.vue index f55c934368..4fffeeaf89 100644 --- a/src/components/node/NodePreview.vue +++ b/src/components/node/NodePreview.vue @@ -1,9 +1,9 @@ -