mirror of
https://github.com/taskgenius/taskgenius-plugin.git
synced 2026-07-22 06:40:25 +00:00
feat(fluent): implement workspace management with persistent filter states
- Add WorkspaceManager for handling workspace operations - Implement filter state persistence per workspace and view - Add workspace settings UI in settings page with create/rename/delete - Fix infinite refresh loop by using debounced quiet save - Store fluentFilterState in workspace overrides for each view - Add workspace events system for UI synchronization - Support workspace switching with automatic state restoration
This commit is contained in:
parent
9f008db1a0
commit
2b7179ed9e
13 changed files with 1836 additions and 193 deletions
|
|
@ -825,6 +825,9 @@ export interface TaskProgressBarSettings {
|
|||
|
||||
// FileSource Settings
|
||||
fileSource: FileSourceConfiguration;
|
||||
|
||||
// Workspace Settings
|
||||
workspaces?: import("../experimental/v2/types/workspace").WorkspacesConfig;
|
||||
}
|
||||
|
||||
/** Define the default settings */
|
||||
|
|
|
|||
|
|
@ -17,7 +17,9 @@ import "./styles/v2-enhanced.css";
|
|||
import "./styles/v2-content-header.css";
|
||||
import "@/styles/v2-project-popover.css";
|
||||
import { TopNavigation, ViewMode } from "./components/V2TopNavigation";
|
||||
import { Workspace, V2ViewState } from "./types";
|
||||
import { V2ViewState } from "./types";
|
||||
import { onWorkspaceSwitched, onWorkspaceOverridesSaved } from "./events/ui-event";
|
||||
import { Events, on } from "../../dataflow/events/Events";
|
||||
import { TaskListItemComponent } from "../../components/features/task/view/listItem";
|
||||
import { TaskTreeItemComponent } from "../../components/features/task/view/treeItem";
|
||||
import {
|
||||
|
|
@ -118,13 +120,14 @@ export class TaskViewV2 extends ItemView {
|
|||
|
||||
// State management
|
||||
private viewState: V2ViewState = {
|
||||
currentWorkspace: "default",
|
||||
currentWorkspace: "",
|
||||
viewMode: "list", // Default should be list, not calendar
|
||||
searchQuery: "",
|
||||
filters: {},
|
||||
};
|
||||
|
||||
private workspaces: Workspace[] = [];
|
||||
private workspaceId: string = "";
|
||||
private isSavingFilterState: boolean = false;
|
||||
|
||||
private tasks: Task[] = [];
|
||||
private filteredTasks: Task[] = [];
|
||||
|
|
@ -140,8 +143,9 @@ export class TaskViewV2 extends ItemView {
|
|||
this.plugin = plugin;
|
||||
this.tasks = this.plugin.preloadedTasks || [];
|
||||
|
||||
// Load workspaces from settings or localStorage
|
||||
this.initializeDefaultWorkspaces();
|
||||
// Initialize workspace ID
|
||||
this.workspaceId = plugin.workspaceManager?.getActiveWorkspace().id || "";
|
||||
this.viewState.currentWorkspace = this.workspaceId;
|
||||
}
|
||||
|
||||
getViewType(): string {
|
||||
|
|
@ -149,7 +153,7 @@ export class TaskViewV2 extends ItemView {
|
|||
}
|
||||
|
||||
getDisplayText(): string {
|
||||
return "Task Genius V2";
|
||||
return t("Task Genius V2");
|
||||
}
|
||||
|
||||
getIcon(): string {
|
||||
|
|
@ -161,6 +165,44 @@ export class TaskViewV2 extends ItemView {
|
|||
this.contentEl.empty();
|
||||
this.contentEl.addClass("task-genius-v2-view");
|
||||
|
||||
// Subscribe to workspace events
|
||||
if (this.plugin.workspaceManager) {
|
||||
this.registerEvent(
|
||||
onWorkspaceSwitched(this.app, async (payload) => {
|
||||
if (payload.workspaceId !== this.workspaceId) {
|
||||
this.workspaceId = payload.workspaceId;
|
||||
this.viewState.currentWorkspace = payload.workspaceId;
|
||||
await this.applyWorkspaceSettings();
|
||||
this.restoreFilterStateFromWorkspace();
|
||||
await this.loadTasks();
|
||||
this.switchView(this.currentViewId);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
this.registerEvent(
|
||||
onWorkspaceOverridesSaved(this.app, async (payload) => {
|
||||
if (payload.workspaceId === this.workspaceId) {
|
||||
await this.applyWorkspaceSettings();
|
||||
await this.loadTasks();
|
||||
this.switchView(this.currentViewId);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// Skip SETTINGS_CHANGED for v2FilterState changes to avoid loops
|
||||
this.registerEvent(
|
||||
on(this.app, Events.SETTINGS_CHANGED, async () => {
|
||||
// Only reload if not caused by filter state save
|
||||
if (!this.isSavingFilterState) {
|
||||
await this.applyWorkspaceSettings();
|
||||
await this.loadTasks();
|
||||
this.switchView(this.currentViewId);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Initialize default view mode to list
|
||||
console.log("[TG-V2] Initial viewMode:", this.viewState.viewMode);
|
||||
this.viewState.viewMode = "list"; // Force list mode for now
|
||||
|
|
@ -306,19 +348,12 @@ export class TaskViewV2 extends ItemView {
|
|||
}
|
||||
|
||||
private initializeSidebar(containerEl: HTMLElement) {
|
||||
const currentWorkspace =
|
||||
this.workspaces.find(
|
||||
(w) => w.id === this.viewState.currentWorkspace
|
||||
) || this.workspaces[0];
|
||||
|
||||
this.sidebar = new V2Sidebar(
|
||||
containerEl,
|
||||
this.plugin,
|
||||
currentWorkspace,
|
||||
this.workspaces,
|
||||
(viewId) => this.handleNavigate(viewId),
|
||||
(workspace) => this.handleWorkspaceChange(workspace),
|
||||
(projectId) => this.handleProjectSelect(projectId)
|
||||
(projectId) => this.handleProjectSelect(projectId),
|
||||
this.isSidebarCollapsed
|
||||
);
|
||||
// Add sidebar as a child component for proper lifecycle management
|
||||
this.addChild(this.sidebar);
|
||||
|
|
@ -929,18 +964,18 @@ export class TaskViewV2 extends ItemView {
|
|||
// Update content header title based on view
|
||||
if (this.contentTitleEl) {
|
||||
const viewTitles: Record<string, string> = {
|
||||
inbox: "Inbox",
|
||||
today: "Today",
|
||||
upcoming: "Upcoming",
|
||||
flagged: "Flagged",
|
||||
forecast: "Forecast",
|
||||
projects: "Projects",
|
||||
tags: "Tags",
|
||||
review: "Review",
|
||||
habit: "Habit",
|
||||
calendar: "Calendar",
|
||||
kanban: "Kanban",
|
||||
gantt: "Gantt",
|
||||
inbox: t("Inbox"),
|
||||
today: t("Today"),
|
||||
upcoming: t("Upcoming"),
|
||||
flagged: t("Flagged"),
|
||||
forecast: t("Forecast"),
|
||||
projects: t("Projects"),
|
||||
tags: t("Tags"),
|
||||
review: t("Review"),
|
||||
habit: t("Habit"),
|
||||
calendar: t("Calendar"),
|
||||
kanban: t("Kanban"),
|
||||
gantt: t("Gantt"),
|
||||
};
|
||||
this.contentTitleEl.setText(t(viewTitles[viewId] || "Tasks"));
|
||||
}
|
||||
|
|
@ -1727,28 +1762,10 @@ export class TaskViewV2 extends ItemView {
|
|||
|
||||
// Workspace management
|
||||
private saveWorkspaceLayout() {
|
||||
const currentWorkspace = this.workspaces.find(
|
||||
(w) => w.id === this.viewState.currentWorkspace
|
||||
);
|
||||
if (!currentWorkspace) return;
|
||||
// Save filter state which includes view preferences
|
||||
this.saveFilterStateToWorkspace();
|
||||
|
||||
// Update workspace settings
|
||||
currentWorkspace.settings = {
|
||||
...currentWorkspace.settings,
|
||||
viewPreferences: {
|
||||
viewMode: this.viewState.viewMode,
|
||||
searchQuery: this.viewState.searchQuery,
|
||||
filters: this.viewState.filters,
|
||||
},
|
||||
};
|
||||
|
||||
// Save all workspaces to localStorage
|
||||
localStorage.setItem(
|
||||
"task-genius-v2-workspaces",
|
||||
JSON.stringify(this.workspaces)
|
||||
);
|
||||
|
||||
// Save current workspace ID
|
||||
// Save current workspace ID to localStorage for persistence
|
||||
localStorage.setItem(
|
||||
"task-genius-v2-current-workspace",
|
||||
this.viewState.currentWorkspace
|
||||
|
|
@ -1756,48 +1773,26 @@ export class TaskViewV2 extends ItemView {
|
|||
}
|
||||
|
||||
private loadWorkspaceLayout() {
|
||||
// Load workspaces from localStorage
|
||||
const savedWorkspaces = localStorage.getItem(
|
||||
"task-genius-v2-workspaces"
|
||||
);
|
||||
if (savedWorkspaces) {
|
||||
try {
|
||||
this.workspaces = JSON.parse(savedWorkspaces);
|
||||
} catch (error) {
|
||||
console.error("Failed to load workspaces:", error);
|
||||
this.initializeDefaultWorkspaces();
|
||||
}
|
||||
} else {
|
||||
this.initializeDefaultWorkspaces();
|
||||
}
|
||||
|
||||
// Load current workspace
|
||||
// Load current workspace from localStorage
|
||||
const savedCurrentWorkspace = localStorage.getItem(
|
||||
"task-genius-v2-current-workspace"
|
||||
);
|
||||
if (savedCurrentWorkspace) {
|
||||
this.viewState.currentWorkspace = savedCurrentWorkspace;
|
||||
this.workspaceId = savedCurrentWorkspace;
|
||||
}
|
||||
|
||||
// Apply workspace settings
|
||||
const currentWorkspace = this.workspaces.find(
|
||||
(w) => w.id === this.viewState.currentWorkspace
|
||||
);
|
||||
if (currentWorkspace?.settings?.viewPreferences) {
|
||||
const prefs = currentWorkspace.settings.viewPreferences;
|
||||
if (prefs.viewMode) this.viewState.viewMode = prefs.viewMode;
|
||||
if (prefs.searchQuery !== undefined)
|
||||
this.viewState.searchQuery = prefs.searchQuery;
|
||||
if (prefs.filters) this.viewState.filters = prefs.filters;
|
||||
}
|
||||
// Restore filter state which includes view preferences
|
||||
this.restoreFilterStateFromWorkspace();
|
||||
}
|
||||
|
||||
private initializeDefaultWorkspaces() {
|
||||
this.workspaces = [
|
||||
{ id: "default", name: "Default", color: "#7c3aed" },
|
||||
{ id: "personal", name: "Personal", color: "#3b82f6" },
|
||||
{ id: "work", name: "Work", color: "#10b981" },
|
||||
];
|
||||
private async applyWorkspaceSettings() {
|
||||
if (!this.plugin.workspaceManager || !this.workspaceId) return;
|
||||
|
||||
const settings = this.plugin.workspaceManager.getEffectiveSettings(this.workspaceId);
|
||||
|
||||
// Workspace settings are now restored via restoreFilterStateFromWorkspace
|
||||
// This method is kept for future workspace-specific settings that are not filter-related
|
||||
}
|
||||
|
||||
private switchWorkspace(workspaceId: string) {
|
||||
|
|
@ -1806,16 +1801,10 @@ export class TaskViewV2 extends ItemView {
|
|||
|
||||
// Update workspace ID
|
||||
this.viewState.currentWorkspace = workspaceId;
|
||||
this.workspaceId = workspaceId;
|
||||
|
||||
// Load new workspace settings
|
||||
const newWorkspace = this.workspaces.find((w) => w.id === workspaceId);
|
||||
if (newWorkspace?.settings?.viewPreferences) {
|
||||
const prefs = newWorkspace.settings.viewPreferences;
|
||||
if (prefs.viewMode) this.viewState.viewMode = prefs.viewMode;
|
||||
if (prefs.searchQuery !== undefined)
|
||||
this.viewState.searchQuery = prefs.searchQuery;
|
||||
if (prefs.filters) this.viewState.filters = prefs.filters;
|
||||
}
|
||||
// Restore filter state from new workspace
|
||||
this.restoreFilterStateFromWorkspace();
|
||||
|
||||
// Apply filters and update view
|
||||
this.applyFilters();
|
||||
|
|
@ -1824,7 +1813,7 @@ export class TaskViewV2 extends ItemView {
|
|||
// Update UI to reflect workspace change
|
||||
if (this.sidebar) {
|
||||
// Update sidebar workspace selection if needed
|
||||
const workspace = this.workspaces.find((w) => w.id === workspaceId);
|
||||
const workspace = this.plugin.workspaceManager?.getWorkspace(workspaceId);
|
||||
if (workspace) {
|
||||
this.sidebar.updateWorkspace(workspace);
|
||||
}
|
||||
|
|
@ -1980,14 +1969,26 @@ export class TaskViewV2 extends ItemView {
|
|||
this.updateActionButtons();
|
||||
}
|
||||
|
||||
private handleWorkspaceChange(workspace: Workspace) {
|
||||
this.viewState.currentWorkspace = workspace.id;
|
||||
this.sidebar?.updateWorkspace(workspace);
|
||||
this.loadTasks().then(() => {
|
||||
this.switchView(this.currentViewId);
|
||||
this.topNavigation?.refresh();
|
||||
});
|
||||
new Notice(`Switched to ${workspace.name} workspace`);
|
||||
private async handleWorkspaceChange(workspaceId: string) {
|
||||
if (!this.plugin.workspaceManager) return;
|
||||
|
||||
// Save to workspace manager
|
||||
await this.plugin.workspaceManager.setActiveWorkspace(workspaceId);
|
||||
|
||||
// Update local state
|
||||
this.workspaceId = workspaceId;
|
||||
this.viewState.currentWorkspace = workspaceId;
|
||||
|
||||
// Update sidebar
|
||||
this.sidebar?.updateWorkspace(workspaceId);
|
||||
|
||||
// Apply workspace settings and reload
|
||||
await this.applyWorkspaceSettings();
|
||||
await this.loadTasks();
|
||||
|
||||
// Refresh current view
|
||||
this.switchView(this.currentViewId);
|
||||
this.topNavigation?.refresh();
|
||||
}
|
||||
|
||||
private handleProjectSelect(projectId: string) {
|
||||
|
|
@ -2465,9 +2466,56 @@ export class TaskViewV2 extends ItemView {
|
|||
this.topNavigation?.refresh();
|
||||
}
|
||||
|
||||
// Save filter state to workspace - debounced to avoid infinite loops
|
||||
private saveFilterStateToWorkspace = debounce(() => {
|
||||
if (!this.plugin.workspaceManager || !this.workspaceId) return;
|
||||
|
||||
const effectiveSettings = this.plugin.workspaceManager.getEffectiveSettings(this.workspaceId);
|
||||
|
||||
// Save current filter state
|
||||
if (!effectiveSettings.v2FilterState) {
|
||||
effectiveSettings.v2FilterState = {};
|
||||
}
|
||||
|
||||
effectiveSettings.v2FilterState[this.currentViewId] = {
|
||||
filters: this.viewState.filters,
|
||||
searchQuery: this.viewState.searchQuery,
|
||||
selectedProject: this.viewState.selectedProject,
|
||||
advancedFilter: this.currentFilterState,
|
||||
viewMode: this.viewState.viewMode
|
||||
};
|
||||
|
||||
// Use saveOverridesQuietly to avoid triggering SETTINGS_CHANGED event
|
||||
this.plugin.workspaceManager.saveOverridesQuietly(this.workspaceId, effectiveSettings);
|
||||
}, 500, true)
|
||||
|
||||
// Restore filter state from workspace
|
||||
private restoreFilterStateFromWorkspace() {
|
||||
if (!this.plugin.workspaceManager || !this.workspaceId) return;
|
||||
|
||||
const effectiveSettings = this.plugin.workspaceManager.getEffectiveSettings(this.workspaceId);
|
||||
|
||||
if (effectiveSettings.v2FilterState && effectiveSettings.v2FilterState[this.currentViewId]) {
|
||||
const savedState = effectiveSettings.v2FilterState[this.currentViewId];
|
||||
|
||||
// Restore filter state
|
||||
this.viewState.filters = savedState.filters || {};
|
||||
this.viewState.searchQuery = savedState.searchQuery || "";
|
||||
this.viewState.selectedProject = savedState.selectedProject || null;
|
||||
this.currentFilterState = savedState.advancedFilter || null;
|
||||
this.viewState.viewMode = savedState.viewMode || "list";
|
||||
|
||||
// Update UI elements
|
||||
if (this.filterInputEl) {
|
||||
this.filterInputEl.value = this.viewState.searchQuery || "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async onClose() {
|
||||
// Save workspace layout
|
||||
// Save workspace layout and filter state
|
||||
this.saveWorkspaceLayout();
|
||||
this.saveFilterStateToWorkspace();
|
||||
|
||||
// Clean up components
|
||||
if (this.kanbanComponent) {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import TaskProgressBarPlugin from "../../../index";
|
|||
import { Task } from "../../../types/task";
|
||||
import { ProjectPopover, ProjectModal } from "./ProjectPopover";
|
||||
import type { CustomProject } from "../../../common/setting-definition";
|
||||
import { t } from "@/translations/helper";
|
||||
|
||||
interface Project {
|
||||
id: string;
|
||||
|
|
@ -221,7 +222,7 @@ export class ProjectList extends Component {
|
|||
|
||||
addProjectBtn.createSpan({
|
||||
cls: "v2-project-name",
|
||||
text: "Add Project",
|
||||
text: t("Add Project"),
|
||||
});
|
||||
|
||||
this.registerDomEvent(addProjectBtn, "click", () => {
|
||||
|
|
@ -374,25 +375,25 @@ export class ProjectList extends Component {
|
|||
value: SortOption;
|
||||
icon: string;
|
||||
}[] = [
|
||||
{ label: "Name (A-Z)", value: "name-asc", icon: "arrow-up-a-z" },
|
||||
{ label: "Name (Z-A)", value: "name-desc", icon: "arrow-down-a-z" },
|
||||
{ label: t("Name (A-Z)"), value: "name-asc", icon: "arrow-up-a-z" },
|
||||
{ label: t("Name (Z-A)"), value: "name-desc", icon: "arrow-down-a-z" },
|
||||
{
|
||||
label: "Tasks (Low to High)",
|
||||
label: t("Tasks (Low to High)"),
|
||||
value: "tasks-asc",
|
||||
icon: "arrow-up-1-0",
|
||||
},
|
||||
{
|
||||
label: "Tasks (High to Low)",
|
||||
label: t("Tasks (High to Low)"),
|
||||
value: "tasks-desc",
|
||||
icon: "arrow-down-1-0",
|
||||
},
|
||||
{
|
||||
label: "Created (Oldest First)",
|
||||
label: t("Created (Oldest First)"),
|
||||
value: "created-asc",
|
||||
icon: "clock",
|
||||
},
|
||||
{
|
||||
label: "Created (Newest First)",
|
||||
label: t("Created (Newest First)"),
|
||||
value: "created-desc",
|
||||
icon: "history",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { Component, Platform, Modal, App } from "obsidian";
|
|||
import { createPopper, Instance as PopperInstance } from "@popperjs/core";
|
||||
import TaskProgressBarPlugin from "../../../index";
|
||||
import type { CustomProject } from "../../../common/setting-definition";
|
||||
import { t } from "@/translations/helper";
|
||||
|
||||
export class ProjectPopover extends Component {
|
||||
private popoverEl: HTMLElement | null = null;
|
||||
|
|
@ -72,22 +73,22 @@ export class ProjectPopover extends Component {
|
|||
|
||||
// Title
|
||||
const header = content.createDiv({ cls: "v2-popover-header" });
|
||||
header.createEl("h3", { text: "New Project" });
|
||||
header.createEl("h3", { text: t("New Project") });
|
||||
|
||||
// Name input
|
||||
const nameSection = content.createDiv({ cls: "v2-popover-section" });
|
||||
nameSection.createEl("label", { text: "Project Name" });
|
||||
nameSection.createEl("label", { text: t("Project Name") });
|
||||
this.nameInput = nameSection.createEl("input", {
|
||||
cls: "v2-popover-input",
|
||||
attr: {
|
||||
type: "text",
|
||||
placeholder: "Enter project name",
|
||||
placeholder: t("Enter project name"),
|
||||
}
|
||||
});
|
||||
|
||||
// Color picker
|
||||
const colorSection = content.createDiv({ cls: "v2-popover-section" });
|
||||
colorSection.createEl("label", { text: "Choose Color" });
|
||||
colorSection.createEl("label", { text: t("Choose Color") });
|
||||
|
||||
const colorGrid = colorSection.createDiv({ cls: "v2-color-grid" });
|
||||
this.colors.forEach(color => {
|
||||
|
|
@ -111,13 +112,13 @@ export class ProjectPopover extends Component {
|
|||
|
||||
const cancelBtn = actions.createEl("button", {
|
||||
cls: "v2-button v2-button-secondary",
|
||||
text: "Cancel"
|
||||
text: t("Cancel")
|
||||
});
|
||||
this.registerDomEvent(cancelBtn, "click", () => this.close());
|
||||
|
||||
const saveBtn = actions.createEl("button", {
|
||||
cls: "v2-button v2-button-primary",
|
||||
text: "Create"
|
||||
text: t("Create")
|
||||
});
|
||||
this.registerDomEvent(saveBtn, "click", () => this.save());
|
||||
|
||||
|
|
@ -312,22 +313,22 @@ export class ProjectModal extends Modal {
|
|||
this.modalEl.addClass("v2-project-modal");
|
||||
|
||||
// Title
|
||||
contentEl.createEl("h2", { text: "Create New Project" });
|
||||
contentEl.createEl("h2", { text: t("Create New Project") });
|
||||
|
||||
// Name input section
|
||||
const nameSection = contentEl.createDiv({ cls: "v2-modal-section" });
|
||||
nameSection.createEl("label", { text: "Project Name" });
|
||||
nameSection.createEl("label", { text: t("Project Name") });
|
||||
this.nameInput = nameSection.createEl("input", {
|
||||
cls: "v2-modal-input",
|
||||
attr: {
|
||||
type: "text",
|
||||
placeholder: "Enter project name"
|
||||
placeholder: t("Enter project name")
|
||||
}
|
||||
});
|
||||
|
||||
// Color picker section
|
||||
const colorSection = contentEl.createDiv({ cls: "v2-modal-section" });
|
||||
colorSection.createEl("label", { text: "Choose Color" });
|
||||
colorSection.createEl("label", { text: t("Choose Color") });
|
||||
|
||||
const colorGrid = colorSection.createDiv({ cls: "v2-modal-color-grid" });
|
||||
this.colors.forEach(color => {
|
||||
|
|
@ -348,7 +349,7 @@ export class ProjectModal extends Modal {
|
|||
|
||||
// Preview section
|
||||
const previewSection = contentEl.createDiv({ cls: "v2-modal-section" });
|
||||
previewSection.createEl("label", { text: "Preview" });
|
||||
previewSection.createEl("label", { text: t("Preview") });
|
||||
const preview = previewSection.createDiv({ cls: "v2-modal-preview" });
|
||||
const previewItem = preview.createDiv({ cls: "v2-project-item-preview" });
|
||||
const previewColor = previewItem.createDiv({ cls: "v2-project-color" });
|
||||
|
|
@ -357,22 +358,22 @@ export class ProjectModal extends Modal {
|
|||
|
||||
// Update preview on name change
|
||||
this.addEventListener(this.nameInput, "input", () => {
|
||||
previewName.setText(this.nameInput?.value || "Project Name");
|
||||
previewName.setText(this.nameInput?.value || t("Project Name"));
|
||||
});
|
||||
previewName.setText("Project Name");
|
||||
previewName.setText(t("Project Name"));
|
||||
|
||||
// Footer with action buttons
|
||||
const footer = contentEl.createDiv({ cls: "v2-modal-footer" });
|
||||
|
||||
const cancelBtn = footer.createEl("button", {
|
||||
cls: "v2-button v2-button-secondary",
|
||||
text: "Cancel"
|
||||
text: t("Cancel")
|
||||
});
|
||||
this.addEventListener(cancelBtn, "click", () => this.close());
|
||||
|
||||
const createBtn = footer.createEl("button", {
|
||||
cls: "v2-button v2-button-primary",
|
||||
text: "Create Project"
|
||||
text: t("Create Project")
|
||||
});
|
||||
this.addEventListener(createBtn, "click", () => this.save());
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,13 @@
|
|||
import { Component, setIcon, Menu } from "obsidian";
|
||||
import { Component, setIcon, Menu, Notice, Modal } from "obsidian";
|
||||
import { WorkspaceSelector } from "./WorkspaceSelector";
|
||||
import { ProjectList } from "@/experimental/v2/components/ProjectList";
|
||||
import { Workspace, V2NavigationItem } from "@/experimental/v2/types";
|
||||
import { V2NavigationItem } from "@/experimental/v2/types";
|
||||
import { WorkspaceData } from "@/experimental/v2/types/workspace";
|
||||
import {
|
||||
onWorkspaceSwitched,
|
||||
onWorkspaceDeleted,
|
||||
onWorkspaceCreated,
|
||||
} from "@/experimental/v2/events/ui-event";
|
||||
import TaskProgressBarPlugin from "@/index";
|
||||
import { t } from "@/translations/helper";
|
||||
|
||||
|
|
@ -11,6 +17,7 @@ export class V2Sidebar extends Component {
|
|||
private workspaceSelector: WorkspaceSelector;
|
||||
public projectList: ProjectList;
|
||||
private collapsed: boolean = false;
|
||||
private currentWorkspaceId: string;
|
||||
|
||||
private primaryItems: V2NavigationItem[] = [
|
||||
{ id: "inbox", label: t("Inbox"), icon: "inbox", type: "primary" },
|
||||
|
|
@ -49,10 +56,7 @@ export class V2Sidebar extends Component {
|
|||
constructor(
|
||||
containerEl: HTMLElement,
|
||||
plugin: TaskProgressBarPlugin,
|
||||
private currentWorkspace: Workspace,
|
||||
private workspaces: Workspace[],
|
||||
private onNavigate: (viewId: string) => void,
|
||||
private onWorkspaceChange: (workspace: Workspace) => void,
|
||||
private onProjectSelect: (projectId: string) => void,
|
||||
collapsed: boolean = false
|
||||
) {
|
||||
|
|
@ -60,6 +64,8 @@ export class V2Sidebar extends Component {
|
|||
this.containerEl = containerEl;
|
||||
this.plugin = plugin;
|
||||
this.collapsed = collapsed;
|
||||
this.currentWorkspaceId =
|
||||
plugin.workspaceManager?.getActiveWorkspace().id || "";
|
||||
}
|
||||
|
||||
private render() {
|
||||
|
|
@ -78,7 +84,7 @@ export class V2Sidebar extends Component {
|
|||
});
|
||||
setIcon(wsBtn, "layers");
|
||||
wsBtn.addEventListener("click", (e) =>
|
||||
this.showWorkspaceMenu(e as MouseEvent)
|
||||
this.showWorkspaceMenuWithManager(e as MouseEvent)
|
||||
);
|
||||
|
||||
// Primary view icons
|
||||
|
|
@ -152,17 +158,18 @@ export class V2Sidebar extends Component {
|
|||
const header = this.containerEl.createDiv({ cls: "v2-sidebar-header" });
|
||||
|
||||
const workspaceSelectorEl = header.createDiv();
|
||||
this.workspaceSelector = new WorkspaceSelector(
|
||||
workspaceSelectorEl,
|
||||
this.workspaces,
|
||||
this.currentWorkspace,
|
||||
this.onWorkspaceChange
|
||||
);
|
||||
if (this.plugin.workspaceManager) {
|
||||
this.workspaceSelector = new WorkspaceSelector(
|
||||
workspaceSelectorEl,
|
||||
this.plugin,
|
||||
(workspaceId: string) => this.handleWorkspaceChange(workspaceId)
|
||||
);
|
||||
}
|
||||
|
||||
// New Task Button
|
||||
const newTaskBtn = header.createEl("button", {
|
||||
cls: "v2-new-task-btn",
|
||||
text: "New Task",
|
||||
text: t("New Task"),
|
||||
});
|
||||
setIcon(newTaskBtn.createDiv({ cls: "v2-new-task-icon" }), "plus");
|
||||
newTaskBtn.addEventListener("click", () => {
|
||||
|
|
@ -186,10 +193,10 @@ export class V2Sidebar extends Component {
|
|||
cls: "v2-section-header",
|
||||
});
|
||||
|
||||
projectHeader.createSpan({ text: "Projects" });
|
||||
projectHeader.createSpan({ text: t("Projects") });
|
||||
const sortProjectBtn = projectHeader.createDiv({
|
||||
cls: "v2-sort-project-btn",
|
||||
attr: { "aria-label": "Sort projects" }
|
||||
attr: { "aria-label": t("Sort projects") },
|
||||
});
|
||||
setIcon(sortProjectBtn, "arrow-up-down");
|
||||
|
||||
|
|
@ -220,7 +227,7 @@ export class V2Sidebar extends Component {
|
|||
);
|
||||
const remainingOther: V2NavigationItem[] =
|
||||
allOtherItems.slice(visibleCount);
|
||||
otherHeader.createSpan({ text: "Other Views" });
|
||||
otherHeader.createSpan({ text: t("Other Views") });
|
||||
if (remainingOther.length > 0) {
|
||||
const moreBtn = otherHeader.createDiv({
|
||||
cls: "v2-section-action",
|
||||
|
|
@ -267,6 +274,28 @@ export class V2Sidebar extends Component {
|
|||
|
||||
onload() {
|
||||
this.render();
|
||||
|
||||
// Subscribe to workspace events
|
||||
if (this.plugin.workspaceManager) {
|
||||
this.registerEvent(
|
||||
onWorkspaceSwitched(this.plugin.app, (payload) => {
|
||||
this.currentWorkspaceId = payload.workspaceId;
|
||||
this.render();
|
||||
})
|
||||
);
|
||||
|
||||
this.registerEvent(
|
||||
onWorkspaceDeleted(this.plugin.app, () => {
|
||||
this.render();
|
||||
})
|
||||
);
|
||||
|
||||
this.registerEvent(
|
||||
onWorkspaceCreated(this.plugin.app, () => {
|
||||
this.render();
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
onunload() {
|
||||
|
|
@ -279,23 +308,116 @@ export class V2Sidebar extends Component {
|
|||
this.render();
|
||||
}
|
||||
|
||||
private showWorkspaceMenu(event: MouseEvent) {
|
||||
private async handleWorkspaceChange(workspaceId: string) {
|
||||
if (this.plugin.workspaceManager) {
|
||||
await this.plugin.workspaceManager.setActiveWorkspace(workspaceId);
|
||||
this.currentWorkspaceId = workspaceId;
|
||||
}
|
||||
}
|
||||
|
||||
private showWorkspaceMenuWithManager(event: MouseEvent) {
|
||||
if (!this.plugin.workspaceManager) return;
|
||||
|
||||
const menu = new Menu();
|
||||
this.workspaces.forEach((w) => {
|
||||
const workspaces = this.plugin.workspaceManager.getAllWorkspaces();
|
||||
const currentWorkspace =
|
||||
this.plugin.workspaceManager.getActiveWorkspace();
|
||||
|
||||
workspaces.forEach((w) => {
|
||||
menu.addItem((item) => {
|
||||
item.setTitle(w.name)
|
||||
const isDefault =
|
||||
this.plugin.workspaceManager?.isDefaultWorkspace(w.id);
|
||||
const title = isDefault ? `${w.name} 🔒` : w.name;
|
||||
|
||||
item.setTitle(title)
|
||||
.setIcon("layers")
|
||||
.onClick(() => {
|
||||
this.currentWorkspace = w;
|
||||
this.onWorkspaceChange(w);
|
||||
this.render();
|
||||
.onClick(async () => {
|
||||
await this.handleWorkspaceChange(w.id);
|
||||
});
|
||||
if (w.id === this.currentWorkspace.id) item.setChecked(true);
|
||||
if (w.id === currentWorkspace.id) item.setChecked(true);
|
||||
});
|
||||
});
|
||||
|
||||
menu.addSeparator();
|
||||
menu.addItem((item) => {
|
||||
item.setTitle(t("Create Workspace"))
|
||||
.setIcon("plus")
|
||||
.onClick(() => this.showCreateWorkspaceDialog());
|
||||
});
|
||||
|
||||
menu.showAtMouseEvent(event);
|
||||
}
|
||||
|
||||
private showCreateWorkspaceDialog() {
|
||||
class CreateWorkspaceModal extends Modal {
|
||||
private nameInput: HTMLInputElement;
|
||||
|
||||
constructor(
|
||||
private plugin: TaskProgressBarPlugin,
|
||||
private onCreated: () => void
|
||||
) {
|
||||
super(plugin.app);
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
const { contentEl } = this;
|
||||
contentEl.createEl("h2", { text: t("Create New Workspace") });
|
||||
|
||||
const inputContainer = contentEl.createDiv();
|
||||
inputContainer.createEl("label", {
|
||||
text: t("Workspace Name:"),
|
||||
});
|
||||
this.nameInput = inputContainer.createEl("input", {
|
||||
type: "text",
|
||||
placeholder: t("Enter workspace name..."),
|
||||
});
|
||||
|
||||
const buttonContainer = contentEl.createDiv({
|
||||
cls: "modal-button-container",
|
||||
});
|
||||
const createButton = buttonContainer.createEl("button", {
|
||||
text: t("Create"),
|
||||
});
|
||||
const cancelButton = buttonContainer.createEl("button", {
|
||||
text: t("Cancel"),
|
||||
});
|
||||
|
||||
createButton.addEventListener("click", async () => {
|
||||
const name = this.nameInput.value.trim();
|
||||
if (name && this.plugin.workspaceManager) {
|
||||
await this.plugin.workspaceManager.createWorkspace(
|
||||
name
|
||||
);
|
||||
new Notice(
|
||||
t('Workspace "{{name}}" created', {
|
||||
interpolation: {
|
||||
name: name,
|
||||
},
|
||||
})
|
||||
);
|
||||
this.onCreated();
|
||||
this.close();
|
||||
} else {
|
||||
new Notice(t("Please enter a workspace name"));
|
||||
}
|
||||
});
|
||||
|
||||
cancelButton.addEventListener("click", () => {
|
||||
this.close();
|
||||
});
|
||||
|
||||
this.nameInput.focus();
|
||||
}
|
||||
|
||||
onClose() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
}
|
||||
|
||||
new CreateWorkspaceModal(this.plugin, () => this.render()).open();
|
||||
}
|
||||
|
||||
private showProjectMenu(event: MouseEvent) {
|
||||
// Try to use existing project list data; if missing, build a temporary one
|
||||
let projects: any[] = [];
|
||||
|
|
@ -381,9 +503,13 @@ export class V2Sidebar extends Component {
|
|||
activeEls.forEach((el) => el.addClass("is-active"));
|
||||
}
|
||||
|
||||
public updateWorkspace(workspace: Workspace) {
|
||||
this.currentWorkspace = workspace;
|
||||
this.workspaceSelector?.setWorkspace(workspace);
|
||||
public updateWorkspace(workspaceOrId: string | WorkspaceData) {
|
||||
const workspaceId =
|
||||
typeof workspaceOrId === "string"
|
||||
? workspaceOrId
|
||||
: workspaceOrId.id;
|
||||
this.currentWorkspaceId = workspaceId;
|
||||
this.workspaceSelector?.setWorkspace(workspaceId);
|
||||
this.projectList?.refresh();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -183,10 +183,10 @@ export class TopNavigation {
|
|||
|
||||
overdueTasks.forEach((task) => {
|
||||
menu.addItem((item) => {
|
||||
item.setTitle(task.content || "Untitled task")
|
||||
item.setTitle(task.content || t("Untitled task"))
|
||||
.setIcon("alert-circle")
|
||||
.onClick(() => {
|
||||
new Notice(`Task: ${task.content}`);
|
||||
new Notice(t("Task: {{content}}", { content: task.content || "" }));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,21 +1,23 @@
|
|||
import { Menu, setIcon } from 'obsidian';
|
||||
import { Workspace } from '../types';
|
||||
import { Menu, setIcon, Notice, Modal, Setting } from "obsidian";
|
||||
import type TaskProgressBarPlugin from "@/index";
|
||||
import { WorkspaceData } from "@/experimental/v2/types/workspace";
|
||||
import { t } from "@/translations/helper";
|
||||
|
||||
export class WorkspaceSelector {
|
||||
private containerEl: HTMLElement;
|
||||
private currentWorkspace: Workspace;
|
||||
private workspaces: Workspace[];
|
||||
private onWorkspaceChange: (workspace: Workspace) => void;
|
||||
private plugin: TaskProgressBarPlugin;
|
||||
private currentWorkspaceId: string;
|
||||
private onWorkspaceChange: (workspaceId: string) => void;
|
||||
|
||||
constructor(
|
||||
containerEl: HTMLElement,
|
||||
workspaces: Workspace[],
|
||||
currentWorkspace: Workspace,
|
||||
onWorkspaceChange: (workspace: Workspace) => void
|
||||
plugin: TaskProgressBarPlugin,
|
||||
onWorkspaceChange: (workspaceId: string) => void
|
||||
) {
|
||||
this.containerEl = containerEl;
|
||||
this.workspaces = workspaces;
|
||||
this.currentWorkspace = currentWorkspace;
|
||||
this.plugin = plugin;
|
||||
this.currentWorkspaceId =
|
||||
plugin.workspaceManager?.getActiveWorkspace().id || "";
|
||||
this.onWorkspaceChange = onWorkspaceChange;
|
||||
|
||||
this.render();
|
||||
|
|
@ -23,59 +25,108 @@ export class WorkspaceSelector {
|
|||
|
||||
private render() {
|
||||
this.containerEl.empty();
|
||||
this.containerEl.addClass('workspace-selector');
|
||||
this.containerEl.addClass("workspace-selector");
|
||||
|
||||
if (!this.plugin.workspaceManager) return;
|
||||
|
||||
const currentWorkspace =
|
||||
this.plugin.workspaceManager.getActiveWorkspace();
|
||||
const isDefault = this.plugin.workspaceManager.isDefaultWorkspace(
|
||||
currentWorkspace.id
|
||||
);
|
||||
|
||||
const selectorButton = this.containerEl.createDiv({
|
||||
cls: 'workspace-selector-button',
|
||||
cls: "workspace-selector-button",
|
||||
});
|
||||
|
||||
const workspaceInfo = selectorButton.createDiv({
|
||||
cls: 'workspace-info'
|
||||
cls: "workspace-info",
|
||||
});
|
||||
|
||||
const workspaceIcon = workspaceInfo.createDiv({
|
||||
cls: 'workspace-icon',
|
||||
cls: "workspace-icon",
|
||||
});
|
||||
workspaceIcon.style.backgroundColor = this.currentWorkspace.color;
|
||||
setIcon(workspaceIcon, 'layers');
|
||||
// Use a color scheme for workspaces if needed
|
||||
workspaceIcon.style.backgroundColor =
|
||||
this.getWorkspaceColor(currentWorkspace);
|
||||
setIcon(workspaceIcon, "layers");
|
||||
|
||||
const workspaceDetails = workspaceInfo.createDiv({
|
||||
cls: 'workspace-details'
|
||||
cls: "workspace-details",
|
||||
});
|
||||
workspaceDetails.createDiv({
|
||||
cls: 'workspace-name',
|
||||
text: this.currentWorkspace.name
|
||||
|
||||
const nameContainer = workspaceDetails.createDiv({
|
||||
cls: "workspace-name-container",
|
||||
});
|
||||
|
||||
nameContainer.createSpan({
|
||||
cls: "workspace-name",
|
||||
text: currentWorkspace.name,
|
||||
});
|
||||
|
||||
workspaceDetails.createDiv({
|
||||
cls: 'workspace-label',
|
||||
text: 'Workspace'
|
||||
cls: "workspace-label",
|
||||
text: t("Workspace"),
|
||||
});
|
||||
|
||||
const dropdownIcon = selectorButton.createDiv({
|
||||
cls: 'workspace-dropdown-icon'
|
||||
cls: "workspace-dropdown-icon",
|
||||
});
|
||||
setIcon(dropdownIcon, 'chevron-down');
|
||||
setIcon(dropdownIcon, "chevron-down");
|
||||
|
||||
selectorButton.addEventListener('click', (e) => {
|
||||
selectorButton.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
this.showWorkspaceMenu(e);
|
||||
});
|
||||
}
|
||||
|
||||
private showWorkspaceMenu(event: MouseEvent) {
|
||||
const menu = new Menu();
|
||||
private getWorkspaceColor(workspace: WorkspaceData): string {
|
||||
// Generate a color based on workspace ID or use predefined colors
|
||||
const colors = [
|
||||
"#e74c3c",
|
||||
"#3498db",
|
||||
"#2ecc71",
|
||||
"#f39c12",
|
||||
"#9b59b6",
|
||||
"#1abc9c",
|
||||
"#34495e",
|
||||
"#e67e22",
|
||||
];
|
||||
const index =
|
||||
Math.abs(
|
||||
workspace.id
|
||||
.split("")
|
||||
.reduce((acc, char) => acc + char.charCodeAt(0), 0)
|
||||
) % colors.length;
|
||||
return colors[index];
|
||||
}
|
||||
|
||||
this.workspaces.forEach(workspace => {
|
||||
menu.addItem(item => {
|
||||
item.setTitle(workspace.name)
|
||||
.setIcon('layers')
|
||||
.onClick(() => {
|
||||
this.currentWorkspace = workspace;
|
||||
this.onWorkspaceChange(workspace);
|
||||
private showWorkspaceMenu(event: MouseEvent) {
|
||||
if (!this.plugin.workspaceManager) return;
|
||||
|
||||
const menu = new Menu();
|
||||
const workspaces = this.plugin.workspaceManager.getAllWorkspaces();
|
||||
const currentWorkspace =
|
||||
this.plugin.workspaceManager.getActiveWorkspace();
|
||||
|
||||
// Add workspace items
|
||||
workspaces.forEach((workspace) => {
|
||||
menu.addItem((item) => {
|
||||
const isDefault =
|
||||
this.plugin.workspaceManager?.isDefaultWorkspace(
|
||||
workspace.id
|
||||
);
|
||||
const title = isDefault ? `${workspace.name}` : workspace.name;
|
||||
|
||||
item.setTitle(title)
|
||||
.setIcon("layers")
|
||||
.onClick(async () => {
|
||||
await this.onWorkspaceChange(workspace.id);
|
||||
this.currentWorkspaceId = workspace.id;
|
||||
this.render();
|
||||
});
|
||||
|
||||
if (workspace.id === this.currentWorkspace.id) {
|
||||
if (workspace.id === currentWorkspace.id) {
|
||||
item.setChecked(true);
|
||||
}
|
||||
});
|
||||
|
|
@ -83,19 +134,319 @@ export class WorkspaceSelector {
|
|||
|
||||
menu.addSeparator();
|
||||
|
||||
menu.addItem(item => {
|
||||
item.setTitle('Create Workspace')
|
||||
.setIcon('plus')
|
||||
// Add management options
|
||||
menu.addItem((item) => {
|
||||
item.setTitle(t("Create Workspace"))
|
||||
.setIcon("plus")
|
||||
.onClick(() => {
|
||||
console.log('Create new workspace');
|
||||
this.showCreateWorkspaceDialog();
|
||||
});
|
||||
});
|
||||
|
||||
// Only show rename/delete for non-default workspaces
|
||||
if (
|
||||
!this.plugin.workspaceManager.isDefaultWorkspace(
|
||||
currentWorkspace.id
|
||||
)
|
||||
) {
|
||||
menu.addItem((item) => {
|
||||
item.setTitle(t("Rename Current Workspace"))
|
||||
.setIcon("edit")
|
||||
.onClick(() => {
|
||||
this.showRenameWorkspaceDialog(currentWorkspace);
|
||||
});
|
||||
});
|
||||
|
||||
menu.addItem((item) => {
|
||||
item.setTitle(t("Delete Current Workspace"))
|
||||
.setIcon("trash")
|
||||
.onClick(() => {
|
||||
this.showDeleteWorkspaceDialog(currentWorkspace);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
menu.addSeparator();
|
||||
|
||||
menu.addItem((item) => {
|
||||
item.setTitle(t("Manage Workspaces..."))
|
||||
.setIcon("settings")
|
||||
.onClick(() => {
|
||||
// Open settings to workspace tab
|
||||
// @ts-ignore
|
||||
this.plugin.app.setting.open();
|
||||
// @ts-ignore
|
||||
this.plugin.app.setting.openTabById(
|
||||
"obsidian-task-progress-bar"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
menu.showAtMouseEvent(event);
|
||||
}
|
||||
|
||||
public setWorkspace(workspace: Workspace) {
|
||||
this.currentWorkspace = workspace;
|
||||
private showCreateWorkspaceDialog() {
|
||||
class CreateWorkspaceModal extends Modal {
|
||||
private nameInput: HTMLInputElement;
|
||||
private baseWorkspaceSelect: HTMLSelectElement;
|
||||
private name: string = "";
|
||||
private selectWorkspaceId: string = "";
|
||||
|
||||
constructor(
|
||||
private plugin: TaskProgressBarPlugin,
|
||||
private onCreated: (workspace: WorkspaceData) => void
|
||||
) {
|
||||
super(plugin.app);
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
const { contentEl } = this;
|
||||
contentEl.createEl("h2", { text: t("Create New Workspace") });
|
||||
|
||||
// Name input
|
||||
new Setting(contentEl)
|
||||
.setName(t("Workspace Name"))
|
||||
.setDesc(t("A descriptive name for the workspace"))
|
||||
.addText((text) => {
|
||||
text.setPlaceholder(t("Workspace Name"))
|
||||
.setValue("")
|
||||
.onChange((value) => {
|
||||
this.name = value;
|
||||
});
|
||||
});
|
||||
|
||||
const currentWorkspace =
|
||||
this.plugin.workspaceManager?.getActiveWorkspace();
|
||||
|
||||
// Base workspace selector
|
||||
new Setting(contentEl)
|
||||
.setName(t("Copy Settings From"))
|
||||
.setDesc(t("Copy settings from an existing workspace"))
|
||||
.addDropdown((dropdown) => {
|
||||
dropdown.addOption("", t("Select a workspace..."));
|
||||
if (currentWorkspace) {
|
||||
dropdown.addOption(
|
||||
currentWorkspace.id,
|
||||
`Current (${currentWorkspace.name})`
|
||||
);
|
||||
}
|
||||
this.plugin.workspaceManager
|
||||
?.getAllWorkspaces()
|
||||
.forEach((ws) => {
|
||||
dropdown.addOption(ws.id, ws.name);
|
||||
});
|
||||
|
||||
dropdown.onChange((value) => {
|
||||
this.selectWorkspaceId = value;
|
||||
});
|
||||
});
|
||||
|
||||
// Buttons
|
||||
const buttonContainer = contentEl.createDiv({
|
||||
cls: "modal-button-container",
|
||||
});
|
||||
const createButton = buttonContainer.createEl("button", {
|
||||
text: t("Create"),
|
||||
cls: "mod-cta",
|
||||
});
|
||||
const cancelButton = buttonContainer.createEl("button", {
|
||||
text: t("Cancel"),
|
||||
});
|
||||
|
||||
createButton.addEventListener("click", async () => {
|
||||
const name = this.name.trim();
|
||||
|
||||
if (!name) {
|
||||
new Notice(t("Please enter a workspace name"));
|
||||
return;
|
||||
}
|
||||
const baseId = this.selectWorkspaceId;
|
||||
|
||||
if (name && this.plugin.workspaceManager) {
|
||||
const workspace =
|
||||
await this.plugin.workspaceManager.createWorkspace(
|
||||
name,
|
||||
baseId
|
||||
);
|
||||
new Notice(
|
||||
t('Workspace "{{name}}" created', {
|
||||
interpolation: { name: name },
|
||||
})
|
||||
);
|
||||
this.onCreated(workspace);
|
||||
this.close();
|
||||
} else {
|
||||
new Notice(t("Please enter a workspace name"));
|
||||
}
|
||||
});
|
||||
|
||||
cancelButton.addEventListener("click", () => {
|
||||
this.close();
|
||||
});
|
||||
}
|
||||
|
||||
onClose() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
}
|
||||
|
||||
new CreateWorkspaceModal(this.plugin, (workspace) => {
|
||||
this.onWorkspaceChange(workspace.id);
|
||||
this.currentWorkspaceId = workspace.id;
|
||||
this.render();
|
||||
}).open();
|
||||
}
|
||||
|
||||
private showRenameWorkspaceDialog(workspace: WorkspaceData) {
|
||||
class RenameWorkspaceModal extends Modal {
|
||||
private nameInput: HTMLInputElement;
|
||||
|
||||
constructor(
|
||||
private plugin: TaskProgressBarPlugin,
|
||||
private workspace: WorkspaceData,
|
||||
private onRenamed: () => void
|
||||
) {
|
||||
super(plugin.app);
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
const { contentEl } = this;
|
||||
contentEl.createEl("h2", { text: t("Rename Workspace") });
|
||||
|
||||
const inputContainer = contentEl.createDiv({
|
||||
cls: "setting-item",
|
||||
});
|
||||
inputContainer.createEl("label", { text: t("New Name") + ":" });
|
||||
this.nameInput = inputContainer.createEl("input", {
|
||||
type: "text",
|
||||
value: this.workspace.name,
|
||||
placeholder: t("Enter workspace name..."),
|
||||
});
|
||||
|
||||
const buttonContainer = contentEl.createDiv({
|
||||
cls: "modal-button-container",
|
||||
});
|
||||
const renameButton = buttonContainer.createEl("button", {
|
||||
text: t("Rename"),
|
||||
cls: "mod-cta",
|
||||
});
|
||||
const cancelButton = buttonContainer.createEl("button", {
|
||||
text: t("Cancel"),
|
||||
});
|
||||
|
||||
renameButton.addEventListener("click", async () => {
|
||||
const newName = this.nameInput.value.trim();
|
||||
if (
|
||||
newName &&
|
||||
newName !== this.workspace.name &&
|
||||
this.plugin.workspaceManager
|
||||
) {
|
||||
await this.plugin.workspaceManager.renameWorkspace(
|
||||
this.workspace.id,
|
||||
newName
|
||||
);
|
||||
new Notice(
|
||||
t('Workspace renamed to "{{name}}"', {
|
||||
interpolation: { name: newName },
|
||||
})
|
||||
);
|
||||
this.onRenamed();
|
||||
this.close();
|
||||
} else {
|
||||
new Notice(t("Please enter a different name"));
|
||||
}
|
||||
});
|
||||
|
||||
cancelButton.addEventListener("click", () => {
|
||||
this.close();
|
||||
});
|
||||
|
||||
this.nameInput.focus();
|
||||
this.nameInput.select();
|
||||
}
|
||||
|
||||
onClose() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
}
|
||||
|
||||
new RenameWorkspaceModal(this.plugin, workspace, () => {
|
||||
this.render();
|
||||
}).open();
|
||||
}
|
||||
|
||||
private showDeleteWorkspaceDialog(workspace: WorkspaceData) {
|
||||
class DeleteWorkspaceModal extends Modal {
|
||||
constructor(
|
||||
private plugin: TaskProgressBarPlugin,
|
||||
private workspace: WorkspaceData,
|
||||
private onDeleted: () => void
|
||||
) {
|
||||
super(plugin.app);
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
const { contentEl } = this;
|
||||
contentEl.createEl("h2", { text: t("Delete Workspace") });
|
||||
|
||||
contentEl.createEl("p", {
|
||||
text: t(
|
||||
'Are you sure you want to delete the workspace "{{name}}"? This action cannot be undone.',
|
||||
{ interpolation: { name: this.workspace.name } }
|
||||
),
|
||||
});
|
||||
|
||||
const buttonContainer = contentEl.createDiv({
|
||||
cls: "modal-button-container",
|
||||
});
|
||||
const deleteButton = buttonContainer.createEl("button", {
|
||||
text: t("Delete"),
|
||||
cls: "mod-warning",
|
||||
});
|
||||
const cancelButton = buttonContainer.createEl("button", {
|
||||
text: t("Cancel"),
|
||||
});
|
||||
|
||||
deleteButton.addEventListener("click", async () => {
|
||||
if (this.plugin.workspaceManager) {
|
||||
await this.plugin.workspaceManager.deleteWorkspace(
|
||||
this.workspace.id
|
||||
);
|
||||
new Notice(
|
||||
t('Workspace "{{name}}" deleted', {
|
||||
interpolation: {
|
||||
name: this.workspace.name,
|
||||
},
|
||||
})
|
||||
);
|
||||
this.onDeleted();
|
||||
this.close();
|
||||
}
|
||||
});
|
||||
|
||||
cancelButton.addEventListener("click", () => {
|
||||
this.close();
|
||||
});
|
||||
}
|
||||
|
||||
onClose() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
}
|
||||
|
||||
new DeleteWorkspaceModal(this.plugin, workspace, () => {
|
||||
// After deletion, workspace manager will automatically switch to default
|
||||
this.currentWorkspaceId =
|
||||
this.plugin.workspaceManager?.getActiveWorkspace().id || "";
|
||||
this.render();
|
||||
}).open();
|
||||
}
|
||||
|
||||
public setWorkspace(workspaceId: string) {
|
||||
this.currentWorkspaceId = workspaceId;
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
102
src/experimental/v2/events/ui-event.ts
Normal file
102
src/experimental/v2/events/ui-event.ts
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
import type { App, EventRef } from "obsidian";
|
||||
import { emit as coreEmit, on as coreOn, Seq } from "../../../dataflow/events/Events";
|
||||
|
||||
export const UIEvents = {
|
||||
WORKSPACE_SWITCHED: "task-genius:ui-workspace-switched",
|
||||
WORKSPACE_OVERRIDES_SAVED: "task-genius:ui-workspace-overrides-saved",
|
||||
WORKSPACE_RESET: "task-genius:ui-workspace-reset",
|
||||
DEFAULT_WORKSPACE_CHANGED: "task-genius:ui-default-workspace-changed",
|
||||
WORKSPACE_CREATED: "task-genius:ui-workspace-created",
|
||||
WORKSPACE_DELETED: "task-genius:ui-workspace-deleted",
|
||||
WORKSPACE_RENAMED: "task-genius:ui-workspace-renamed"
|
||||
} as const;
|
||||
|
||||
export interface WorkspaceEventPayload {
|
||||
workspaceId: string;
|
||||
seq: number;
|
||||
changedKeys?: string[];
|
||||
baseId?: string; // For cloned workspaces
|
||||
newName?: string; // For rename events
|
||||
}
|
||||
|
||||
// Event emitters
|
||||
export const emitWorkspaceSwitched = (app: App, workspaceId: string) => {
|
||||
coreEmit(app, UIEvents.WORKSPACE_SWITCHED, {
|
||||
workspaceId,
|
||||
seq: Seq.next()
|
||||
} as WorkspaceEventPayload);
|
||||
};
|
||||
|
||||
export const emitWorkspaceOverridesSaved = (app: App, workspaceId: string, changedKeys?: string[]) => {
|
||||
coreEmit(app, UIEvents.WORKSPACE_OVERRIDES_SAVED, {
|
||||
workspaceId,
|
||||
changedKeys,
|
||||
seq: Seq.next()
|
||||
} as WorkspaceEventPayload);
|
||||
};
|
||||
|
||||
export const emitWorkspaceReset = (app: App, workspaceId: string) => {
|
||||
coreEmit(app, UIEvents.WORKSPACE_RESET, {
|
||||
workspaceId,
|
||||
seq: Seq.next()
|
||||
} as WorkspaceEventPayload);
|
||||
};
|
||||
|
||||
export const emitDefaultWorkspaceChanged = (app: App, workspaceId: string) => {
|
||||
coreEmit(app, UIEvents.DEFAULT_WORKSPACE_CHANGED, {
|
||||
workspaceId,
|
||||
seq: Seq.next()
|
||||
} as WorkspaceEventPayload);
|
||||
};
|
||||
|
||||
export const emitWorkspaceCreated = (app: App, workspaceId: string, baseId?: string) => {
|
||||
coreEmit(app, UIEvents.WORKSPACE_CREATED, {
|
||||
workspaceId,
|
||||
baseId,
|
||||
seq: Seq.next()
|
||||
} as WorkspaceEventPayload);
|
||||
};
|
||||
|
||||
export const emitWorkspaceDeleted = (app: App, workspaceId: string) => {
|
||||
coreEmit(app, UIEvents.WORKSPACE_DELETED, {
|
||||
workspaceId,
|
||||
seq: Seq.next()
|
||||
} as WorkspaceEventPayload);
|
||||
};
|
||||
|
||||
export const emitWorkspaceRenamed = (app: App, workspaceId: string, newName: string) => {
|
||||
coreEmit(app, UIEvents.WORKSPACE_RENAMED, {
|
||||
workspaceId,
|
||||
newName,
|
||||
seq: Seq.next()
|
||||
} as WorkspaceEventPayload);
|
||||
};
|
||||
|
||||
// Event subscribers
|
||||
export const onWorkspaceSwitched = (app: App, handler: (payload: WorkspaceEventPayload) => void): EventRef => {
|
||||
return coreOn(app, UIEvents.WORKSPACE_SWITCHED, handler);
|
||||
};
|
||||
|
||||
export const onWorkspaceOverridesSaved = (app: App, handler: (payload: WorkspaceEventPayload) => void): EventRef => {
|
||||
return coreOn(app, UIEvents.WORKSPACE_OVERRIDES_SAVED, handler);
|
||||
};
|
||||
|
||||
export const onWorkspaceReset = (app: App, handler: (payload: WorkspaceEventPayload) => void): EventRef => {
|
||||
return coreOn(app, UIEvents.WORKSPACE_RESET, handler);
|
||||
};
|
||||
|
||||
export const onDefaultWorkspaceChanged = (app: App, handler: (payload: WorkspaceEventPayload) => void): EventRef => {
|
||||
return coreOn(app, UIEvents.DEFAULT_WORKSPACE_CHANGED, handler);
|
||||
};
|
||||
|
||||
export const onWorkspaceCreated = (app: App, handler: (payload: WorkspaceEventPayload) => void): EventRef => {
|
||||
return coreOn(app, UIEvents.WORKSPACE_CREATED, handler);
|
||||
};
|
||||
|
||||
export const onWorkspaceDeleted = (app: App, handler: (payload: WorkspaceEventPayload) => void): EventRef => {
|
||||
return coreOn(app, UIEvents.WORKSPACE_DELETED, handler);
|
||||
};
|
||||
|
||||
export const onWorkspaceRenamed = (app: App, handler: (payload: WorkspaceEventPayload) => void): EventRef => {
|
||||
return coreOn(app, UIEvents.WORKSPACE_RENAMED, handler);
|
||||
};
|
||||
523
src/experimental/v2/managers/WorkspaceManager.ts
Normal file
523
src/experimental/v2/managers/WorkspaceManager.ts
Normal file
|
|
@ -0,0 +1,523 @@
|
|||
import { App, Notice } from "obsidian";
|
||||
import {
|
||||
WorkspaceData,
|
||||
WorkspacesConfig,
|
||||
EffectiveSettings,
|
||||
WORKSPACE_SCOPED_KEYS,
|
||||
WorkspaceOverrides
|
||||
} from "../types/workspace";
|
||||
import {
|
||||
emitWorkspaceSwitched,
|
||||
emitWorkspaceOverridesSaved,
|
||||
emitWorkspaceReset,
|
||||
emitDefaultWorkspaceChanged,
|
||||
emitWorkspaceCreated,
|
||||
emitWorkspaceDeleted,
|
||||
emitWorkspaceRenamed
|
||||
} from "../events/ui-event";
|
||||
import { Events } from "../../../dataflow/events/Events";
|
||||
import { emit } from "../../../dataflow/events/Events";
|
||||
import type TaskProgressBarPlugin from "../../../index";
|
||||
|
||||
export class WorkspaceManager {
|
||||
private app: App;
|
||||
private plugin: TaskProgressBarPlugin;
|
||||
private effectiveCache: Map<string, EffectiveSettings> = new Map();
|
||||
|
||||
constructor(plugin: TaskProgressBarPlugin) {
|
||||
this.plugin = plugin;
|
||||
this.app = plugin.app;
|
||||
}
|
||||
|
||||
// Get the workspace configuration, initializing if needed
|
||||
private getWorkspacesConfig(): WorkspacesConfig {
|
||||
if (!this.plugin.settings.workspaces) {
|
||||
return this.initializeWorkspaces();
|
||||
}
|
||||
return this.plugin.settings.workspaces;
|
||||
}
|
||||
|
||||
// Initialize workspace system
|
||||
private initializeWorkspaces(): WorkspacesConfig {
|
||||
const defaultId = this.generateId();
|
||||
this.plugin.settings.workspaces = {
|
||||
version: 2,
|
||||
defaultWorkspaceId: defaultId,
|
||||
activeWorkspaceId: defaultId,
|
||||
order: [defaultId],
|
||||
byId: {
|
||||
[defaultId]: {
|
||||
id: defaultId,
|
||||
name: "Default",
|
||||
updatedAt: Date.now(),
|
||||
settings: {} // Default workspace has no overrides
|
||||
}
|
||||
}
|
||||
};
|
||||
return this.plugin.settings.workspaces;
|
||||
}
|
||||
|
||||
// Ensure default workspace invariants
|
||||
public ensureDefaultWorkspaceInvariant(): void {
|
||||
const config = this.getWorkspacesConfig();
|
||||
|
||||
// Ensure default workspace exists
|
||||
if (!config.defaultWorkspaceId || !config.byId[config.defaultWorkspaceId]) {
|
||||
const defaultId = this.generateId();
|
||||
config.defaultWorkspaceId = defaultId;
|
||||
config.byId[defaultId] = {
|
||||
id: defaultId,
|
||||
name: "Default",
|
||||
updatedAt: Date.now(),
|
||||
settings: {}
|
||||
};
|
||||
if (!config.order.includes(defaultId)) {
|
||||
config.order.unshift(defaultId);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure default workspace has no overrides
|
||||
const defaultWs = config.byId[config.defaultWorkspaceId];
|
||||
if (defaultWs.settings && Object.keys(defaultWs.settings).length > 0) {
|
||||
// Merge any overrides into global settings and clear
|
||||
this.mergeIntoGlobal(defaultWs.settings);
|
||||
defaultWs.settings = {};
|
||||
}
|
||||
|
||||
// Ensure active workspace exists
|
||||
if (!config.activeWorkspaceId || !config.byId[config.activeWorkspaceId]) {
|
||||
config.activeWorkspaceId = config.defaultWorkspaceId;
|
||||
}
|
||||
}
|
||||
|
||||
// Merge workspace overrides into global settings
|
||||
private mergeIntoGlobal(overrides: WorkspaceOverrides): void {
|
||||
for (const key of Object.keys(overrides)) {
|
||||
if (WORKSPACE_SCOPED_KEYS.includes(key as any)) {
|
||||
(this.plugin.settings as any)[key] = structuredClone(overrides[key as keyof WorkspaceOverrides]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate effective settings for a workspace
|
||||
public getEffectiveSettings(workspaceId?: string): EffectiveSettings {
|
||||
const config = this.getWorkspacesConfig();
|
||||
const id = workspaceId || config.activeWorkspaceId || config.defaultWorkspaceId;
|
||||
|
||||
// Return from cache if available
|
||||
if (this.effectiveCache.has(id)) {
|
||||
return this.effectiveCache.get(id)!;
|
||||
}
|
||||
|
||||
// Build effective settings
|
||||
const workspace = config.byId[id];
|
||||
if (!workspace) {
|
||||
// Fallback to default if workspace doesn't exist
|
||||
return this.getEffectiveSettings(config.defaultWorkspaceId);
|
||||
}
|
||||
|
||||
// Start with global settings
|
||||
const effective: EffectiveSettings = { ...this.plugin.settings };
|
||||
|
||||
// Apply workspace overrides if not default
|
||||
if (id !== config.defaultWorkspaceId && workspace.settings) {
|
||||
for (const key of WORKSPACE_SCOPED_KEYS) {
|
||||
if (workspace.settings[key] !== undefined) {
|
||||
effective[key] = structuredClone(workspace.settings[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cache the result
|
||||
this.effectiveCache.set(id, effective);
|
||||
return effective;
|
||||
}
|
||||
|
||||
// Calculate overrides from effective settings
|
||||
private toOverrides(effective: EffectiveSettings): WorkspaceOverrides {
|
||||
const overrides: WorkspaceOverrides = {};
|
||||
|
||||
for (const key of WORKSPACE_SCOPED_KEYS) {
|
||||
const effValue = effective[key];
|
||||
const globalValue = (this.plugin.settings as any)[key];
|
||||
|
||||
if (JSON.stringify(effValue) !== JSON.stringify(globalValue)) {
|
||||
overrides[key] = structuredClone(effValue);
|
||||
}
|
||||
}
|
||||
|
||||
return overrides;
|
||||
}
|
||||
|
||||
// Normalize overrides (remove ones identical to global)
|
||||
private normalizeOverrides(): void {
|
||||
const config = this.getWorkspacesConfig();
|
||||
|
||||
for (const id of config.order) {
|
||||
if (id === config.defaultWorkspaceId) continue;
|
||||
|
||||
const workspace = config.byId[id];
|
||||
if (!workspace.settings) continue;
|
||||
|
||||
for (const key of WORKSPACE_SCOPED_KEYS) {
|
||||
if (workspace.settings[key] !== undefined) {
|
||||
const globalValue = (this.plugin.settings as any)[key];
|
||||
if (JSON.stringify(workspace.settings[key]) === JSON.stringify(globalValue)) {
|
||||
delete workspace.settings[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the effective cache
|
||||
public clearCache(): void {
|
||||
this.effectiveCache.clear();
|
||||
}
|
||||
|
||||
// Get all workspaces
|
||||
public getAllWorkspaces(): WorkspaceData[] {
|
||||
const config = this.getWorkspacesConfig();
|
||||
return config.order.map(id => config.byId[id]).filter(ws => ws !== undefined);
|
||||
}
|
||||
|
||||
// Get workspace by ID
|
||||
public getWorkspace(id: string): WorkspaceData | undefined {
|
||||
const config = this.getWorkspacesConfig();
|
||||
return config.byId[id];
|
||||
}
|
||||
|
||||
// Get active workspace
|
||||
public getActiveWorkspace(): WorkspaceData {
|
||||
const config = this.getWorkspacesConfig();
|
||||
const activeId = config.activeWorkspaceId || config.defaultWorkspaceId;
|
||||
return config.byId[activeId] || config.byId[config.defaultWorkspaceId];
|
||||
}
|
||||
|
||||
// Set active workspace
|
||||
public async setActiveWorkspace(workspaceId: string): Promise<void> {
|
||||
const config = this.getWorkspacesConfig();
|
||||
|
||||
if (!config.byId[workspaceId]) {
|
||||
new Notice(`Workspace not found. Using default workspace.`);
|
||||
workspaceId = config.defaultWorkspaceId;
|
||||
}
|
||||
|
||||
if (config.activeWorkspaceId === workspaceId) {
|
||||
return; // Already active
|
||||
}
|
||||
|
||||
config.activeWorkspaceId = workspaceId;
|
||||
this.clearCache();
|
||||
|
||||
await this.plugin.saveSettings();
|
||||
emitWorkspaceSwitched(this.app, workspaceId);
|
||||
}
|
||||
|
||||
// Create new workspace (cloned from current or default)
|
||||
public async createWorkspace(name: string, baseWorkspaceId?: string): Promise<WorkspaceData> {
|
||||
const config = this.getWorkspacesConfig();
|
||||
const id = this.generateId();
|
||||
|
||||
// Use current active workspace as base if not specified
|
||||
const baseId = baseWorkspaceId || config.activeWorkspaceId || config.defaultWorkspaceId;
|
||||
const baseWorkspace = config.byId[baseId];
|
||||
|
||||
// Clone settings from base workspace (if not default)
|
||||
let settings: WorkspaceOverrides = {};
|
||||
if (baseId !== config.defaultWorkspaceId && baseWorkspace?.settings) {
|
||||
settings = structuredClone(baseWorkspace.settings);
|
||||
} else if (baseId === config.defaultWorkspaceId) {
|
||||
// Creating from default means starting with current global values as-is
|
||||
settings = {};
|
||||
}
|
||||
|
||||
const newWorkspace: WorkspaceData = {
|
||||
id,
|
||||
name,
|
||||
updatedAt: Date.now(),
|
||||
settings
|
||||
};
|
||||
|
||||
config.byId[id] = newWorkspace;
|
||||
config.order.push(id);
|
||||
|
||||
await this.plugin.saveSettings();
|
||||
emitWorkspaceCreated(this.app, id, baseId);
|
||||
|
||||
return newWorkspace;
|
||||
}
|
||||
|
||||
// Delete workspace
|
||||
public async deleteWorkspace(workspaceId: string): Promise<void> {
|
||||
const config = this.getWorkspacesConfig();
|
||||
|
||||
// Cannot delete default workspace
|
||||
if (workspaceId === config.defaultWorkspaceId) {
|
||||
new Notice("Cannot delete the default workspace");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!config.byId[workspaceId]) {
|
||||
return; // Already doesn't exist
|
||||
}
|
||||
|
||||
// Remove from config
|
||||
delete config.byId[workspaceId];
|
||||
const orderIndex = config.order.indexOf(workspaceId);
|
||||
if (orderIndex !== -1) {
|
||||
config.order.splice(orderIndex, 1);
|
||||
}
|
||||
|
||||
// If this was the active workspace, switch to default
|
||||
if (config.activeWorkspaceId === workspaceId) {
|
||||
config.activeWorkspaceId = config.defaultWorkspaceId;
|
||||
emitWorkspaceSwitched(this.app, config.defaultWorkspaceId);
|
||||
}
|
||||
|
||||
this.clearCache();
|
||||
await this.plugin.saveSettings();
|
||||
emitWorkspaceDeleted(this.app, workspaceId);
|
||||
}
|
||||
|
||||
// Rename workspace
|
||||
public async renameWorkspace(workspaceId: string, newName: string): Promise<void> {
|
||||
const config = this.getWorkspacesConfig();
|
||||
const workspace = config.byId[workspaceId];
|
||||
|
||||
if (!workspace) {
|
||||
return;
|
||||
}
|
||||
|
||||
workspace.name = newName;
|
||||
workspace.updatedAt = Date.now();
|
||||
|
||||
await this.plugin.saveSettings();
|
||||
emitWorkspaceRenamed(this.app, workspaceId, newName);
|
||||
}
|
||||
|
||||
// Save overrides for a workspace
|
||||
public async saveOverrides(workspaceId: string, effective: EffectiveSettings): Promise<void> {
|
||||
const config = this.getWorkspacesConfig();
|
||||
|
||||
// Cannot save overrides to default workspace
|
||||
if (workspaceId === config.defaultWorkspaceId) {
|
||||
// For default, write directly to global settings
|
||||
for (const key of WORKSPACE_SCOPED_KEYS) {
|
||||
if (effective[key] !== undefined) {
|
||||
(this.plugin.settings as any)[key] = structuredClone(effective[key]);
|
||||
}
|
||||
}
|
||||
await this.plugin.saveSettings();
|
||||
emit(this.app, Events.SETTINGS_CHANGED);
|
||||
return;
|
||||
}
|
||||
|
||||
const workspace = config.byId[workspaceId];
|
||||
if (!workspace) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate overrides
|
||||
const overrides = this.toOverrides(effective);
|
||||
const changedKeys = Object.keys(overrides);
|
||||
|
||||
workspace.settings = overrides;
|
||||
workspace.updatedAt = Date.now();
|
||||
|
||||
this.clearCache();
|
||||
await this.plugin.saveSettings();
|
||||
|
||||
emitWorkspaceOverridesSaved(this.app, workspaceId, changedKeys);
|
||||
emit(this.app, Events.SETTINGS_CHANGED);
|
||||
}
|
||||
|
||||
// Save overrides quietly without triggering SETTINGS_CHANGED event
|
||||
public async saveOverridesQuietly(workspaceId: string, effective: EffectiveSettings): Promise<void> {
|
||||
const config = this.getWorkspacesConfig();
|
||||
|
||||
// Cannot save overrides to default workspace
|
||||
if (workspaceId === config.defaultWorkspaceId) {
|
||||
// For default, write directly to global settings
|
||||
for (const key of WORKSPACE_SCOPED_KEYS) {
|
||||
if (effective[key] !== undefined) {
|
||||
(this.plugin.settings as any)[key] = structuredClone(effective[key]);
|
||||
}
|
||||
}
|
||||
await this.plugin.saveSettings();
|
||||
return;
|
||||
}
|
||||
|
||||
const workspace = config.byId[workspaceId];
|
||||
if (!workspace) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate overrides
|
||||
const overrides = this.toOverrides(effective);
|
||||
|
||||
workspace.settings = overrides;
|
||||
workspace.updatedAt = Date.now();
|
||||
|
||||
this.clearCache();
|
||||
await this.plugin.saveSettings();
|
||||
|
||||
// Don't emit events to avoid triggering reload loops
|
||||
}
|
||||
|
||||
// Reset workspace overrides
|
||||
public async resetOverrides(workspaceId: string): Promise<void> {
|
||||
const config = this.getWorkspacesConfig();
|
||||
|
||||
// Cannot reset default workspace
|
||||
if (workspaceId === config.defaultWorkspaceId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const workspace = config.byId[workspaceId];
|
||||
if (!workspace) {
|
||||
return;
|
||||
}
|
||||
|
||||
workspace.settings = {};
|
||||
workspace.updatedAt = Date.now();
|
||||
|
||||
this.clearCache();
|
||||
await this.plugin.saveSettings();
|
||||
|
||||
emitWorkspaceReset(this.app, workspaceId);
|
||||
emit(this.app, Events.SETTINGS_CHANGED);
|
||||
}
|
||||
|
||||
// Set default workspace (change which one is default)
|
||||
public async setDefaultWorkspace(workspaceId: string): Promise<void> {
|
||||
const config = this.getWorkspacesConfig();
|
||||
|
||||
if (!config.byId[workspaceId]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.defaultWorkspaceId === workspaceId) {
|
||||
return; // Already default
|
||||
}
|
||||
|
||||
// The old default workspace needs to get current global settings as overrides
|
||||
// The new default workspace's overrides become the new global settings
|
||||
|
||||
const oldDefaultId = config.defaultWorkspaceId;
|
||||
const newDefaultWorkspace = config.byId[workspaceId];
|
||||
|
||||
// Save current global as overrides for old default
|
||||
const currentGlobalAsOverrides: WorkspaceOverrides = {};
|
||||
for (const key of WORKSPACE_SCOPED_KEYS) {
|
||||
const globalValue = (this.plugin.settings as any)[key];
|
||||
if (globalValue !== undefined) {
|
||||
currentGlobalAsOverrides[key] = structuredClone(globalValue);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply new default's overrides to global
|
||||
if (newDefaultWorkspace.settings) {
|
||||
this.mergeIntoGlobal(newDefaultWorkspace.settings);
|
||||
}
|
||||
|
||||
// Set old default's overrides
|
||||
config.byId[oldDefaultId].settings = currentGlobalAsOverrides;
|
||||
|
||||
// Clear new default's overrides
|
||||
newDefaultWorkspace.settings = {};
|
||||
|
||||
// Update default ID
|
||||
config.defaultWorkspaceId = workspaceId;
|
||||
|
||||
// Normalize all overrides
|
||||
this.normalizeOverrides();
|
||||
|
||||
this.clearCache();
|
||||
await this.plugin.saveSettings();
|
||||
|
||||
emitDefaultWorkspaceChanged(this.app, workspaceId);
|
||||
emit(this.app, Events.SETTINGS_CHANGED);
|
||||
}
|
||||
|
||||
// Generate unique ID
|
||||
private generateId(): string {
|
||||
return `ws_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
}
|
||||
|
||||
// Migrate from v1 to v2
|
||||
public async migrateToV2(): Promise<void> {
|
||||
if (this.plugin.settings.workspaces?.version === 2) {
|
||||
return; // Already v2
|
||||
}
|
||||
|
||||
// Initialize v2 structure
|
||||
this.initializeWorkspaces();
|
||||
this.ensureDefaultWorkspaceInvariant();
|
||||
|
||||
// If there were v1 workspaces, migrate them
|
||||
// (This is a placeholder - implement based on actual v1 structure)
|
||||
|
||||
await this.plugin.saveSettings();
|
||||
}
|
||||
|
||||
// Reorder workspaces
|
||||
public async reorderWorkspaces(newOrder: string[]): Promise<void> {
|
||||
const config = this.getWorkspacesConfig();
|
||||
|
||||
// Validate that all IDs exist and default is first
|
||||
const validOrder = newOrder.filter(id => config.byId[id]);
|
||||
|
||||
// Ensure default is always first
|
||||
const defaultIndex = validOrder.indexOf(config.defaultWorkspaceId);
|
||||
if (defaultIndex > 0) {
|
||||
validOrder.splice(defaultIndex, 1);
|
||||
validOrder.unshift(config.defaultWorkspaceId);
|
||||
} else if (defaultIndex === -1) {
|
||||
validOrder.unshift(config.defaultWorkspaceId);
|
||||
}
|
||||
|
||||
config.order = validOrder;
|
||||
await this.plugin.saveSettings();
|
||||
}
|
||||
|
||||
// Check if a workspace is the default
|
||||
public isDefaultWorkspace(workspaceId: string): boolean {
|
||||
const config = this.getWorkspacesConfig();
|
||||
return workspaceId === config.defaultWorkspaceId;
|
||||
}
|
||||
|
||||
// Export workspace configuration
|
||||
public exportWorkspace(workspaceId: string): string | null {
|
||||
const workspace = this.getWorkspace(workspaceId);
|
||||
if (!workspace) return null;
|
||||
|
||||
const exportData = {
|
||||
name: workspace.name,
|
||||
settings: workspace.settings,
|
||||
exportedAt: Date.now(),
|
||||
version: 1
|
||||
};
|
||||
|
||||
return JSON.stringify(exportData, null, 2);
|
||||
}
|
||||
|
||||
// Import workspace configuration
|
||||
public async importWorkspace(jsonData: string, name?: string): Promise<WorkspaceData | null> {
|
||||
try {
|
||||
const importData = JSON.parse(jsonData);
|
||||
const workspaceName = name || importData.name || "Imported Workspace";
|
||||
const settings = importData.settings || {};
|
||||
|
||||
const newWorkspace = await this.createWorkspace(workspaceName);
|
||||
newWorkspace.settings = settings;
|
||||
|
||||
await this.plugin.saveSettings();
|
||||
return newWorkspace;
|
||||
} catch (e) {
|
||||
new Notice("Failed to import workspace configuration");
|
||||
console.error("Workspace import error:", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
// Deprecated - use WorkspaceData from types/workspace.ts instead
|
||||
export interface Workspace {
|
||||
id: string;
|
||||
name: string;
|
||||
|
|
@ -14,7 +15,7 @@ export interface WorkspaceSettings {
|
|||
|
||||
export interface V2ViewState {
|
||||
currentWorkspace: string;
|
||||
selectedProject?: string;
|
||||
selectedProject?: string | null;
|
||||
viewMode: 'list' | 'kanban' | 'tree' | 'calendar';
|
||||
searchQuery?: string;
|
||||
filters?: any;
|
||||
|
|
|
|||
94
src/experimental/v2/types/workspace.ts
Normal file
94
src/experimental/v2/types/workspace.ts
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
export interface WorkspaceData {
|
||||
id: string;
|
||||
name: string;
|
||||
color?: string;
|
||||
updatedAt: number;
|
||||
order?: number;
|
||||
settings: WorkspaceOverrides; // Empty for Default workspace
|
||||
}
|
||||
|
||||
export interface WorkspaceOverrides {
|
||||
// View display settings
|
||||
filters?: any;
|
||||
sort?: any;
|
||||
group?: any;
|
||||
columns?: any;
|
||||
viewMode?: string;
|
||||
|
||||
// Calendar settings
|
||||
calendar?: any;
|
||||
|
||||
// Kanban settings
|
||||
kanban?: any;
|
||||
|
||||
// Gantt settings
|
||||
gantt?: any;
|
||||
|
||||
// Other display-related settings
|
||||
displayOptions?: any;
|
||||
viewConfiguration?: any;
|
||||
taskListDisplayOption?: any;
|
||||
forecastOption?: any;
|
||||
customProjectGroupsAndNames?: any;
|
||||
tagCustomOrder?: any;
|
||||
|
||||
// V2 filter state per view
|
||||
v2FilterState?: Record<string, {
|
||||
filters?: any;
|
||||
searchQuery?: string;
|
||||
selectedProject?: string | null;
|
||||
advancedFilter?: any;
|
||||
viewMode?: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface WorkspacesConfig {
|
||||
version: number;
|
||||
defaultWorkspaceId: string;
|
||||
activeWorkspaceId?: string;
|
||||
order: string[]; // Workspace IDs in display order
|
||||
byId: Record<string, WorkspaceData>;
|
||||
}
|
||||
|
||||
export interface EffectiveSettings {
|
||||
// Merged result of global + workspace overrides
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
// Keys that can be overridden per workspace
|
||||
export const WORKSPACE_SCOPED_KEYS = [
|
||||
'filters',
|
||||
'sort',
|
||||
'group',
|
||||
'columns',
|
||||
'viewMode',
|
||||
'calendar',
|
||||
'kanban',
|
||||
'gantt',
|
||||
'displayOptions',
|
||||
'viewConfiguration',
|
||||
'taskListDisplayOption',
|
||||
'forecastOption',
|
||||
'customProjectGroupsAndNames',
|
||||
'tagCustomOrder',
|
||||
'v2FilterState'
|
||||
] as const;
|
||||
|
||||
export type WorkspaceScopedKey = typeof WORKSPACE_SCOPED_KEYS[number];
|
||||
|
||||
// Global-only keys (cannot be overridden)
|
||||
export const GLOBAL_ONLY_KEYS = [
|
||||
'autoRun',
|
||||
'lang',
|
||||
'experimental',
|
||||
'appearance',
|
||||
'hotkeys',
|
||||
'quickCapture',
|
||||
'workflow',
|
||||
'habit',
|
||||
'reward',
|
||||
'integrations',
|
||||
'editorExtensions'
|
||||
] as const;
|
||||
|
||||
export type GlobalOnlyKey = typeof GLOBAL_ONLY_KEYS[number];
|
||||
|
|
@ -246,6 +246,9 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
// Setting tab
|
||||
settingTab: TaskProgressBarSettingTab;
|
||||
|
||||
// Workspace manager instance
|
||||
workspaceManager?: import("./experimental/v2/managers/WorkspaceManager").WorkspaceManager;
|
||||
|
||||
// Task Genius Icon manager instance
|
||||
taskGeniusIconManager: TaskGeniusIconManager;
|
||||
|
||||
|
|
@ -286,6 +289,12 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
// Initialize global suggest manager
|
||||
this.globalSuggestManager = new SuggestManager(this.app, this);
|
||||
|
||||
// Initialize workspace manager
|
||||
const { WorkspaceManager } = await import("./experimental/v2/managers/WorkspaceManager");
|
||||
this.workspaceManager = new WorkspaceManager(this);
|
||||
await this.workspaceManager.migrateToV2();
|
||||
this.workspaceManager.ensureDefaultWorkspaceInvariant();
|
||||
|
||||
// Initialize URI handler
|
||||
this.uriHandler = new ObsidianUriHandler(this);
|
||||
this.uriHandler.register();
|
||||
|
|
|
|||
384
src/setting.ts
384
src/setting.ts
|
|
@ -7,6 +7,9 @@ import {
|
|||
Platform,
|
||||
requireApiVersion,
|
||||
debounce,
|
||||
Menu,
|
||||
Modal,
|
||||
Notice,
|
||||
} from "obsidian";
|
||||
import TaskProgressBarPlugin from ".";
|
||||
|
||||
|
|
@ -42,6 +45,7 @@ import { renderMcpIntegrationSettingsTab } from "./components/features/settings/
|
|||
import { IframeModal } from "@/components/ui/modals/IframeModal";
|
||||
import { renderTaskTimerSettingTab } from "./components/features/settings/tabs/TaskTimerSettingsTab";
|
||||
import { renderBasesSettingsTab } from "./components/features/settings/tabs/BasesSettingsTab";
|
||||
import { WorkspaceData } from "./experimental/v2/types/workspace";
|
||||
|
||||
export class TaskProgressBarSettingTab extends PluginSettingTab {
|
||||
plugin: TaskProgressBarPlugin;
|
||||
|
|
@ -196,6 +200,12 @@ export class TaskProgressBarSettingTab extends PluginSettingTab {
|
|||
icon: "layout",
|
||||
category: "integration",
|
||||
},
|
||||
{
|
||||
id: "workspaces",
|
||||
name: t("Workspaces"),
|
||||
icon: "layers",
|
||||
category: "integration",
|
||||
},
|
||||
{
|
||||
id: "beta-test",
|
||||
name: t("Beta Features"),
|
||||
|
|
@ -675,6 +685,10 @@ export class TaskProgressBarSettingTab extends PluginSettingTab {
|
|||
this.displayBasesSettings(basesSection);
|
||||
}
|
||||
|
||||
// Workspaces Tab
|
||||
const workspacesSection = this.createTabSection("workspaces");
|
||||
this.displayWorkspacesSettings(workspacesSection);
|
||||
|
||||
// Beta Test Tab
|
||||
const betaTestSection = this.createTabSection("beta-test");
|
||||
this.displayBetaTestSettings(betaTestSection);
|
||||
|
|
@ -734,6 +748,8 @@ export class TaskProgressBarSettingTab extends PluginSettingTab {
|
|||
return `${base}/bases-support`;
|
||||
case "desktop-integration":
|
||||
return `${base}/bases-support`;
|
||||
case "workspaces":
|
||||
return `${base}/workspaces`;
|
||||
case "beta-test":
|
||||
return `${base}/getting-started`;
|
||||
case "experimental":
|
||||
|
|
@ -848,6 +864,10 @@ export class TaskProgressBarSettingTab extends PluginSettingTab {
|
|||
renderBetaTestSettingsTab(this, containerEl);
|
||||
}
|
||||
|
||||
private displayWorkspacesSettings(containerEl: HTMLElement): void {
|
||||
this.renderWorkspacesSettingsTab(containerEl);
|
||||
}
|
||||
|
||||
// private displayExperimentalSettings(containerEl: HTMLElement): void {
|
||||
// this.renderExperimentalSettingsTab(containerEl);
|
||||
// }
|
||||
|
|
@ -856,6 +876,370 @@ export class TaskProgressBarSettingTab extends PluginSettingTab {
|
|||
renderTaskTimerSettingTab(this, containerEl);
|
||||
}
|
||||
|
||||
private renderWorkspacesSettingsTab(containerEl: HTMLElement): void {
|
||||
// Create workspaces settings section
|
||||
const workspacesSection = containerEl.createDiv();
|
||||
workspacesSection.addClass("workspaces-settings-section");
|
||||
|
||||
// Section header
|
||||
const headerEl = workspacesSection.createEl("h2");
|
||||
headerEl.setText(t("Workspace Management"));
|
||||
headerEl.addClass("workspaces-section-heading");
|
||||
|
||||
// Description
|
||||
const descEl = workspacesSection.createDiv();
|
||||
descEl.addClass("workspaces-description");
|
||||
descEl.setText(
|
||||
t("Manage workspaces to organize different contexts with their own settings and filters.")
|
||||
);
|
||||
|
||||
if (!this.plugin.workspaceManager) {
|
||||
const warningEl = workspacesSection.createDiv();
|
||||
warningEl.addClass("workspaces-warning");
|
||||
warningEl.setText(t("Workspace manager is not available."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Current workspace info
|
||||
const currentWorkspace = this.plugin.workspaceManager.getActiveWorkspace();
|
||||
const isDefault = this.plugin.workspaceManager.isDefaultWorkspace(currentWorkspace.id);
|
||||
|
||||
new Setting(workspacesSection)
|
||||
.setName(t("Current Workspace"))
|
||||
.setDesc(
|
||||
`${currentWorkspace.name}${isDefault ? " (" + t("Default") + ")" : ""}`
|
||||
)
|
||||
.addButton((button) => {
|
||||
button
|
||||
.setButtonText(t("Switch Workspace"))
|
||||
.onClick(() => {
|
||||
this.showWorkspaceSelector();
|
||||
});
|
||||
});
|
||||
|
||||
// Workspace list
|
||||
const allWorkspaces = this.plugin.workspaceManager.getAllWorkspaces();
|
||||
|
||||
const workspaceListEl = workspacesSection.createDiv();
|
||||
workspaceListEl.addClass("workspace-list");
|
||||
|
||||
const listHeaderEl = workspaceListEl.createEl("h3");
|
||||
listHeaderEl.setText(t("All Workspaces"));
|
||||
|
||||
allWorkspaces.forEach((workspace) => {
|
||||
const workspaceItemEl = workspaceListEl.createDiv();
|
||||
workspaceItemEl.addClass("workspace-item");
|
||||
|
||||
const isCurrentActive = workspace.id === currentWorkspace.id;
|
||||
const isDefaultWs = this.plugin.workspaceManager!.isDefaultWorkspace(workspace.id);
|
||||
|
||||
if (isCurrentActive) {
|
||||
workspaceItemEl.addClass("workspace-item-active");
|
||||
}
|
||||
|
||||
new Setting(workspaceItemEl)
|
||||
.setName(workspace.name)
|
||||
.setDesc(
|
||||
isDefaultWs
|
||||
? t("Default workspace")
|
||||
: t("Last updated: {{date}}", {
|
||||
date: new Date(workspace.updatedAt).toLocaleDateString(),
|
||||
})
|
||||
)
|
||||
.addButton((button) => {
|
||||
if (isCurrentActive) {
|
||||
button.setButtonText(t("Active")).setDisabled(true);
|
||||
} else {
|
||||
button.setButtonText(t("Switch")).onClick(async () => {
|
||||
await this.plugin.workspaceManager!.setActiveWorkspace(
|
||||
workspace.id
|
||||
);
|
||||
this.display();
|
||||
});
|
||||
}
|
||||
})
|
||||
.addButton((button) => {
|
||||
button.setIcon("edit").setTooltip(t("Rename")).onClick(() => {
|
||||
this.showRenameWorkspaceDialog(workspace);
|
||||
});
|
||||
})
|
||||
.addButton((button) => {
|
||||
if (isDefaultWs) {
|
||||
button.setDisabled(true);
|
||||
} else {
|
||||
button
|
||||
.setIcon("trash")
|
||||
.setTooltip(t("Delete"))
|
||||
.onClick(() => {
|
||||
this.showDeleteWorkspaceDialog(workspace);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Create new workspace button
|
||||
new Setting(workspacesSection)
|
||||
.setName(t("Create New Workspace"))
|
||||
.setDesc(t("Create a new workspace with custom settings"))
|
||||
.addButton((button) => {
|
||||
button
|
||||
.setButtonText(t("Create"))
|
||||
.setCta()
|
||||
.onClick(() => {
|
||||
this.showCreateWorkspaceDialog();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private showWorkspaceSelector() {
|
||||
if (!this.plugin.workspaceManager) return;
|
||||
|
||||
const menu = new Menu();
|
||||
const workspaces = this.plugin.workspaceManager.getAllWorkspaces();
|
||||
const currentWorkspace = this.plugin.workspaceManager.getActiveWorkspace();
|
||||
|
||||
workspaces.forEach((workspace) => {
|
||||
menu.addItem((item) => {
|
||||
item.setTitle(workspace.name)
|
||||
.setIcon("layers")
|
||||
.onClick(async () => {
|
||||
await this.plugin.workspaceManager!.setActiveWorkspace(
|
||||
workspace.id
|
||||
);
|
||||
this.display();
|
||||
});
|
||||
|
||||
if (workspace.id === currentWorkspace.id) {
|
||||
item.setChecked(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
menu.showAtPosition({ x: 0, y: 0 });
|
||||
}
|
||||
|
||||
private showCreateWorkspaceDialog() {
|
||||
if (!this.plugin.workspaceManager) return;
|
||||
|
||||
class CreateWorkspaceModal extends Modal {
|
||||
private nameInput: HTMLInputElement;
|
||||
private baseSelect: HTMLSelectElement;
|
||||
|
||||
constructor(
|
||||
private plugin: TaskProgressBarPlugin,
|
||||
private onCreated: () => void
|
||||
) {
|
||||
super(plugin.app);
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
const { contentEl } = this;
|
||||
contentEl.createEl("h2", { text: t("Create New Workspace") });
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName(t("Workspace Name"))
|
||||
.addText((text) => {
|
||||
text.setPlaceholder(t("Enter workspace name"));
|
||||
this.nameInput = text.inputEl;
|
||||
});
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName(t("Copy Settings From"))
|
||||
.addDropdown((dropdown) => {
|
||||
dropdown.addOption("", t("Default settings"));
|
||||
this.plugin.workspaceManager
|
||||
?.getAllWorkspaces()
|
||||
.forEach((ws) => {
|
||||
dropdown.addOption(ws.id, ws.name);
|
||||
});
|
||||
this.baseSelect = dropdown.selectEl;
|
||||
});
|
||||
|
||||
const buttonContainer = contentEl.createDiv({
|
||||
cls: "modal-button-container",
|
||||
});
|
||||
|
||||
const createButton = buttonContainer.createEl("button", {
|
||||
text: t("Create"),
|
||||
cls: "mod-cta",
|
||||
});
|
||||
|
||||
const cancelButton = buttonContainer.createEl("button", {
|
||||
text: t("Cancel"),
|
||||
});
|
||||
|
||||
createButton.addEventListener("click", async () => {
|
||||
const name = this.nameInput.value.trim();
|
||||
const baseId = this.baseSelect.value || undefined;
|
||||
|
||||
if (name && this.plugin.workspaceManager) {
|
||||
await this.plugin.workspaceManager.createWorkspace(
|
||||
name,
|
||||
baseId
|
||||
);
|
||||
new Notice(t("Workspace created"));
|
||||
this.onCreated();
|
||||
this.close();
|
||||
} else {
|
||||
new Notice(t("Please enter a workspace name"));
|
||||
}
|
||||
});
|
||||
|
||||
cancelButton.addEventListener("click", () => {
|
||||
this.close();
|
||||
});
|
||||
}
|
||||
|
||||
onClose() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
}
|
||||
|
||||
new CreateWorkspaceModal(this.plugin, () => {
|
||||
this.display();
|
||||
}).open();
|
||||
}
|
||||
|
||||
private showRenameWorkspaceDialog(workspace: WorkspaceData) {
|
||||
if (!this.plugin.workspaceManager) return;
|
||||
|
||||
class RenameWorkspaceModal extends Modal {
|
||||
private nameInput: HTMLInputElement;
|
||||
|
||||
constructor(
|
||||
private plugin: TaskProgressBarPlugin,
|
||||
private workspace: WorkspaceData,
|
||||
private onRenamed: () => void
|
||||
) {
|
||||
super(plugin.app);
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
const { contentEl } = this;
|
||||
contentEl.createEl("h2", { text: t("Rename Workspace") });
|
||||
|
||||
new Setting(contentEl).setName(t("New Name")).addText((text) => {
|
||||
text
|
||||
.setValue(this.workspace.name)
|
||||
.setPlaceholder(t("Enter new name"));
|
||||
this.nameInput = text.inputEl;
|
||||
});
|
||||
|
||||
const buttonContainer = contentEl.createDiv({
|
||||
cls: "modal-button-container",
|
||||
});
|
||||
|
||||
const renameButton = buttonContainer.createEl("button", {
|
||||
text: t("Rename"),
|
||||
cls: "mod-cta",
|
||||
});
|
||||
|
||||
const cancelButton = buttonContainer.createEl("button", {
|
||||
text: t("Cancel"),
|
||||
});
|
||||
|
||||
renameButton.addEventListener("click", async () => {
|
||||
const newName = this.nameInput.value.trim();
|
||||
if (
|
||||
newName &&
|
||||
newName !== this.workspace.name &&
|
||||
this.plugin.workspaceManager
|
||||
) {
|
||||
await this.plugin.workspaceManager.renameWorkspace(
|
||||
this.workspace.id,
|
||||
newName
|
||||
);
|
||||
new Notice(t("Workspace renamed"));
|
||||
this.onRenamed();
|
||||
this.close();
|
||||
} else {
|
||||
new Notice(t("Please enter a different name"));
|
||||
}
|
||||
});
|
||||
|
||||
cancelButton.addEventListener("click", () => {
|
||||
this.close();
|
||||
});
|
||||
|
||||
this.nameInput.focus();
|
||||
this.nameInput.select();
|
||||
}
|
||||
|
||||
onClose() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
}
|
||||
|
||||
new RenameWorkspaceModal(this.plugin, workspace, () => {
|
||||
this.display();
|
||||
}).open();
|
||||
}
|
||||
|
||||
private showDeleteWorkspaceDialog(workspace: WorkspaceData) {
|
||||
if (!this.plugin.workspaceManager) return;
|
||||
|
||||
class DeleteWorkspaceModal extends Modal {
|
||||
constructor(
|
||||
private plugin: TaskProgressBarPlugin,
|
||||
private workspace: WorkspaceData,
|
||||
private onDeleted: () => void
|
||||
) {
|
||||
super(plugin.app);
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
const { contentEl } = this;
|
||||
contentEl.createEl("h2", { text: t("Delete Workspace") });
|
||||
|
||||
contentEl.createEl("p", {
|
||||
text: t(
|
||||
'Are you sure you want to delete "{{name}}"? This action cannot be undone.',
|
||||
{ name: this.workspace.name }
|
||||
),
|
||||
});
|
||||
|
||||
const buttonContainer = contentEl.createDiv({
|
||||
cls: "modal-button-container",
|
||||
});
|
||||
|
||||
const deleteButton = buttonContainer.createEl("button", {
|
||||
text: t("Delete"),
|
||||
cls: "mod-warning",
|
||||
});
|
||||
|
||||
const cancelButton = buttonContainer.createEl("button", {
|
||||
text: t("Cancel"),
|
||||
});
|
||||
|
||||
deleteButton.addEventListener("click", async () => {
|
||||
if (this.plugin.workspaceManager) {
|
||||
await this.plugin.workspaceManager.deleteWorkspace(
|
||||
this.workspace.id
|
||||
);
|
||||
new Notice(t("Workspace deleted"));
|
||||
this.onDeleted();
|
||||
this.close();
|
||||
}
|
||||
});
|
||||
|
||||
cancelButton.addEventListener("click", () => {
|
||||
this.close();
|
||||
});
|
||||
}
|
||||
|
||||
onClose() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
}
|
||||
|
||||
new DeleteWorkspaceModal(this.plugin, workspace, () => {
|
||||
this.display();
|
||||
}).open();
|
||||
}
|
||||
|
||||
private renderExperimentalSettingsTab(containerEl: HTMLElement): void {
|
||||
// Create experimental settings section
|
||||
const experimentalSection = containerEl.createDiv();
|
||||
|
|
|
|||
Loading…
Reference in a new issue