mirror of
https://github.com/taskgenius/taskgenius-plugin.git
synced 2026-07-22 06:40:25 +00:00
feat(fluent): add overflow menu for other views and optimized task updates
- Add configurable overflow threshold for Other Views (default: 5) - Show overflow menu (ellipsis) when more than 5 other views exist - Add setting in Beta Test tab to configure overflow threshold - Implement refreshCurrentViewData for lightweight updates - Optimize task updates to only refresh affected components - Preserve details panel state during task updates - Avoid full view re-renders when task data changes
This commit is contained in:
parent
86f8d1719e
commit
c747e2bb14
5 changed files with 202 additions and 30 deletions
|
|
@ -429,6 +429,7 @@ export interface ExperimentalSettings {
|
|||
showNewSidebar: boolean;
|
||||
allowViewSwitching: boolean;
|
||||
persistViewMode: boolean;
|
||||
maxOtherViewsBeforeOverflow?: number; // how many other views to show before overflow menu
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -1645,7 +1646,7 @@ export const DEFAULT_SETTINGS: TaskProgressBarSettings = {
|
|||
// Helper function to get view settings safely
|
||||
export function getViewSettingOrDefault(
|
||||
plugin: TaskProgressBarPlugin,
|
||||
viewId: ViewMode,
|
||||
viewId: ViewMode
|
||||
): ViewConfig {
|
||||
const viewConfiguration =
|
||||
plugin.settings.viewConfiguration || DEFAULT_SETTINGS.viewConfiguration;
|
||||
|
|
@ -1655,7 +1656,7 @@ export function getViewSettingOrDefault(
|
|||
|
||||
// Then check if it exists in default settings
|
||||
const defaultConfig = DEFAULT_SETTINGS.viewConfiguration.find(
|
||||
(v) => v.id === viewId,
|
||||
(v) => v.id === viewId
|
||||
);
|
||||
|
||||
// If neither exists, create a fallback default for custom views
|
||||
|
|
@ -1687,7 +1688,7 @@ export function getViewSettingOrDefault(
|
|||
? {
|
||||
...(baseConfig.filterRules || {}), // Start with base's filterRules
|
||||
...savedConfig.filterRules, // Override with saved filterRules properties
|
||||
}
|
||||
}
|
||||
: baseConfig.filterRules || {}, // If no saved filterRules, use base's
|
||||
// Merge specificConfig: Saved overrides default, default overrides base (which might be fallback without specificConfig)
|
||||
// Ensure that the spread of savedConfig doesn't overwrite specificConfig object entirely if base has one and saved doesn't.
|
||||
|
|
@ -1697,7 +1698,7 @@ export function getViewSettingOrDefault(
|
|||
// If saved has specificConfig, merge it onto base's
|
||||
...(baseConfig.specificConfig || {}),
|
||||
...savedConfig.specificConfig,
|
||||
}
|
||||
}
|
||||
: baseConfig.specificConfig, // Otherwise, just use base's specificConfig (could be undefined)
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@ import { t } from "@/translations/helper";
|
|||
|
||||
export function renderBetaTestSettingsTab(
|
||||
settingTab: TaskProgressBarSettingTab,
|
||||
containerEl: HTMLElement,
|
||||
containerEl: HTMLElement
|
||||
) {
|
||||
new Setting(containerEl)
|
||||
.setName(t("Beta Test Features"))
|
||||
.setDesc(
|
||||
t(
|
||||
"Experimental features that are currently in testing phase. These features may be unstable and could change or be removed in future updates.",
|
||||
),
|
||||
"Experimental features that are currently in testing phase. These features may be unstable and could change or be removed in future updates."
|
||||
)
|
||||
)
|
||||
.setHeading();
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ export function renderBetaTestSettingsTab(
|
|||
const warningText = warningContent.createEl("div", {
|
||||
cls: "beta-warning-text",
|
||||
text: t(
|
||||
"These features are experimental and may be unstable. They could change significantly or be removed in future updates due to Obsidian API changes or other factors. Please use with caution and provide feedback to help improve these features.",
|
||||
"These features are experimental and may be unstable. They could change significantly or be removed in future updates due to Obsidian API changes or other factors. Please use with caution and provide feedback to help improve these features."
|
||||
),
|
||||
});
|
||||
|
||||
|
|
@ -48,13 +48,13 @@ export function renderBetaTestSettingsTab(
|
|||
.setName(t("Task Genius V2 Interface"))
|
||||
.setDesc(
|
||||
t(
|
||||
"Enable the experimental V2 interface with improved layout and workspace support",
|
||||
),
|
||||
"Enable the experimental V2 interface with improved layout and workspace support"
|
||||
)
|
||||
)
|
||||
.addToggle((toggle) => {
|
||||
toggle
|
||||
.setValue(
|
||||
settingTab.plugin.settings.experimental?.enableV2 ?? false,
|
||||
settingTab.plugin.settings.experimental?.enableV2 ?? false
|
||||
)
|
||||
.onChange(async (value) => {
|
||||
if (!settingTab.plugin.settings.experimental) {
|
||||
|
|
@ -70,9 +70,9 @@ export function renderBetaTestSettingsTab(
|
|||
new Notice(
|
||||
value
|
||||
? t(
|
||||
"V2 interface enabled. Use the command 'Open Task View V2' to launch.",
|
||||
)
|
||||
: t("V2 interface disabled."),
|
||||
"V2 interface enabled. Use the command 'Open Task View V2' to launch."
|
||||
)
|
||||
: t("V2 interface disabled.")
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -84,7 +84,7 @@ export function renderBetaTestSettingsTab(
|
|||
toggle
|
||||
.setValue(
|
||||
settingTab.plugin.settings.experimental?.showV2Ribbon ??
|
||||
false,
|
||||
false
|
||||
)
|
||||
.onChange(async (value) => {
|
||||
if (!settingTab.plugin.settings.experimental) {
|
||||
|
|
@ -99,19 +99,59 @@ export function renderBetaTestSettingsTab(
|
|||
|
||||
new Notice(
|
||||
t(
|
||||
"Please reload the plugin for ribbon icon changes to take effect.",
|
||||
),
|
||||
"Please reload the plugin for ribbon icon changes to take effect."
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// V2 Sidebar Other Views overflow threshold
|
||||
new Setting(containerEl)
|
||||
.setName(t("V2: 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.experimental?.v2Config
|
||||
?.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.experimental) {
|
||||
settingTab.plugin.settings.experimental = {
|
||||
enableV2: false,
|
||||
showV2Ribbon: false,
|
||||
};
|
||||
}
|
||||
if (!settingTab.plugin.settings.experimental.v2Config) {
|
||||
settingTab.plugin.settings.experimental.v2Config = {
|
||||
enableWorkspaces: true,
|
||||
defaultWorkspace: "default",
|
||||
showTopNavigation: true,
|
||||
showNewSidebar: true,
|
||||
allowViewSwitching: true,
|
||||
persistViewMode: true,
|
||||
};
|
||||
}
|
||||
settingTab.plugin.settings.experimental.v2Config.maxOtherViewsBeforeOverflow =
|
||||
n;
|
||||
await settingTab.plugin.saveSettings();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Feedback section
|
||||
new Setting(containerEl)
|
||||
.setName(t("Beta Feedback"))
|
||||
.setDesc(
|
||||
t(
|
||||
"Help improve these features by providing feedback on your experience.",
|
||||
),
|
||||
"Help improve these features by providing feedback on your experience."
|
||||
)
|
||||
)
|
||||
.setHeading();
|
||||
|
||||
|
|
@ -119,13 +159,13 @@ export function renderBetaTestSettingsTab(
|
|||
.setName(t("Report Issues"))
|
||||
.setDesc(
|
||||
t(
|
||||
"If you encounter any issues with beta features, please report them to help improve the plugin.",
|
||||
),
|
||||
"If you encounter any issues with beta features, please report them to help improve the plugin."
|
||||
)
|
||||
)
|
||||
.addButton((button) => {
|
||||
button.setButtonText(t("Report Issue")).onClick(() => {
|
||||
window.open(
|
||||
"https://github.com/quorafind/obsidian-task-genius/issues",
|
||||
"https://github.com/quorafind/obsidian-task-genius/issues"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1509,6 +1509,70 @@ export class TaskViewV2 extends ItemView {
|
|||
this.switchView(this.currentViewId);
|
||||
}
|
||||
|
||||
private refreshCurrentViewData() {
|
||||
const viewId = this.currentViewId;
|
||||
// Update top badges and projects minimally
|
||||
this.topNavigation?.refresh?.();
|
||||
this.sidebar?.projectList?.refresh?.();
|
||||
// Content-based views (list/tree/kanban/calendar under inbox/today/upcoming/flagged/projects)
|
||||
if (this.isContentBasedView(viewId)) {
|
||||
switch (this.viewState.viewMode) {
|
||||
case "kanban":
|
||||
this.kanbanComponent?.setTasks?.(this.filteredTasks);
|
||||
break;
|
||||
case "calendar":
|
||||
this.calendarComponent?.setTasks?.(this.filteredTasks);
|
||||
break;
|
||||
case "tree":
|
||||
case "list":
|
||||
default:
|
||||
// ContentComponent expects (filtered, all)
|
||||
this.contentComponent?.setTasks?.(
|
||||
this.filteredTasks,
|
||||
this.tasks,
|
||||
true
|
||||
);
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Special/other views
|
||||
if (this.viewComponentManager?.isSpecialView(viewId)) {
|
||||
const comp: any = (
|
||||
this.viewComponentManager as any
|
||||
).getOrCreateComponent(viewId);
|
||||
if (comp?.updateTasks) {
|
||||
comp.updateTasks(this.filteredTasks);
|
||||
} else if (comp?.setTasks) {
|
||||
comp.setTasks(this.filteredTasks, this.tasks);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Direct known components fallback
|
||||
const mapping: Record<string, any> = {
|
||||
forecast: this.forecastComponent,
|
||||
tags: this.tagsComponent,
|
||||
projects: this.contentComponent,
|
||||
review: this.reviewComponent,
|
||||
habit: this.habitComponent,
|
||||
gantt: this.ganttComponent,
|
||||
kanban: this.kanbanComponent,
|
||||
calendar: this.calendarComponent,
|
||||
};
|
||||
const target: any = (mapping as any)[viewId];
|
||||
if (target?.setTasks) {
|
||||
if (viewId === "projects" || this.isContentBasedView(viewId)) {
|
||||
target.setTasks(this.filteredTasks, this.tasks, true);
|
||||
} else {
|
||||
target.setTasks(this.filteredTasks);
|
||||
}
|
||||
} else if (target?.updateTasks) {
|
||||
target.updateTasks(this.filteredTasks);
|
||||
}
|
||||
}
|
||||
|
||||
private renderLoadingState() {
|
||||
console.log("[TG-V2] renderLoadingState called");
|
||||
this.clearCurrentView();
|
||||
|
|
@ -2073,9 +2137,23 @@ export class TaskViewV2 extends ItemView {
|
|||
this.tasks[index] = writeResult.task || updatedTask;
|
||||
}
|
||||
|
||||
// Refresh view
|
||||
// Re-apply filters and lightly refresh current view without full rerender
|
||||
this.applyFilters();
|
||||
this.renderContent();
|
||||
this.refreshCurrentViewData();
|
||||
|
||||
// If the updated task is currently selected, refresh the details panel only
|
||||
const updated = writeResult.task || updatedTask;
|
||||
if (
|
||||
this.currentSelectedTaskId &&
|
||||
updated &&
|
||||
this.currentSelectedTaskId === updated.id
|
||||
) {
|
||||
if (this.detailsComponent?.isCurrentlyEditing?.()) {
|
||||
(this.detailsComponent as any).currentTask = updated;
|
||||
} else {
|
||||
this.detailsComponent?.showTaskDetails(updated);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to update task:", error);
|
||||
new Notice("Failed to update task");
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ export class V2Integration {
|
|||
// Register the V2 view
|
||||
this.plugin.registerView(
|
||||
TASK_VIEW_V2_TYPE,
|
||||
(leaf: WorkspaceLeaf) => new TaskViewV2(leaf, this.plugin),
|
||||
(leaf: WorkspaceLeaf) => new TaskViewV2(leaf, this.plugin)
|
||||
);
|
||||
|
||||
// Add command to open V2 view
|
||||
|
|
@ -41,7 +41,7 @@ export class V2Integration {
|
|||
"Task Genius V2",
|
||||
() => {
|
||||
this.openV2View();
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -104,6 +104,7 @@ export class V2Integration {
|
|||
showNewSidebar: true,
|
||||
allowViewSwitching: true,
|
||||
persistViewMode: true,
|
||||
maxOtherViewsBeforeOverflow: 5,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -95,9 +95,18 @@ export class V2Sidebar {
|
|||
});
|
||||
});
|
||||
|
||||
// Other view icons
|
||||
const otherItems = this.computeOtherItems();
|
||||
otherItems.forEach((item: V2NavigationItem) => {
|
||||
// Other view icons with overflow menu when > 5
|
||||
const allOtherItems = this.computeOtherItems();
|
||||
const visibleCount =
|
||||
this.plugin?.settings?.experimental?.v2Config
|
||||
?.maxOtherViewsBeforeOverflow ?? 5;
|
||||
const displayedOther: V2NavigationItem[] = allOtherItems.slice(
|
||||
0,
|
||||
visibleCount
|
||||
);
|
||||
const remainingOther: V2NavigationItem[] =
|
||||
allOtherItems.slice(visibleCount);
|
||||
displayedOther.forEach((item: V2NavigationItem) => {
|
||||
const btn = rail.createDiv({
|
||||
cls: "v2-rail-btn",
|
||||
attr: { "aria-label": item.label, "data-view-id": item.id },
|
||||
|
|
@ -108,6 +117,16 @@ export class V2Sidebar {
|
|||
this.onNavigate(item.id);
|
||||
});
|
||||
});
|
||||
if (remainingOther.length > 0) {
|
||||
const moreBtn = rail.createDiv({
|
||||
cls: "v2-rail-btn",
|
||||
attr: { "aria-label": t("More views") },
|
||||
});
|
||||
setIcon(moreBtn, "more-horizontal");
|
||||
moreBtn.addEventListener("click", (e) =>
|
||||
this.showOtherViewsMenu(e as MouseEvent, remainingOther)
|
||||
);
|
||||
}
|
||||
|
||||
// Projects menu button
|
||||
const projBtn = rail.createDiv({
|
||||
|
|
@ -188,8 +207,26 @@ export class V2Sidebar {
|
|||
const otherHeader = otherSection.createDiv({
|
||||
cls: "v2-section-header",
|
||||
});
|
||||
const allOtherItems = this.computeOtherItems();
|
||||
const visibleCount = 5;
|
||||
const displayedOther: V2NavigationItem[] = allOtherItems.slice(
|
||||
0,
|
||||
visibleCount
|
||||
);
|
||||
const remainingOther: V2NavigationItem[] =
|
||||
allOtherItems.slice(visibleCount);
|
||||
otherHeader.createSpan({ text: "Other Views" });
|
||||
this.renderNavigationItems(otherSection, this.computeOtherItems());
|
||||
if (remainingOther.length > 0) {
|
||||
const moreBtn = otherHeader.createDiv({
|
||||
cls: "v2-section-action",
|
||||
attr: { "aria-label": t("More views") },
|
||||
});
|
||||
setIcon(moreBtn, "more-horizontal");
|
||||
moreBtn.addEventListener("click", (e) =>
|
||||
this.showOtherViewsMenu(e as MouseEvent, remainingOther)
|
||||
);
|
||||
}
|
||||
this.renderNavigationItems(otherSection, displayedOther);
|
||||
}
|
||||
|
||||
private computeOtherItems(): V2NavigationItem[] {
|
||||
|
|
@ -275,6 +312,21 @@ export class V2Sidebar {
|
|||
menu.showAtMouseEvent(event);
|
||||
}
|
||||
|
||||
private showOtherViewsMenu(event: MouseEvent, items: V2NavigationItem[]) {
|
||||
const menu = new Menu();
|
||||
items.forEach((it: V2NavigationItem) => {
|
||||
menu.addItem((mi) => {
|
||||
mi.setTitle(it.label)
|
||||
.setIcon(it.icon)
|
||||
.onClick(() => {
|
||||
this.setActiveItem(it.id);
|
||||
this.onNavigate(it.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
menu.showAtMouseEvent(event);
|
||||
}
|
||||
|
||||
private renderNavigationItems(
|
||||
containerEl: HTMLElement,
|
||||
items: V2NavigationItem[]
|
||||
|
|
|
|||
Loading…
Reference in a new issue