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
This commit is contained in:
Quorafind 2025-10-09 16:59:21 +08:00
parent 75162619d6
commit af31b39f30
4 changed files with 264 additions and 140 deletions

View file

@ -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";

View file

@ -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"))

View file

@ -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<string>[] = [
{
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<string>(
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;
}

View file

@ -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);
}