From af31b39f309b97f374288fc56a3fcd929d45c620 Mon Sep 17 00:00:00 2001 From: Quorafind Date: Thu, 9 Oct 2025 16:59:21 +0800 Subject: [PATCH] feat(settings): add dedicated Interface settings tab with Fluent/Legacy mode selection - Create new InterfaceSettingsTab with visual mode selection using SelectableCard component - Move Fluent interface settings from Beta tab to new Interface tab - Remove deprecated ribbon icon setting - Conditionally show Fluent-specific settings based on selected mode - Add Interface tab to Core Settings category in main settings --- src/components/features/settings/index.ts | 1 + .../settings/tabs/BetaTestSettingsTab.ts | 140 ---------- .../settings/tabs/InterfaceSettingsTab.ts | 246 ++++++++++++++++++ src/setting.ts | 17 ++ 4 files changed, 264 insertions(+), 140 deletions(-) create mode 100644 src/components/features/settings/tabs/InterfaceSettingsTab.ts diff --git a/src/components/features/settings/index.ts b/src/components/features/settings/index.ts index 3209b9dc..da7b1670 100644 --- a/src/components/features/settings/index.ts +++ b/src/components/features/settings/index.ts @@ -2,6 +2,7 @@ export { renderAboutSettingsTab } from "./tabs/AboutSettingsTab"; export { renderBetaTestSettingsTab } from "./tabs/BetaTestSettingsTab"; export { renderHabitSettingsTab } from "./tabs/HabitSettingsTab"; export { IcsSettingsComponent } from "./tabs/IcsSettingsTab"; +export { renderInterfaceSettingsTab } from "./tabs/InterfaceSettingsTab"; export { renderProgressSettingsTab } from "./tabs/ProgressSettingsTab"; export { renderQuickCaptureSettingsTab } from "./tabs/QuickCaptureSettingsTab"; export { renderRewardSettingsTab } from "./tabs/RewardSettingsTab"; diff --git a/src/components/features/settings/tabs/BetaTestSettingsTab.ts b/src/components/features/settings/tabs/BetaTestSettingsTab.ts index 07b8e097..2537bf4d 100644 --- a/src/components/features/settings/tabs/BetaTestSettingsTab.ts +++ b/src/components/features/settings/tabs/BetaTestSettingsTab.ts @@ -41,146 +41,6 @@ export function renderBetaTestSettingsTab( ), }); - // Base View Settings - - // V2 View Settings - new Setting(containerEl) - .setName(t("Task Genius Fluent Interface")) - .setDesc( - t( - "Enable the experimental V2 interface with improved layout and workspace support" - ) - ) - .addToggle((toggle) => { - toggle - .setValue( - settingTab.plugin.settings.fluentView?.enableFluent ?? false - ) - .onChange(async (value) => { - if (!settingTab.plugin.settings.fluentView) { - settingTab.plugin.settings.fluentView = { - enableFluent: false, - showFluentRibbon: false, - }; - } - settingTab.plugin.settings.fluentView!.enableFluent = value; - await settingTab.plugin.saveSettings(); - - // Notify user - new Notice( - value - ? t( - "Fluent interface enabled. Use the command 'Open Task View V2' to launch." - ) - : t("Fluent interface disabled.") - ); - }); - }); - - new Setting(containerEl) - .setName(t("Show Fluent(beta) View Ribbon Icon")) - .setDesc(t("Show a ribbon icon for quick access to Fluent interface")) - .addToggle((toggle) => { - toggle - .setValue( - settingTab.plugin.settings.fluentView?.showFluentRibbon ?? - false - ) - .onChange(async (value) => { - if (!settingTab.plugin.settings.fluentView) { - settingTab.plugin.settings.fluentView = { - enableFluent: false, - showFluentRibbon: false, - }; - } - settingTab.plugin.settings.fluentView!.showFluentRibbon = - value; - await settingTab.plugin.saveSettings(); - - new Notice( - t( - "Please reload the plugin for ribbon icon changes to take effect." - ) - ); - }); - }); - - // V2: Use workspace side leaves for Sidebar & Details - new Setting(containerEl) - .setName(t("Fluent: Use Workspace Side Leaves")) - .setDesc( - t( - "Use left/right workspace side leaves for Sidebar and Details. When enabled, the main V2 view won't render in-view sidebar or details." - ) - ) - .addToggle((toggle) => { - const current = ((settingTab.plugin.settings.fluentView)?.useWorkspaceSideLeaves ?? true); - toggle - .setValue(current) - .onChange(async (value) => { - if (!settingTab.plugin.settings.fluentView) { - settingTab.plugin.settings.fluentView = { - enableFluent: false, - showFluentRibbon: false, - }; - } - if (!settingTab.plugin.settings.fluentView.fluentConfig) { - settingTab.plugin.settings.fluentView.fluentConfig = { - enableWorkspaces: true, - defaultWorkspace: "default", - showTopNavigation: true, - showNewSidebar: true, - allowViewSwitching: true, - persistViewMode: true, - }; - } - // Store via 'any' to avoid typing constraints for experimental backfill - ((settingTab.plugin.settings.fluentView)).useWorkspaceSideLeaves = value; - await settingTab.plugin.saveSettings(); - new Notice(t("Saved. Reopen the view to apply.")); - }); - }); - - // V2 Sidebar Other Views overflow threshold - new Setting(containerEl) - .setName(t("Fluent: Max Other Views before overflow")) - .setDesc( - t( - "Number of 'Other Views' to show before grouping the rest into an overflow menu (ellipsis)" - ) - ) - .addText((text) => { - const current = - settingTab.plugin.settings.fluentView?.fluentConfig - ?.maxOtherViewsBeforeOverflow ?? 5; - text.setPlaceholder("5") - .setValue(String(current)) - .onChange(async (value) => { - const n = parseInt(value, 10); - if (!isNaN(n) && n >= 1 && n <= 50) { - if (!settingTab.plugin.settings.fluentView) { - settingTab.plugin.settings.fluentView = { - enableFluent: false, - showFluentRibbon: false, - }; - } - if (!settingTab.plugin.settings.fluentView.fluentConfig) { - settingTab.plugin.settings.fluentView.fluentConfig = { - enableWorkspaces: true, - defaultWorkspace: "default", - showTopNavigation: true, - showNewSidebar: true, - allowViewSwitching: true, - persistViewMode: true, - }; - } - settingTab.plugin.settings.fluentView.fluentConfig.maxOtherViewsBeforeOverflow = - n; - await settingTab.plugin.saveSettings(); - } - }); - }); - // Feedback section new Setting(containerEl) .setName(t("Beta Feedback")) diff --git a/src/components/features/settings/tabs/InterfaceSettingsTab.ts b/src/components/features/settings/tabs/InterfaceSettingsTab.ts new file mode 100644 index 00000000..51c3f1a3 --- /dev/null +++ b/src/components/features/settings/tabs/InterfaceSettingsTab.ts @@ -0,0 +1,246 @@ +import { Setting, Notice } from "obsidian"; +import { TaskProgressBarSettingTab } from "@/setting"; +import { t } from "@/translations/helper"; +import { + SelectableCard, + SelectableCardConfig, +} from "@/components/features/onboarding/ui/SelectableCard"; + +export function renderInterfaceSettingsTab( + settingTab: TaskProgressBarSettingTab, + containerEl: HTMLElement +) { + // Header + new Setting(containerEl) + .setName(t("User Interface")) + .setDesc( + t( + "Choose your preferred interface style and configure how Task Genius displays in your workspace." + ) + ) + .setHeading(); + + // Mode Selection Section + new Setting(containerEl) + .setName(t("Interface Mode")) + .setDesc( + t( + "Select between the modern Fluent interface or the classic Legacy interface." + ) + ) + .setHeading(); + + // Create mode selection container + const modeSelectionContainer = containerEl.createDiv({ + cls: "interface-mode-selection", + }); + + // Get current mode + const currentMode = settingTab.plugin.settings.fluentView?.enableFluent + ? "fluent" + : "legacy"; + + // Create cards configuration + const cardConfigs: SelectableCardConfig[] = [ + { + id: "fluent", + title: t("Fluent"), + subtitle: t("Modern & Sleek"), + description: t( + "New visual design with elegant animations and modern interactions" + ), + preview: createFluentPreview(), + }, + { + id: "legacy", + title: t("Legacy"), + subtitle: t("Classic & Familiar"), + description: t( + "Keep the familiar interface and interaction style you know" + ), + preview: createLegacyPreview(), + }, + ]; + + // Create selectable cards + const card = new SelectableCard( + modeSelectionContainer, + cardConfigs, + { + containerClass: "selectable-cards-container", + cardClass: "selectable-card", + showPreview: true, + }, + async (mode) => { + // Update settings + if (!settingTab.plugin.settings.fluentView) { + settingTab.plugin.settings.fluentView = { + enableFluent: false, + showFluentRibbon: false, + }; + } + settingTab.plugin.settings.fluentView.enableFluent = mode === "fluent"; + await settingTab.plugin.saveSettings(); + + // Re-render the settings to show/hide Fluent-specific options + renderFluentSpecificSettings(); + } + ); + + // Set initial selection + card.setSelected(currentMode); + + // Container for Fluent-specific settings + const fluentSettingsContainer = containerEl.createDiv({ + cls: "fluent-specific-settings", + }); + + // Function to render Fluent-specific settings + const renderFluentSpecificSettings = () => { + // Clear the container + fluentSettingsContainer.empty(); + + // Only show if Fluent is enabled + if (!settingTab.plugin.settings.fluentView?.enableFluent) { + return; + } + + // Fluent Settings Header + new Setting(fluentSettingsContainer) + .setName(t("Fluent Interface Settings")) + .setDesc(t("Configure options specific to the Fluent interface.")) + .setHeading(); + + // Use workspace side leaves for Sidebar & Details + new Setting(fluentSettingsContainer) + .setName(t("Use Workspace Side Leaves")) + .setDesc( + t( + "Use left/right workspace side leaves for Sidebar and Details. When enabled, the main V2 view won't render in-view sidebar or details." + ) + ) + .addToggle((toggle) => { + const current = settingTab.plugin.settings.fluentView?.useWorkspaceSideLeaves ?? true; + toggle + .setValue(current) + .onChange(async (value) => { + if (!settingTab.plugin.settings.fluentView) { + settingTab.plugin.settings.fluentView = { + enableFluent: false, + showFluentRibbon: false, + }; + } + if (!settingTab.plugin.settings.fluentView.fluentConfig) { + settingTab.plugin.settings.fluentView.fluentConfig = { + enableWorkspaces: true, + defaultWorkspace: "default", + showTopNavigation: true, + showNewSidebar: true, + allowViewSwitching: true, + persistViewMode: true, + }; + } + // Store via 'any' to avoid typing constraints for experimental backfill + (settingTab.plugin.settings.fluentView as any).useWorkspaceSideLeaves = value; + await settingTab.plugin.saveSettings(); + new Notice(t("Saved. Reopen the view to apply.")); + }); + }); + + // Max Other Views before overflow threshold + new Setting(fluentSettingsContainer) + .setName(t("Max Other Views before overflow")) + .setDesc( + t( + "Number of 'Other Views' to show before grouping the rest into an overflow menu (ellipsis)" + ) + ) + .addText((text) => { + const current = + settingTab.plugin.settings.fluentView?.fluentConfig + ?.maxOtherViewsBeforeOverflow ?? 5; + text.setPlaceholder("5") + .setValue(String(current)) + .onChange(async (value) => { + const n = parseInt(value, 10); + if (!isNaN(n) && n >= 1 && n <= 50) { + if (!settingTab.plugin.settings.fluentView) { + settingTab.plugin.settings.fluentView = { + enableFluent: false, + showFluentRibbon: false, + }; + } + if (!settingTab.plugin.settings.fluentView.fluentConfig) { + settingTab.plugin.settings.fluentView.fluentConfig = { + enableWorkspaces: true, + defaultWorkspace: "default", + showTopNavigation: true, + showNewSidebar: true, + allowViewSwitching: true, + persistViewMode: true, + }; + } + settingTab.plugin.settings.fluentView.fluentConfig.maxOtherViewsBeforeOverflow = n; + await settingTab.plugin.saveSettings(); + } + }); + }); + }; + + // Initial render of Fluent-specific settings + renderFluentSpecificSettings(); +} + +/** + * Create Fluent mode preview + */ +function createFluentPreview(): HTMLElement { + const preview = createDiv({ + cls: ["mode-preview", "mode-preview-fluent"], + }); + + // Check theme + const isDark = document.body.classList.contains("theme-dark"); + const theme = isDark ? "" : "-light"; + const imageUrl = `https://raw.githubusercontent.com/Quorafind/Obsidian-Task-Progress-Bar/master/media/fluent${theme}.png`; + + const img = preview.createEl("img", { + attr: { + src: imageUrl, + alt: "Fluent mode preview", + }, + }); + img.style.maxWidth = "100%"; + img.style.maxHeight = "100%"; + img.style.objectFit = "contain"; + img.style.borderRadius = "4px"; + + return preview; +} + +/** + * Create Legacy mode preview + */ +function createLegacyPreview(): HTMLElement { + const preview = createDiv({ + cls: ["mode-preview", "mode-preview-legacy"], + }); + + // Check theme + const isDark = document.body.classList.contains("theme-dark"); + const theme = isDark ? "" : "-light"; + const imageUrl = `https://raw.githubusercontent.com/Quorafind/Obsidian-Task-Progress-Bar/master/media/legacy${theme}.png`; + + const img = preview.createEl("img", { + attr: { + src: imageUrl, + alt: "Legacy mode preview", + }, + }); + img.style.maxWidth = "100%"; + img.style.maxHeight = "100%"; + img.style.objectFit = "contain"; + img.style.borderRadius = "4px"; + + return preview; +} diff --git a/src/setting.ts b/src/setting.ts index 26b7778f..e4ead14e 100644 --- a/src/setting.ts +++ b/src/setting.ts @@ -24,6 +24,7 @@ import { renderAboutSettingsTab, renderBetaTestSettingsTab, renderHabitSettingsTab, + renderInterfaceSettingsTab, renderProgressSettingsTab, renderTaskStatusSettingsTab, renderDatePrioritySettingsTab, @@ -82,6 +83,12 @@ export class TaskProgressBarSettingTab extends PluginSettingTab { icon: "layout", category: "core", }, + { + id: "interface", + name: t("Interface"), + icon: "layout-dashboard", + category: "core", + }, { id: "file-filter", name: t("File Filter"), @@ -673,6 +680,10 @@ export class TaskProgressBarSettingTab extends PluginSettingTab { const viewSettingsSection = this.createTabSection("view-settings"); this.displayViewSettings(viewSettingsSection); + // Interface Tab + const interfaceSection = this.createTabSection("interface"); + this.displayInterfaceSettings(interfaceSection); + // Reward Tab const rewardSection = this.createTabSection("reward"); this.displayRewardSettings(rewardSection); @@ -729,6 +740,8 @@ export class TaskProgressBarSettingTab extends PluginSettingTab { return `${base}/task-view/indexer`; case "view-settings": return `${base}/task-view`; + case "interface": + return `${base}/interface`; case "file-filter": return `${base}/file-filter`; case "progress-bar": @@ -830,6 +843,10 @@ export class TaskProgressBarSettingTab extends PluginSettingTab { renderViewSettingsTab(this, containerEl); } + private displayInterfaceSettings(containerEl: HTMLElement): void { + renderInterfaceSettingsTab(this, containerEl); + } + private displayIndexSettings(containerEl: HTMLElement): void { renderIndexSettingsTab(this, containerEl); }