mirror of
https://github.com/taskgenius/taskgenius-plugin.git
synced 2026-07-22 06:40:25 +00:00
feat(fluent): add responsive sidebar auto-collapse
- Auto-collapse sidebar when view width ≤ 600px - Auto-expand sidebar when view width > 600px - Add leaf-width-updated event for width tracking - Implement debounced onResize handler in FluentTaskView - Add responsive tab text hiding for narrow views - Remove unnecessary console warnings in ContentComponent - Fix CHANGELOG typo (Shinny → Features)
This commit is contained in:
parent
f694663991
commit
d5f27b7205
9 changed files with 152 additions and 72 deletions
|
|
@ -16,7 +16,7 @@ All notable changes to this project will be documented in this file.
|
|||
|
||||
## [9.9.0](https://github.com/Quorafind/Obsidian-Task-Genius/compare/9.8.14...9.9.0) (2025-10-21)
|
||||
|
||||
### Shinny new things
|
||||
### Features
|
||||
|
||||
- **Fluent Interface**: Brand new modern interface mode
|
||||
|
||||
|
|
@ -607,4 +607,4 @@ All notable changes to this project will be documented in this file.
|
|||
|
||||
### Tests
|
||||
|
||||
* improve test reliability and fix flaky date tests ([d66a13a](https://github.com/Quorafind/Obsidian-Task-Progress-Bar/commit/d66a13a5f41a5ea74d22c7b9215087aef80b5b07))
|
||||
* improve test reliability and fix flaky date tests ([d66a13a](https://github.com/Quorafind/Obsidian-Task-Progress-Bar/commit/d66a13a5f41a5ea74d22c7b9215087aef80b5b07))
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import { t } from "@/translations/helper";
|
|||
import { ViewConfigModal } from "@/components/features/task/view/modals/ViewConfigModal";
|
||||
import { TASK_SPECIFIC_VIEW_TYPE } from "@/pages/TaskSpecificView";
|
||||
import { ViewConfig, ViewFilterRule } from "@/common/setting-definition";
|
||||
import { Events, on } from "@/dataflow/events/Events";
|
||||
|
||||
export class FluentSidebar extends Component {
|
||||
private containerEl: HTMLElement;
|
||||
|
|
@ -74,8 +75,7 @@ export class FluentSidebar extends Component {
|
|||
const manager = this.plugin.workspaceManager;
|
||||
if (!manager) return true;
|
||||
const workspaceId =
|
||||
this.currentWorkspaceId ||
|
||||
manager.getActiveWorkspace()?.id;
|
||||
this.currentWorkspaceId || manager.getActiveWorkspace()?.id;
|
||||
return !manager.isViewHidden(viewId, workspaceId);
|
||||
}
|
||||
|
||||
|
|
@ -168,10 +168,7 @@ export class FluentSidebar extends Component {
|
|||
|
||||
this.registerDomEvent(treeToggleBtn, "click", () => {
|
||||
this.isTreeView = !this.isTreeView;
|
||||
setIcon(
|
||||
treeToggleBtn,
|
||||
this.isTreeView ? "git-branch" : "list",
|
||||
);
|
||||
setIcon(treeToggleBtn, this.isTreeView ? "git-branch" : "list");
|
||||
// Save preference
|
||||
this.plugin.app.saveLocalStorage(
|
||||
"task-genius-project-view-mode",
|
||||
|
|
@ -246,20 +243,20 @@ export class FluentSidebar extends Component {
|
|||
this.primaryItems
|
||||
.filter((item) => this.isViewVisible(item.id))
|
||||
.forEach((item) => {
|
||||
const btn = this.railEl!.createDiv({
|
||||
cls: "fluent-rail-btn",
|
||||
attr: { "aria-label": item.label, "data-view-id": item.id },
|
||||
const btn = this.railEl!.createDiv({
|
||||
cls: "fluent-rail-btn",
|
||||
attr: { "aria-label": item.label, "data-view-id": item.id },
|
||||
});
|
||||
setIcon(btn, item.icon);
|
||||
this.registerDomEvent(btn, "click", () => {
|
||||
this.setActiveItem(item.id);
|
||||
this.onNavigate(item.id);
|
||||
});
|
||||
// Add context menu handler for rail button
|
||||
this.registerDomEvent(btn, "contextmenu", (e) => {
|
||||
this.showViewContextMenu(e as MouseEvent, item.id);
|
||||
});
|
||||
});
|
||||
setIcon(btn, item.icon);
|
||||
this.registerDomEvent(btn, "click", () => {
|
||||
this.setActiveItem(item.id);
|
||||
this.onNavigate(item.id);
|
||||
});
|
||||
// Add context menu handler for rail button
|
||||
this.registerDomEvent(btn, "contextmenu", (e) => {
|
||||
this.showViewContextMenu(e as MouseEvent, item.id);
|
||||
});
|
||||
});
|
||||
|
||||
// Other view icons with overflow menu when > 5
|
||||
if (
|
||||
|
|
|
|||
|
|
@ -230,6 +230,27 @@ export class FluentLayoutManager extends Component {
|
|||
.setTooltip(t("Toggle Sidebar"))
|
||||
.setClass("clickable-icon")
|
||||
.onClick(() => this.toggleSidebar());
|
||||
|
||||
this.registerEvent(
|
||||
this.plugin.app.workspace.on(
|
||||
"task-genius:leaf-width-updated",
|
||||
(width: number) => {
|
||||
if (
|
||||
width <= 600 &&
|
||||
width !== 0 &&
|
||||
!this.isSidebarCollapsed
|
||||
) {
|
||||
this.toggleSidebar();
|
||||
} else if (
|
||||
width > 600 &&
|
||||
width !== 0 &&
|
||||
this.isSidebarCollapsed
|
||||
) {
|
||||
this.toggleSidebar();
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -376,9 +376,6 @@ export class ContentComponent extends Component {
|
|||
private refreshTaskList() {
|
||||
// Defer rendering if container is not visible yet (e.g., view hidden during init)
|
||||
if (!this.isContainerVisible()) {
|
||||
console.warn(
|
||||
"ContentComponent: Cannot render: Container not visible. Queuing refresh...",
|
||||
);
|
||||
this.pendingForceRefresh = true;
|
||||
if (!this.pendingVisibilityRetry) {
|
||||
this.pendingVisibilityRetry = true;
|
||||
|
|
@ -398,9 +395,6 @@ export class ContentComponent extends Component {
|
|||
} else {
|
||||
this.pendingVisibilityRetry = false;
|
||||
this.visibilityRetryCount = 0;
|
||||
console.warn(
|
||||
"ContentComponent: Container still not visible after retries; will wait for next trigger.",
|
||||
);
|
||||
}
|
||||
};
|
||||
tryAgain();
|
||||
|
|
@ -585,7 +579,7 @@ export class ContentComponent extends Component {
|
|||
this.currentViewId, // Pass currentViewId
|
||||
this.app,
|
||||
this.plugin,
|
||||
this.params.selectionManager // Pass selection manager
|
||||
this.params.selectionManager, // Pass selection manager
|
||||
);
|
||||
|
||||
// Attach event handlers
|
||||
|
|
@ -646,7 +640,7 @@ export class ContentComponent extends Component {
|
|||
childTasks,
|
||||
taskMap,
|
||||
this.plugin,
|
||||
this.params.selectionManager // Pass selection manager
|
||||
this.params.selectionManager, // Pass selection manager
|
||||
);
|
||||
|
||||
// Attach event handlers
|
||||
|
|
@ -827,7 +821,7 @@ export class ContentComponent extends Component {
|
|||
this.currentViewId,
|
||||
this.app,
|
||||
this.plugin,
|
||||
this.params.selectionManager // Pass selection manager
|
||||
this.params.selectionManager, // Pass selection manager
|
||||
);
|
||||
// Attach events
|
||||
taskComponent.onTaskSelected = this.selectTask.bind(this);
|
||||
|
|
@ -941,7 +935,7 @@ export class ContentComponent extends Component {
|
|||
childTasks,
|
||||
taskMap,
|
||||
this.plugin,
|
||||
this.params.selectionManager // Pass selection manager
|
||||
this.params.selectionManager, // Pass selection manager
|
||||
);
|
||||
newRoot.onTaskSelected = this.selectTask.bind(this);
|
||||
newRoot.onTaskCompleted = (t) => {
|
||||
|
|
|
|||
|
|
@ -2,22 +2,22 @@ import type { App, EventRef } from "obsidian";
|
|||
|
||||
// Keep names simple and consistent
|
||||
export const Events = {
|
||||
CACHE_READY: "task-genius:cache-ready",
|
||||
TASK_CACHE_UPDATED: "task-genius:task-cache-updated",
|
||||
FILE_UPDATED: "task-genius:file-updated",
|
||||
PROJECT_DATA_UPDATED: "task-genius:project-data-updated",
|
||||
SETTINGS_CHANGED: "task-genius:settings-changed",
|
||||
TASK_COMPLETED: "task-genius:task-completed",
|
||||
TASK_ADDED: "task-genius:task-added",
|
||||
TASK_UPDATED: "task-genius:task-updated",
|
||||
TASK_DELETED: "task-genius:task-deleted",
|
||||
WRITE_OPERATION_START: "task-genius:write-operation-start",
|
||||
WRITE_OPERATION_COMPLETE: "task-genius:write-operation-complete",
|
||||
ICS_EVENTS_UPDATED: "task-genius:ics-events-updated",
|
||||
FILE_TASK_UPDATED: "task-genius:file-task-updated",
|
||||
FILE_TASK_REMOVED: "task-genius:file-task-removed",
|
||||
BATCH_OPERATION_START: "task-genius:batch-operation-start",
|
||||
BATCH_OPERATION_COMPLETE: "task-genius:batch-operation-complete",
|
||||
CACHE_READY: "task-genius:cache-ready",
|
||||
TASK_CACHE_UPDATED: "task-genius:task-cache-updated",
|
||||
FILE_UPDATED: "task-genius:file-updated",
|
||||
PROJECT_DATA_UPDATED: "task-genius:project-data-updated",
|
||||
SETTINGS_CHANGED: "task-genius:settings-changed",
|
||||
TASK_COMPLETED: "task-genius:task-completed",
|
||||
TASK_ADDED: "task-genius:task-added",
|
||||
TASK_UPDATED: "task-genius:task-updated",
|
||||
TASK_DELETED: "task-genius:task-deleted",
|
||||
WRITE_OPERATION_START: "task-genius:write-operation-start",
|
||||
WRITE_OPERATION_COMPLETE: "task-genius:write-operation-complete",
|
||||
ICS_EVENTS_UPDATED: "task-genius:ics-events-updated",
|
||||
FILE_TASK_UPDATED: "task-genius:file-task-updated",
|
||||
FILE_TASK_REMOVED: "task-genius:file-task-removed",
|
||||
BATCH_OPERATION_START: "task-genius:batch-operation-start",
|
||||
BATCH_OPERATION_COMPLETE: "task-genius:batch-operation-complete",
|
||||
} as const;
|
||||
|
||||
// Batch operation payload types
|
||||
|
|
@ -34,31 +34,38 @@ export type SeqClock = { next(): number };
|
|||
|
||||
let _seq = 0;
|
||||
export const Seq: SeqClock = {
|
||||
next() {
|
||||
_seq = (_seq + 1) >>> 0;
|
||||
return _seq;
|
||||
},
|
||||
next() {
|
||||
_seq = (_seq + 1) >>> 0;
|
||||
return _seq;
|
||||
},
|
||||
};
|
||||
|
||||
// Emit helpers (payload kept as any to maintain compatibility with existing trigger signatures)
|
||||
export function emit(app: App, name: string, payload?: any): void {
|
||||
// @ts-expect-error keep compatibility with existing Obsidian typing overloads
|
||||
app.workspace.trigger(name, payload);
|
||||
// @ts-expect-error keep compatibility with existing Obsidian typing overloads
|
||||
app.workspace.trigger(name, payload);
|
||||
}
|
||||
|
||||
export function on(app: App, name: string, handler: (...args: any[]) => void): EventRef {
|
||||
// Consumers should use component.registerEvent(on(...)) to auto-unsub
|
||||
return app.workspace.on(name as any, handler) as EventRef;
|
||||
export function on(
|
||||
app: App,
|
||||
name: string,
|
||||
handler: (...args: any[]) => void,
|
||||
): EventRef {
|
||||
// Consumers should use component.registerEvent(on(...)) to auto-unsub
|
||||
return app.workspace.on(name as any, handler) as EventRef;
|
||||
}
|
||||
|
||||
// Convenience
|
||||
export const onTaskCacheUpdated = (app: App, handler: (payload: any) => void) =>
|
||||
on(app, Events.TASK_CACHE_UPDATED, handler);
|
||||
on(app, Events.TASK_CACHE_UPDATED, handler);
|
||||
export const emitTaskCacheUpdated = (app: App, payload: any) =>
|
||||
emit(app, Events.TASK_CACHE_UPDATED, payload);
|
||||
|
||||
export const emitBatchOperationStart = (app: App, payload: BatchOperationStartPayload) =>
|
||||
emit(app, Events.BATCH_OPERATION_START, payload);
|
||||
export const emitBatchOperationComplete = (app: App, payload: BatchOperationCompletePayload) =>
|
||||
emit(app, Events.BATCH_OPERATION_COMPLETE, payload);
|
||||
emit(app, Events.TASK_CACHE_UPDATED, payload);
|
||||
|
||||
export const emitBatchOperationStart = (
|
||||
app: App,
|
||||
payload: BatchOperationStartPayload,
|
||||
) => emit(app, Events.BATCH_OPERATION_START, payload);
|
||||
export const emitBatchOperationComplete = (
|
||||
app: App,
|
||||
payload: BatchOperationCompletePayload,
|
||||
) => emit(app, Events.BATCH_OPERATION_COMPLETE, payload);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ItemView, Scope, WorkspaceLeaf } from "obsidian";
|
||||
import { debounce, ItemView, Scope, WorkspaceLeaf } from "obsidian";
|
||||
import TaskProgressBarPlugin from "@/index";
|
||||
import { Task } from "@/types/task";
|
||||
import "@/styles/fluent/fluent-main.css";
|
||||
|
|
@ -137,6 +137,17 @@ export class FluentTaskView extends ItemView {
|
|||
return !!this.plugin.settings.fluentView?.useWorkspaceSideLeaves;
|
||||
}
|
||||
|
||||
updateWorkspaceLeafWidth = debounce(() => {
|
||||
this.app.workspace.trigger(
|
||||
"task-genius:leaf-width-updated",
|
||||
this.leaf.width,
|
||||
);
|
||||
}, 200);
|
||||
|
||||
onResize(): void {
|
||||
this.updateWorkspaceLeafWidth();
|
||||
}
|
||||
|
||||
private ensureViewModeForView(viewId: string): ViewMode {
|
||||
const availableModes =
|
||||
this.componentManager?.getAvailableModesForView(viewId) ?? [];
|
||||
|
|
@ -480,21 +491,28 @@ export class FluentTaskView extends ItemView {
|
|||
// When navigating to projects view directly, clear project selection
|
||||
// This enables the full project overview mode
|
||||
if (viewId === "projects") {
|
||||
console.log("[TG] Navigating to projects overview - clearing project selection");
|
||||
console.log(
|
||||
"[TG] Navigating to projects overview - clearing project selection",
|
||||
);
|
||||
this.viewState.selectedProject = undefined;
|
||||
|
||||
// Clear project filter from filter state
|
||||
try {
|
||||
if (this.liveFilterState) {
|
||||
const nextState = { ...this.liveFilterState };
|
||||
nextState.filterGroups = (nextState.filterGroups || [])
|
||||
nextState.filterGroups = (
|
||||
nextState.filterGroups || []
|
||||
)
|
||||
.map((g: any) => ({
|
||||
...g,
|
||||
filters: (g.filters || []).filter(
|
||||
(f: any) => f.property !== "project",
|
||||
),
|
||||
}))
|
||||
.filter((g: any) => g.filters && g.filters.length > 0);
|
||||
.filter(
|
||||
(g: any) =>
|
||||
g.filters && g.filters.length > 0,
|
||||
);
|
||||
|
||||
this.liveFilterState = nextState as any;
|
||||
this.currentFilterState = nextState as any;
|
||||
|
|
@ -657,6 +675,9 @@ export class FluentTaskView extends ItemView {
|
|||
this.selectionManager.clearSelection();
|
||||
});
|
||||
|
||||
// Trigger initial workspace leaf width update
|
||||
this.updateWorkspaceLeafWidth();
|
||||
|
||||
console.log("[TG] Managers initialized");
|
||||
}
|
||||
|
||||
|
|
@ -856,7 +877,7 @@ export class FluentTaskView extends ItemView {
|
|||
this.workspaceStateManager.captureFilterStateSnapshot();
|
||||
if (snapshot) {
|
||||
await this.workspaceStateManager.saveFilterStateImmediately(
|
||||
snapshot
|
||||
snapshot,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -89,7 +89,9 @@
|
|||
width: var(--size-4-9);
|
||||
flex-shrink: 0;
|
||||
|
||||
transition: width 0.3s ease-in-out, flex-shrink 0.3s ease-in-out;
|
||||
transition:
|
||||
width 0.3s ease-in-out,
|
||||
flex-shrink 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.task-sidebar.collapsed .nav-item-icon {
|
||||
|
|
@ -409,12 +411,12 @@
|
|||
}
|
||||
|
||||
.mod-left-split
|
||||
.workspace-tab-header-status-container:has(.task-genius-action-btn) {
|
||||
.workspace-tab-header-status-container:has(.task-genius-action-btn) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mod-right-split
|
||||
.workspace-tab-header-status-container:has(.task-genius-action-btn) {
|
||||
.workspace-tab-header-status-container:has(.task-genius-action-btn) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
|
@ -428,6 +430,18 @@
|
|||
justify-content: center;
|
||||
}
|
||||
|
||||
.tg-fluent-error-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
--icon-size: var(--size-4-12);
|
||||
font-size: var(--font-ui-large);
|
||||
gap: var(--size-2-2);
|
||||
}
|
||||
|
||||
.mod-root .task-genius-tab-header {
|
||||
container-type: inline-size !important;
|
||||
}
|
||||
|
|
|
|||
7
src/types/obsidian-ex.d.ts
vendored
7
src/types/obsidian-ex.d.ts
vendored
|
|
@ -378,6 +378,11 @@ declare module "obsidian" {
|
|||
callback: (taskId: string) => void,
|
||||
): EventRef;
|
||||
|
||||
on(
|
||||
event: "task-genius:leaf-width-updated",
|
||||
callback: (width: number) => void,
|
||||
): EventRef;
|
||||
|
||||
on(
|
||||
event: "task-genius:task-cache-updated",
|
||||
callback: (cache: TaskCache) => void,
|
||||
|
|
@ -426,6 +431,8 @@ declare module "obsidian" {
|
|||
|
||||
trigger(event: "task-genius:task-deleted", taskId: string): void;
|
||||
|
||||
trigger(event: "task-genius:leaf-width-updated", width: number): void;
|
||||
|
||||
trigger(
|
||||
event: "task-genius:task-cache-updated",
|
||||
cache: TaskCache,
|
||||
|
|
|
|||
21
styles.css
21
styles.css
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue