diff --git a/browser_tests/fixtures/components/SettingDialog.ts b/browser_tests/fixtures/components/SettingDialog.ts index e9040a3a9..db98e0373 100644 --- a/browser_tests/fixtures/components/SettingDialog.ts +++ b/browser_tests/fixtures/components/SettingDialog.ts @@ -9,12 +9,12 @@ export class SettingDialog { ) {} get root() { - return this.page.locator('div.settings-container') + return this.page.locator('[data-testid="settings-dialog"]') } async open() { await this.comfyPage.executeCommand('Comfy.ShowSettingsDialog') - await this.page.waitForSelector('div.settings-container') + await this.page.waitForSelector('[data-testid="settings-dialog"]') } /** @@ -23,9 +23,7 @@ export class SettingDialog { * @param value - The value to set */ async setStringSetting(id: string, value: string) { - const settingInputDiv = this.page.locator( - `div.settings-container div[id="${id}"]` - ) + const settingInputDiv = this.root.locator(`div[id="${id}"]`) await settingInputDiv.locator('input').fill(value) } @@ -34,15 +32,15 @@ export class SettingDialog { * @param id - The id of the setting */ async toggleBooleanSetting(id: string) { - const settingInputDiv = this.page.locator( - `div.settings-container div[id="${id}"]` - ) + const settingInputDiv = this.root.locator(`div[id="${id}"]`) await settingInputDiv.locator('input').click() } async goToAboutPanel() { - const aboutButton = this.page.locator('li[aria-label="About"]') + const aboutButton = this.root.locator('nav [role="button"]', { + hasText: 'About' + }) await aboutButton.click() - await this.page.waitForSelector('div.about-container') + await this.page.waitForSelector('.about-container') } } diff --git a/browser_tests/tests/dialog.spec.ts b/browser_tests/tests/dialog.spec.ts index 081f47275..aeb959bf9 100644 --- a/browser_tests/tests/dialog.spec.ts +++ b/browser_tests/tests/dialog.spec.ts @@ -232,9 +232,13 @@ test.describe('Missing models warning', () => { test.describe('Settings', () => { test('@mobile Should be visible on mobile', async ({ comfyPage }) => { await comfyPage.page.keyboard.press('Control+,') - const settingsContent = comfyPage.page.locator('.settings-content') - await expect(settingsContent).toBeVisible() - const isUsableHeight = await settingsContent.evaluate( + const settingsDialog = comfyPage.page.locator( + '[data-testid="settings-dialog"]' + ) + await expect(settingsDialog).toBeVisible() + const contentArea = settingsDialog.locator('main') + await expect(contentArea).toBeVisible() + const isUsableHeight = await contentArea.evaluate( (el) => el.clientHeight > 30 ) expect(isUsableHeight).toBeTruthy() @@ -244,7 +248,9 @@ test.describe('Settings', () => { await comfyPage.page.keyboard.down('ControlOrMeta') await comfyPage.page.keyboard.press(',') await comfyPage.page.keyboard.up('ControlOrMeta') - const settingsLocator = comfyPage.page.locator('.settings-container') + const settingsLocator = comfyPage.page.locator( + '[data-testid="settings-dialog"]' + ) await expect(settingsLocator).toBeVisible() await comfyPage.page.keyboard.press('Escape') await expect(settingsLocator).not.toBeVisible() @@ -261,10 +267,15 @@ test.describe('Settings', () => { test('Should persist keybinding setting', async ({ comfyPage }) => { // Open the settings dialog await comfyPage.page.keyboard.press('Control+,') - await comfyPage.page.waitForSelector('.settings-container') + await comfyPage.page.waitForSelector('[data-testid="settings-dialog"]') // Open the keybinding tab - await comfyPage.page.getByLabel('Keybinding').click() + const settingsDialog = comfyPage.page.locator( + '[data-testid="settings-dialog"]' + ) + await settingsDialog + .locator('nav [role="button"]', { hasText: 'Keybinding' }) + .click() await comfyPage.page.waitForSelector( '[placeholder="Search Keybindings..."]' ) diff --git a/browser_tests/tests/subgraph.spec.ts b/browser_tests/tests/subgraph.spec.ts index 635915621..9e9b96795 100644 --- a/browser_tests/tests/subgraph.spec.ts +++ b/browser_tests/tests/subgraph.spec.ts @@ -794,7 +794,7 @@ test.describe('Subgraph Operations', () => { // Open settings dialog using hotkey await comfyPage.page.keyboard.press('Control+,') - await comfyPage.page.waitForSelector('.settings-container', { + await comfyPage.page.waitForSelector('[data-testid="settings-dialog"]', { state: 'visible' }) @@ -804,7 +804,7 @@ test.describe('Subgraph Operations', () => { // Dialog should be closed await expect( - comfyPage.page.locator('.settings-container') + comfyPage.page.locator('[data-testid="settings-dialog"]') ).not.toBeVisible() // Should still be in subgraph diff --git a/browser_tests/tests/useSettingSearch.spec.ts b/browser_tests/tests/useSettingSearch.spec.ts index f022bb4bd..2eafa9fdf 100644 --- a/browser_tests/tests/useSettingSearch.spec.ts +++ b/browser_tests/tests/useSettingSearch.spec.ts @@ -41,16 +41,15 @@ test.describe('Settings Search functionality', () => { }) test('can open settings dialog and use search box', async ({ comfyPage }) => { - // Open settings dialog await comfyPage.page.keyboard.press('Control+,') - const settingsDialog = comfyPage.page.locator('.settings-container') + const settingsDialog = comfyPage.page.locator( + '[data-testid="settings-dialog"]' + ) await expect(settingsDialog).toBeVisible() - // Find the search box - const searchBox = comfyPage.page.locator('.settings-search-box input') + const searchBox = settingsDialog.locator('input[placeholder*="Search"]') await expect(searchBox).toBeVisible() - // Verify search box has the correct placeholder await expect(searchBox).toHaveAttribute( 'placeholder', expect.stringContaining('Search') @@ -58,226 +57,198 @@ test.describe('Settings Search functionality', () => { }) test('search box is functional and accepts input', async ({ comfyPage }) => { - // Open settings dialog await comfyPage.page.keyboard.press('Control+,') - const settingsDialog = comfyPage.page.locator('.settings-container') + const settingsDialog = comfyPage.page.locator( + '[data-testid="settings-dialog"]' + ) await expect(settingsDialog).toBeVisible() - // Find and interact with the search box - const searchBox = comfyPage.page.locator('.settings-search-box input') + const searchBox = settingsDialog.locator('input[placeholder*="Search"]') await searchBox.fill('Comfy') - // Verify the input was accepted await expect(searchBox).toHaveValue('Comfy') }) test('search box clears properly', async ({ comfyPage }) => { - // Open settings dialog await comfyPage.page.keyboard.press('Control+,') - const settingsDialog = comfyPage.page.locator('.settings-container') + const settingsDialog = comfyPage.page.locator( + '[data-testid="settings-dialog"]' + ) await expect(settingsDialog).toBeVisible() - // Find and interact with the search box - const searchBox = comfyPage.page.locator('.settings-search-box input') + const searchBox = settingsDialog.locator('input[placeholder*="Search"]') await searchBox.fill('test') await expect(searchBox).toHaveValue('test') - // Clear the search box await searchBox.clear() await expect(searchBox).toHaveValue('') }) test('settings categories are visible in sidebar', async ({ comfyPage }) => { - // Open settings dialog await comfyPage.page.keyboard.press('Control+,') - const settingsDialog = comfyPage.page.locator('.settings-container') + const settingsDialog = comfyPage.page.locator( + '[data-testid="settings-dialog"]' + ) await expect(settingsDialog).toBeVisible() - // Check that the sidebar has categories - const categories = comfyPage.page.locator( - '.settings-sidebar .p-listbox-option' - ) + const categories = settingsDialog.locator('nav [role="button"]') expect(await categories.count()).toBeGreaterThan(0) - // Check that at least one category is visible await expect(categories.first()).toBeVisible() }) test('can select different categories in sidebar', async ({ comfyPage }) => { - // Open settings dialog await comfyPage.page.keyboard.press('Control+,') - const settingsDialog = comfyPage.page.locator('.settings-container') + const settingsDialog = comfyPage.page.locator( + '[data-testid="settings-dialog"]' + ) await expect(settingsDialog).toBeVisible() - // Get categories and click on different ones - const categories = comfyPage.page.locator( - '.settings-sidebar .p-listbox-option' - ) + const categories = settingsDialog.locator('nav [role="button"]') const categoryCount = await categories.count() if (categoryCount > 1) { - // Click on the second category await categories.nth(1).click() - // Verify the category is selected - await expect(categories.nth(1)).toHaveClass(/p-listbox-option-selected/) + await expect(categories.nth(1)).toHaveClass( + /bg-interface-menu-component-surface-selected/ + ) } }) test('settings content area is visible', async ({ comfyPage }) => { - // Open settings dialog await comfyPage.page.keyboard.press('Control+,') - const settingsDialog = comfyPage.page.locator('.settings-container') + const settingsDialog = comfyPage.page.locator( + '[data-testid="settings-dialog"]' + ) await expect(settingsDialog).toBeVisible() - // Check that the content area is visible - const contentArea = comfyPage.page.locator('.settings-content') + const contentArea = settingsDialog.locator('main') await expect(contentArea).toBeVisible() - - // Check that tab panels are visible - const tabPanels = comfyPage.page.locator('.settings-tab-panels') - await expect(tabPanels).toBeVisible() }) test('search functionality affects UI state', async ({ comfyPage }) => { - // Open settings dialog await comfyPage.page.keyboard.press('Control+,') - const settingsDialog = comfyPage.page.locator('.settings-container') + const settingsDialog = comfyPage.page.locator( + '[data-testid="settings-dialog"]' + ) await expect(settingsDialog).toBeVisible() - // Find the search box - const searchBox = comfyPage.page.locator('.settings-search-box input') - - // Type in search box + const searchBox = settingsDialog.locator('input[placeholder*="Search"]') await searchBox.fill('graph') - // Verify that the search input is handled await expect(searchBox).toHaveValue('graph') }) test('settings dialog can be closed', async ({ comfyPage }) => { - // Open settings dialog await comfyPage.page.keyboard.press('Control+,') - const settingsDialog = comfyPage.page.locator('.settings-container') + const settingsDialog = comfyPage.page.locator( + '[data-testid="settings-dialog"]' + ) await expect(settingsDialog).toBeVisible() - // Close with escape key await comfyPage.page.keyboard.press('Escape') - // Verify dialog is closed await expect(settingsDialog).not.toBeVisible() }) test('search box has proper debouncing behavior', async ({ comfyPage }) => { - // Open settings dialog await comfyPage.page.keyboard.press('Control+,') - const settingsDialog = comfyPage.page.locator('.settings-container') + const settingsDialog = comfyPage.page.locator( + '[data-testid="settings-dialog"]' + ) await expect(settingsDialog).toBeVisible() - // Type rapidly in search box - const searchBox = comfyPage.page.locator('.settings-search-box input') + const searchBox = settingsDialog.locator('input[placeholder*="Search"]') await searchBox.fill('a') await searchBox.fill('ab') await searchBox.fill('abc') await searchBox.fill('abcd') - // Verify final value await expect(searchBox).toHaveValue('abcd') }) test('search excludes hidden settings from results', async ({ comfyPage }) => { - // Open settings dialog await comfyPage.page.keyboard.press('Control+,') - const settingsDialog = comfyPage.page.locator('.settings-container') + const settingsDialog = comfyPage.page.locator( + '[data-testid="settings-dialog"]' + ) await expect(settingsDialog).toBeVisible() - // Search for our test settings - const searchBox = comfyPage.page.locator('.settings-search-box input') + const searchBox = settingsDialog.locator('input[placeholder*="Search"]') await searchBox.fill('Test') - // Get all settings content - const settingsContent = comfyPage.page.locator('.settings-tab-panels') + const contentArea = settingsDialog.locator('main') - // Should show visible setting but not hidden setting - await expect(settingsContent).toContainText('Test Visible Setting') - await expect(settingsContent).not.toContainText('Test Hidden Setting') + await expect(contentArea).toContainText('Test Visible Setting') + await expect(contentArea).not.toContainText('Test Hidden Setting') }) test('search excludes deprecated settings from results', async ({ comfyPage }) => { - // Open settings dialog await comfyPage.page.keyboard.press('Control+,') - const settingsDialog = comfyPage.page.locator('.settings-container') + const settingsDialog = comfyPage.page.locator( + '[data-testid="settings-dialog"]' + ) await expect(settingsDialog).toBeVisible() - // Search for our test settings - const searchBox = comfyPage.page.locator('.settings-search-box input') + const searchBox = settingsDialog.locator('input[placeholder*="Search"]') await searchBox.fill('Test') - // Get all settings content - const settingsContent = comfyPage.page.locator('.settings-tab-panels') + const contentArea = settingsDialog.locator('main') - // Should show visible setting but not deprecated setting - await expect(settingsContent).toContainText('Test Visible Setting') - await expect(settingsContent).not.toContainText('Test Deprecated Setting') + await expect(contentArea).toContainText('Test Visible Setting') + await expect(contentArea).not.toContainText('Test Deprecated Setting') }) test('search shows visible settings but excludes hidden and deprecated', async ({ comfyPage }) => { - // Open settings dialog await comfyPage.page.keyboard.press('Control+,') - const settingsDialog = comfyPage.page.locator('.settings-container') + const settingsDialog = comfyPage.page.locator( + '[data-testid="settings-dialog"]' + ) await expect(settingsDialog).toBeVisible() - // Search for our test settings - const searchBox = comfyPage.page.locator('.settings-search-box input') + const searchBox = settingsDialog.locator('input[placeholder*="Search"]') await searchBox.fill('Test') - // Get all settings content - const settingsContent = comfyPage.page.locator('.settings-tab-panels') + const contentArea = settingsDialog.locator('main') - // Should only show the visible setting - await expect(settingsContent).toContainText('Test Visible Setting') + await expect(contentArea).toContainText('Test Visible Setting') - // Should not show hidden or deprecated settings - await expect(settingsContent).not.toContainText('Test Hidden Setting') - await expect(settingsContent).not.toContainText('Test Deprecated Setting') + await expect(contentArea).not.toContainText('Test Hidden Setting') + await expect(contentArea).not.toContainText('Test Deprecated Setting') }) test('search by setting name excludes hidden and deprecated', async ({ comfyPage }) => { - // Open settings dialog await comfyPage.page.keyboard.press('Control+,') - const settingsDialog = comfyPage.page.locator('.settings-container') + const settingsDialog = comfyPage.page.locator( + '[data-testid="settings-dialog"]' + ) await expect(settingsDialog).toBeVisible() - const searchBox = comfyPage.page.locator('.settings-search-box input') - const settingsContent = comfyPage.page.locator('.settings-tab-panels') + const searchBox = settingsDialog.locator('input[placeholder*="Search"]') + const contentArea = settingsDialog.locator('main') - // Search specifically for hidden setting by name await searchBox.clear() await searchBox.fill('Hidden') - // Should not show the hidden setting even when searching by name - await expect(settingsContent).not.toContainText('Test Hidden Setting') + await expect(contentArea).not.toContainText('Test Hidden Setting') - // Search specifically for deprecated setting by name await searchBox.clear() await searchBox.fill('Deprecated') - // Should not show the deprecated setting even when searching by name - await expect(settingsContent).not.toContainText('Test Deprecated Setting') + await expect(contentArea).not.toContainText('Test Deprecated Setting') - // Search for visible setting by name - should work await searchBox.clear() await searchBox.fill('Visible') - // Should show the visible setting - await expect(settingsContent).toContainText('Test Visible Setting') + await expect(contentArea).toContainText('Test Visible Setting') }) }) diff --git a/src/components/bottomPanel/BottomPanel.vue b/src/components/bottomPanel/BottomPanel.vue index aa6800677..08f23c509 100644 --- a/src/components/bottomPanel/BottomPanel.vue +++ b/src/components/bottomPanel/BottomPanel.vue @@ -89,12 +89,12 @@ import { useI18n } from 'vue-i18n' import ExtensionSlot from '@/components/common/ExtensionSlot.vue' import Button from '@/components/ui/button/Button.vue' -import { useDialogService } from '@/services/dialogService' +import { useSettingsDialog } from '@/platform/settings/composables/useSettingsDialog' import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore' import type { BottomPanelExtension } from '@/types/extensionTypes' const bottomPanelStore = useBottomPanelStore() -const dialogService = useDialogService() +const settingsDialog = useSettingsDialog() const { t } = useI18n() const isShortcutsTabActive = computed(() => { @@ -115,7 +115,7 @@ const getTabDisplayTitle = (tab: BottomPanelExtension): string => { } const openKeybindingSettings = async () => { - dialogService.showSettingsDialog('keybinding') + settingsDialog.show('keybinding') } const closeBottomPanel = () => { diff --git a/src/components/dialog/GlobalDialog.vue b/src/components/dialog/GlobalDialog.vue index 2a1f0ef3d..2074132b5 100644 --- a/src/components/dialog/GlobalDialog.vue +++ b/src/components/dialog/GlobalDialog.vue @@ -4,12 +4,7 @@ v-for="item in dialogStore.dialogStack" :key="item.key" v-model:visible="item.visible" - :class="[ - 'global-dialog', - item.key === 'global-settings' && teamWorkspacesEnabled - ? 'settings-dialog-workspace' - : '' - ]" + class="global-dialog" v-bind="item.dialogComponentProps" :pt="item.dialogComponentProps.pt" :aria-labelledby="item.key" @@ -43,15 +38,7 @@ @@ -69,16 +56,6 @@ const dialogStore = useDialogStore() @apply pt-0; } -/* Workspace mode: wider settings dialog */ -.settings-dialog-workspace { - width: 100%; - max-width: 1440px; -} - -.settings-dialog-workspace .p-dialog-content { - width: 100%; -} - .manager-dialog { height: 80vh; max-width: 1724px; diff --git a/src/components/dialog/content/TopUpCreditsDialogContent.vue b/src/components/dialog/content/TopUpCreditsDialogContent.vue index 51c29aaf5..110c3012d 100644 --- a/src/components/dialog/content/TopUpCreditsDialogContent.vue +++ b/src/components/dialog/content/TopUpCreditsDialogContent.vue @@ -161,7 +161,7 @@ import { useExternalLink } from '@/composables/useExternalLink' import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription' import { useTelemetry } from '@/platform/telemetry' import { clearTopupTracking } from '@/platform/telemetry/topupTracker' -import { useDialogService } from '@/services/dialogService' +import { useSettingsDialog } from '@/platform/settings/composables/useSettingsDialog' import { useDialogStore } from '@/stores/dialogStore' import { cn } from '@/utils/tailwindUtil' @@ -172,7 +172,7 @@ const { isInsufficientCredits = false } = defineProps<{ const { t } = useI18n() const authActions = useFirebaseAuthActions() const dialogStore = useDialogStore() -const dialogService = useDialogService() +const settingsDialog = useSettingsDialog() const telemetry = useTelemetry() const toast = useToast() const { buildDocsUrl, docsPaths } = useExternalLink() @@ -256,9 +256,7 @@ async function handleBuy() { // Close top-up dialog (keep tracking) and open credits panel to show updated balance handleClose(false) - dialogService.showSettingsDialog( - isSubscriptionEnabled() ? 'subscription' : 'credits' - ) + settingsDialog.show(isSubscriptionEnabled() ? 'subscription' : 'credits') } catch (error) { console.error('Purchase failed:', error) diff --git a/src/components/dialog/content/setting/AboutPanel.vue b/src/components/dialog/content/setting/AboutPanel.vue index 33ada1ce3..0fdddad17 100644 --- a/src/components/dialog/content/setting/AboutPanel.vue +++ b/src/components/dialog/content/setting/AboutPanel.vue @@ -1,5 +1,5 @@ diff --git a/src/components/dialog/content/setting/KeybindingPanel.vue b/src/components/dialog/content/setting/KeybindingPanel.vue index 03ca3df47..79e81ef3d 100644 --- a/src/components/dialog/content/setting/KeybindingPanel.vue +++ b/src/components/dialog/content/setting/KeybindingPanel.vue @@ -1,11 +1,9 @@ diff --git a/src/components/dialog/content/setting/UserPanel.vue b/src/components/dialog/content/setting/UserPanel.vue index 60bc6dba3..7476b3c09 100644 --- a/src/components/dialog/content/setting/UserPanel.vue +++ b/src/components/dialog/content/setting/UserPanel.vue @@ -1,5 +1,5 @@ diff --git a/src/components/dialog/content/setting/WorkspacePanelContent.vue b/src/components/dialog/content/setting/WorkspacePanelContent.vue index 9366a573f..4ea72f0fb 100644 --- a/src/components/dialog/content/setting/WorkspacePanelContent.vue +++ b/src/components/dialog/content/setting/WorkspacePanelContent.vue @@ -9,70 +9,64 @@ {{ workspaceName }} - -
- - {{ $t('workspacePanel.tabs.planCredits') }} - +
+ + {{ $t('workspacePanel.tabs.planCredits') }} + - -
+ +
- - - - - -
+
+ +
diff --git a/src/components/dialog/content/setting/WorkspaceSidebarItem.vue b/src/components/dialog/content/setting/WorkspaceSidebarItem.vue deleted file mode 100644 index cab92c7a8..000000000 --- a/src/components/dialog/content/setting/WorkspaceSidebarItem.vue +++ /dev/null @@ -1,19 +0,0 @@ - - - diff --git a/src/components/dialog/header/SettingDialogHeader.vue b/src/components/dialog/header/SettingDialogHeader.vue deleted file mode 100644 index 959cfa14d..000000000 --- a/src/components/dialog/header/SettingDialogHeader.vue +++ /dev/null @@ -1,29 +0,0 @@ - - - - diff --git a/src/components/rightSidePanel/settings/TabGlobalSettings.vue b/src/components/rightSidePanel/settings/TabGlobalSettings.vue index d283c2e41..f94cf23ea 100644 --- a/src/components/rightSidePanel/settings/TabGlobalSettings.vue +++ b/src/components/rightSidePanel/settings/TabGlobalSettings.vue @@ -11,7 +11,7 @@ import type { LinkRenderType } from '@/lib/litegraph/src/types/globalEnums' import { LinkMarkerShape } from '@/lib/litegraph/src/types/globalEnums' import { useSettingStore } from '@/platform/settings/settingStore' import { WidgetInputBaseClass } from '@/renderer/extensions/vueNodes/widgets/components/layout' -import { useDialogService } from '@/services/dialogService' +import { useSettingsDialog } from '@/platform/settings/composables/useSettingsDialog' import { cn } from '@/utils/tailwindUtil' import PropertiesAccordionItem from '../layout/PropertiesAccordionItem.vue' @@ -20,7 +20,7 @@ import LayoutField from './LayoutField.vue' const { t } = useI18n() const settingStore = useSettingStore() -const dialogService = useDialogService() +const settingsDialog = useSettingsDialog() // NODES settings const showAdvancedParameters = ref(false) // Placeholder for future implementation @@ -88,7 +88,7 @@ function updateGridSpacingFromInput(value: number | null | undefined) { } function openFullSettings() { - dialogService.showSettingsDialog() + settingsDialog.show() } diff --git a/src/components/sidebar/ComfyMenuButton.vue b/src/components/sidebar/ComfyMenuButton.vue index 84b2bbfc6..486fea23c 100644 --- a/src/components/sidebar/ComfyMenuButton.vue +++ b/src/components/sidebar/ComfyMenuButton.vue @@ -108,15 +108,13 @@ import ToggleSwitch from 'primevue/toggleswitch' import { computed, nextTick, ref } from 'vue' import { useI18n } from 'vue-i18n' -import SettingDialogHeader from '@/components/dialog/header/SettingDialogHeader.vue' import ComfyLogo from '@/components/icons/ComfyLogo.vue' import { useWorkflowTemplateSelectorDialog } from '@/composables/useWorkflowTemplateSelectorDialog' -import SettingDialogContent from '@/platform/settings/components/SettingDialogContent.vue' import { useSettingStore } from '@/platform/settings/settingStore' import { useTelemetry } from '@/platform/telemetry' import { useColorPaletteService } from '@/services/colorPaletteService' +import { useSettingsDialog } from '@/platform/settings/composables/useSettingsDialog' 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' @@ -129,7 +127,7 @@ const commandStore = useCommandStore() const menuItemStore = useMenuItemStore() const colorPaletteStore = useColorPaletteStore() const colorPaletteService = useColorPaletteService() -const dialogStore = useDialogStore() +const settingsDialog = useSettingsDialog() const managerState = useManagerState() const settingStore = useSettingStore() @@ -167,14 +165,9 @@ const translateMenuItem = (item: MenuItem): MenuItem => { } const showSettings = (defaultPanel?: string) => { - dialogStore.showDialog({ - key: 'global-settings', - headerComponent: SettingDialogHeader, - component: SettingDialogContent, - props: { - defaultPanel - } - }) + settingsDialog.show( + defaultPanel as Parameters[0] + ) } const showManageExtensions = async () => { diff --git a/src/components/topbar/CurrentUserPopover.test.ts b/src/components/topbar/CurrentUserPopover.test.ts index d7315dad5..98e33abc9 100644 --- a/src/components/topbar/CurrentUserPopover.test.ts +++ b/src/components/topbar/CurrentUserPopover.test.ts @@ -31,6 +31,15 @@ vi.mock('pinia') const mockShowSettingsDialog = vi.fn() const mockShowTopUpCreditsDialog = vi.fn() +// Mock the settings dialog composable +vi.mock('@/platform/settings/composables/useSettingsDialog', () => ({ + useSettingsDialog: vi.fn(() => ({ + show: mockShowSettingsDialog, + hide: vi.fn(), + showAbout: vi.fn() + })) +})) + // Mock window.open const originalWindowOpen = window.open beforeEach(() => { @@ -64,7 +73,6 @@ vi.mock('@/composables/auth/useFirebaseAuthActions', () => ({ // Mock the dialog service vi.mock('@/services/dialogService', () => ({ useDialogService: vi.fn(() => ({ - showSettingsDialog: mockShowSettingsDialog, showTopUpCreditsDialog: mockShowTopUpCreditsDialog })) })) diff --git a/src/components/topbar/CurrentUserPopover.vue b/src/components/topbar/CurrentUserPopover.vue index 69961628d..e6e0bdd87 100644 --- a/src/components/topbar/CurrentUserPopover.vue +++ b/src/components/topbar/CurrentUserPopover.vue @@ -152,6 +152,7 @@ import { useSubscription } from '@/platform/cloud/subscription/composables/useSu import { useSubscriptionDialog } from '@/platform/cloud/subscription/composables/useSubscriptionDialog' import { isCloud } from '@/platform/distribution/types' import { useTelemetry } from '@/platform/telemetry' +import { useSettingsDialog } from '@/platform/settings/composables/useSettingsDialog' import { useDialogService } from '@/services/dialogService' import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore' @@ -165,6 +166,7 @@ const { userDisplayName, userEmail, userPhotoUrl, handleSignOut } = useCurrentUser() const authActions = useFirebaseAuthActions() const authStore = useFirebaseAuthStore() +const settingsDialog = useSettingsDialog() const dialogService = useDialogService() const { isActiveSubscription, @@ -198,7 +200,7 @@ const canUpgrade = computed(() => { }) const handleOpenUserSettings = () => { - dialogService.showSettingsDialog('user') + settingsDialog.show('user') emit('close') } @@ -209,9 +211,9 @@ const handleOpenPlansAndPricing = () => { const handleOpenPlanAndCreditsSettings = () => { if (isCloud) { - dialogService.showSettingsDialog('subscription') + settingsDialog.show('subscription') } else { - dialogService.showSettingsDialog('credits') + settingsDialog.show('credits') } emit('close') diff --git a/src/components/topbar/CurrentUserPopoverWorkspace.vue b/src/components/topbar/CurrentUserPopoverWorkspace.vue index 51aa19dd5..9766be4be 100644 --- a/src/components/topbar/CurrentUserPopoverWorkspace.vue +++ b/src/components/topbar/CurrentUserPopoverWorkspace.vue @@ -215,6 +215,7 @@ import { isCloud } from '@/platform/distribution/types' import { useTelemetry } from '@/platform/telemetry' import { useWorkspaceUI } from '@/platform/workspace/composables/useWorkspaceUI' import { useTeamWorkspaceStore } from '@/platform/workspace/stores/teamWorkspaceStore' +import { useSettingsDialog } from '@/platform/settings/composables/useSettingsDialog' import { useDialogService } from '@/services/dialogService' const workspaceStore = useTeamWorkspaceStore() @@ -236,6 +237,7 @@ const { buildDocsUrl, docsPaths } = useExternalLink() const { userDisplayName, userEmail, userPhotoUrl, handleSignOut } = useCurrentUser() const authActions = useFirebaseAuthActions() +const settingsDialog = useSettingsDialog() const dialogService = useDialogService() const { isActiveSubscription } = useSubscription() const { totalCredits, isLoadingBalance } = useSubscriptionCredits() @@ -277,12 +279,12 @@ const showCreditsSection = computed( ) const handleOpenUserSettings = () => { - dialogService.showSettingsDialog('user') + settingsDialog.show('user') emit('close') } const handleOpenWorkspaceSettings = () => { - dialogService.showSettingsDialog('workspace') + settingsDialog.show('workspace') emit('close') } @@ -293,9 +295,9 @@ const handleOpenPlansAndPricing = () => { const handleOpenPlanAndCreditsSettings = () => { if (isCloud) { - dialogService.showSettingsDialog('workspace') + settingsDialog.show('workspace') } else { - dialogService.showSettingsDialog('credits') + settingsDialog.show('credits') } emit('close') diff --git a/src/composables/useCoreCommands.ts b/src/composables/useCoreCommands.ts index 8f3f72304..b2e424348 100644 --- a/src/composables/useCoreCommands.ts +++ b/src/composables/useCoreCommands.ts @@ -34,6 +34,7 @@ import { } from '@/renderer/core/canvas/canvasStore' import { api } from '@/scripts/api' import { app } from '@/scripts/app' +import { useSettingsDialog } from '@/platform/settings/composables/useSettingsDialog' import { useDialogService } from '@/services/dialogService' import { useLitegraphService } from '@/services/litegraphService' import type { ComfyCommand } from '@/stores/commandStore' @@ -73,6 +74,7 @@ const moveSelectedNodesVersionAdded = '1.22.2' export function useCoreCommands(): ComfyCommand[] { const workflowService = useWorkflowService() const workflowStore = useWorkflowStore() + const settingsDialog = useSettingsDialog() const dialogService = useDialogService() const colorPaletteStore = useColorPaletteStore() const firebaseAuthActions = useFirebaseAuthActions() @@ -581,7 +583,7 @@ export function useCoreCommands(): ComfyCommand[] { versionAdded: '1.3.7', category: 'view-controls' as const, function: () => { - dialogService.showSettingsDialog() + settingsDialog.show() } }, { @@ -830,7 +832,7 @@ export function useCoreCommands(): ComfyCommand[] { menubarLabel: 'About ComfyUI', versionAdded: '1.6.4', function: () => { - dialogService.showSettingsDialog('about') + settingsDialog.showAbout() } }, { diff --git a/src/platform/cloud/subscription/components/SubscriptionPanel.vue b/src/platform/cloud/subscription/components/SubscriptionPanel.vue index e395cb7d1..6f1d21302 100644 --- a/src/platform/cloud/subscription/components/SubscriptionPanel.vue +++ b/src/platform/cloud/subscription/components/SubscriptionPanel.vue @@ -1,5 +1,5 @@ diff --git a/src/platform/settings/components/SettingDialogContent.vue b/src/platform/settings/components/SettingDialogContent.vue deleted file mode 100644 index 628301735..000000000 --- a/src/platform/settings/components/SettingDialogContent.vue +++ /dev/null @@ -1,244 +0,0 @@ - - - - - - - diff --git a/src/platform/settings/composables/useSettingUI.ts b/src/platform/settings/composables/useSettingUI.ts index 3b4fc44a0..6f74a48f0 100644 --- a/src/platform/settings/composables/useSettingUI.ts +++ b/src/platform/settings/composables/useSettingUI.ts @@ -8,29 +8,37 @@ import { useVueFeatureFlags } from '@/composables/useVueFeatureFlags' import { isCloud } from '@/platform/distribution/types' import type { SettingTreeNode } from '@/platform/settings/settingStore' import { useSettingStore } from '@/platform/settings/settingStore' -import type { SettingParams } from '@/platform/settings/types' +import type { SettingPanelType, SettingParams } from '@/platform/settings/types' import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription' +import type { NavGroupData } from '@/types/navTypes' import { isElectron } from '@/utils/envUtil' import { normalizeI18nKey } from '@/utils/formatUtil' import { buildTree } from '@/utils/treeUtil' +const CATEGORY_ICONS: Record = { + Comfy: 'icon-[lucide--settings]', + LiteGraph: 'icon-[lucide--workflow]', + Appearance: 'icon-[lucide--palette]', + '3D': 'icon-[lucide--box]', + 'Mask Editor': 'icon-[lucide--pen-tool]', + Other: 'icon-[lucide--ellipsis]', + about: 'icon-[lucide--info]', + credits: 'icon-[lucide--coins]', + user: 'icon-[lucide--user]', + workspace: 'icon-[lucide--building-2]', + keybinding: 'icon-[lucide--keyboard]', + extension: 'icon-[lucide--puzzle]', + 'server-config': 'icon-[lucide--server]', + PlanCredits: 'icon-[lucide--credit-card]' +} + interface SettingPanelItem { node: SettingTreeNode component: Component props?: Record } -export function useSettingUI( - defaultPanel?: - | 'about' - | 'keybinding' - | 'extension' - | 'server-config' - | 'user' - | 'credits' - | 'subscription' - | 'workspace' -) { +export function useSettingUI(defaultPanel?: SettingPanelType) { const { t } = useI18n() const { isLoggedIn } = useCurrentUser() const settingStore = useSettingStore() @@ -321,6 +329,36 @@ export function useSettingUI( : legacyMenuTreeNodes.value ) + const navGroups = computed(() => + groupedMenuTreeNodes.value.map((group) => ({ + title: + (group as SettingTreeNode & { translatedLabel?: string }) + .translatedLabel ?? group.label, + items: (group.children ?? []).map((child) => ({ + id: child.key, + label: + (child as SettingTreeNode & { translatedLabel?: string }) + .translatedLabel ?? child.label, + icon: + CATEGORY_ICONS[child.key] ?? + CATEGORY_ICONS[child.label] ?? + 'icon-[lucide--plug]' + })) + })) + ) + + function findCategoryByKey(key: string): SettingTreeNode | null { + for (const group of groupedMenuTreeNodes.value) { + const found = group.children?.find((node) => node.key === key) + if (found) return found + } + return null + } + + function findPanelByKey(key: string): SettingPanelItem | null { + return panels.value.find((p) => p.node.key === key) ?? null + } + onMounted(() => { activeCategory.value = defaultCategory.value }) @@ -330,6 +368,10 @@ export function useSettingUI( activeCategory, defaultCategory, groupedMenuTreeNodes, - settingCategories + settingCategories, + navGroups, + teamWorkspacesEnabled, + findCategoryByKey, + findPanelByKey } } diff --git a/src/platform/settings/composables/useSettingsDialog.ts b/src/platform/settings/composables/useSettingsDialog.ts new file mode 100644 index 000000000..a9f74cc9e --- /dev/null +++ b/src/platform/settings/composables/useSettingsDialog.ts @@ -0,0 +1,33 @@ +import { useDialogService } from '@/services/dialogService' +import { useDialogStore } from '@/stores/dialogStore' + +import SettingDialog from '@/platform/settings/components/SettingDialog.vue' +import type { SettingPanelType } from '@/platform/settings/types' + +const DIALOG_KEY = 'global-settings' + +export function useSettingsDialog() { + const dialogService = useDialogService() + const dialogStore = useDialogStore() + + function hide() { + dialogStore.closeDialog({ key: DIALOG_KEY }) + } + + function show(panel?: SettingPanelType) { + dialogService.showLayoutDialog({ + key: DIALOG_KEY, + component: SettingDialog, + props: { + onClose: hide, + ...(panel ? { defaultPanel: panel } : {}) + } + }) + } + + function showAbout() { + show('about') + } + + return { show, hide, showAbout } +} diff --git a/src/platform/settings/types.ts b/src/platform/settings/types.ts index 021fac375..2a2c38c2c 100644 --- a/src/platform/settings/types.ts +++ b/src/platform/settings/types.ts @@ -64,3 +64,13 @@ export interface ISettingGroup { label: string settings: SettingParams[] } + +export type SettingPanelType = + | 'about' + | 'keybinding' + | 'extension' + | 'server-config' + | 'user' + | 'credits' + | 'subscription' + | 'workspace' diff --git a/src/platform/workspace/composables/useWorkspaceUI.ts b/src/platform/workspace/composables/useWorkspaceUI.ts index 477d9026b..f9c8ead90 100644 --- a/src/platform/workspace/composables/useWorkspaceUI.ts +++ b/src/platform/workspace/composables/useWorkspaceUI.ts @@ -1,4 +1,4 @@ -import { computed, ref } from 'vue' +import { computed } from 'vue' import { createSharedComposable } from '@vueuse/core' import type { WorkspaceRole, WorkspaceType } from '../api/workspaceApi' @@ -81,13 +81,6 @@ function getUIConfig( function useWorkspaceUIInternal() { const store = useTeamWorkspaceStore() - // Tab management (shared UI state) - const activeTab = ref('plan') - - function setActiveTab(tab: string | number) { - activeTab.value = String(tab) - } - const workspaceType = computed( () => store.activeWorkspace?.type ?? 'personal' ) @@ -105,10 +98,6 @@ function useWorkspaceUIInternal() { ) return { - // Tab management - activeTab: computed(() => activeTab.value), - setActiveTab, - // Permissions and config permissions, uiConfig, diff --git a/src/scripts/ui.ts b/src/scripts/ui.ts index 9a0f9d5cc..e44b8ee56 100644 --- a/src/scripts/ui.ts +++ b/src/scripts/ui.ts @@ -1,7 +1,7 @@ import { useSettingStore } from '@/platform/settings/settingStore' import { WORKFLOW_ACCEPT_STRING } from '@/platform/workflow/core/types/formats' import { type StatusWsMessageStatus } from '@/schemas/apiSchema' -import { useDialogService } from '@/services/dialogService' +import { useSettingsDialog } from '@/platform/settings/composables/useSettingsDialog' import { isCloud } from '@/platform/distribution/types' import { useTelemetry } from '@/platform/telemetry' import { useLitegraphService } from '@/services/litegraphService' @@ -469,7 +469,7 @@ export class ComfyUI { $el('button.comfy-settings-btn', { textContent: '⚙️', onclick: () => { - useDialogService().showSettingsDialog() + useSettingsDialog().show() } }), $el('button.comfy-close-menu-btn', { diff --git a/src/services/dialogService.ts b/src/services/dialogService.ts index a5fc807e1..17cbbbcd0 100644 --- a/src/services/dialogService.ts +++ b/src/services/dialogService.ts @@ -13,12 +13,10 @@ import SignInContent from '@/components/dialog/content/SignInContent.vue' import TopUpCreditsDialogContent from '@/components/dialog/content/TopUpCreditsDialogContent.vue' import UpdatePasswordContent from '@/components/dialog/content/UpdatePasswordContent.vue' import ComfyOrgHeader from '@/components/dialog/header/ComfyOrgHeader.vue' -import SettingDialogHeader from '@/components/dialog/header/SettingDialogHeader.vue' import { t } from '@/i18n' import { useTelemetry } from '@/platform/telemetry' import { isCloud } from '@/platform/distribution/types' import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription' -import SettingDialogContent from '@/platform/settings/components/SettingDialogContent.vue' import { useDialogStore } from '@/stores/dialogStore' import type { DialogComponentProps, @@ -93,38 +91,6 @@ export const useDialogService = () => { }) } - function showSettingsDialog( - panel?: - | 'about' - | 'keybinding' - | 'extension' - | 'server-config' - | 'user' - | 'credits' - | 'subscription' - | 'workspace' - ) { - const props = panel ? { props: { defaultPanel: panel } } : undefined - - dialogStore.showDialog({ - key: 'global-settings', - headerComponent: SettingDialogHeader, - component: SettingDialogContent, - ...props - }) - } - - function showAboutDialog() { - dialogStore.showDialog({ - key: 'global-settings', - headerComponent: SettingDialogHeader, - component: SettingDialogContent, - props: { - defaultPanel: 'about' - } - }) - } - function showExecutionErrorDialog(executionError: ExecutionErrorDialogInput) { const props: ComponentAttrs = { error: { @@ -592,8 +558,6 @@ export const useDialogService = () => { return { showLoadWorkflowWarning, showMissingModelsWarning, - showSettingsDialog, - showAboutDialog, showExecutionErrorDialog, showApiNodesSignInDialog, showSignInDialog, diff --git a/src/stores/firebaseAuthStore.test.ts b/src/stores/firebaseAuthStore.test.ts index bbad43264..ace511592 100644 --- a/src/stores/firebaseAuthStore.test.ts +++ b/src/stores/firebaseAuthStore.test.ts @@ -102,7 +102,6 @@ describe('useFirebaseAuthStore', () => { // Setup dialog service mock vi.mocked(useDialogService, { partial: true }).mockReturnValue({ - showSettingsDialog: vi.fn(), showErrorDialog: vi.fn() }) diff --git a/src/workbench/extensions/manager/composables/useManagerState.test.ts b/src/workbench/extensions/manager/composables/useManagerState.test.ts index 1ba41d107..809244223 100644 --- a/src/workbench/extensions/manager/composables/useManagerState.test.ts +++ b/src/workbench/extensions/manager/composables/useManagerState.test.ts @@ -33,11 +33,11 @@ vi.mock('@/stores/systemStatsStore', () => ({ useSystemStatsStore: vi.fn() })) -vi.mock('@/services/dialogService', () => ({ - useDialogService: vi.fn(() => ({ - showManagerPopup: vi.fn(), - showLegacyManagerPopup: vi.fn(), - showSettingsDialog: vi.fn() +vi.mock('@/platform/settings/composables/useSettingsDialog', () => ({ + useSettingsDialog: vi.fn(() => ({ + show: vi.fn(), + hide: vi.fn(), + showAbout: vi.fn() })) })) diff --git a/src/workbench/extensions/manager/composables/useManagerState.ts b/src/workbench/extensions/manager/composables/useManagerState.ts index 49c9debfd..f435a6530 100644 --- a/src/workbench/extensions/manager/composables/useManagerState.ts +++ b/src/workbench/extensions/manager/composables/useManagerState.ts @@ -4,7 +4,7 @@ import { computed, readonly } from 'vue' import { t } from '@/i18n' import { useToastStore } from '@/platform/updates/common/toastStore' import { api } from '@/scripts/api' -import { useDialogService } from '@/services/dialogService' +import { useSettingsDialog } from '@/platform/settings/composables/useSettingsDialog' import { useCommandStore } from '@/stores/commandStore' import { useSystemStatsStore } from '@/stores/systemStatsStore' import { useManagerDialog } from '@/workbench/extensions/manager/composables/useManagerDialog' @@ -148,12 +148,12 @@ export function useManagerState() { isLegacyOnly?: boolean }): Promise => { const state = managerUIState.value - const dialogService = useDialogService() + const settingsDialog = useSettingsDialog() const commandStore = useCommandStore() switch (state) { case ManagerUIState.DISABLED: - dialogService.showSettingsDialog('extension') + settingsDialog.show('extension') break case ManagerUIState.LEGACY_UI: { @@ -173,7 +173,7 @@ export function useManagerState() { } // Fallback to extensions panel if not showing toast if (options?.showToastOnLegacyError === false) { - dialogService.showSettingsDialog('extension') + settingsDialog.show('extension') } } break