Archived
Remember selected workspace per project
This commit is contained in:
@@ -29,6 +29,7 @@ export class ProjectController {
|
|||||||
async closeProject(projectId: string) {
|
async closeProject(projectId: string) {
|
||||||
try {
|
try {
|
||||||
await api.closeProject(projectId);
|
await api.closeProject(projectId);
|
||||||
|
this.workspaces.forgetProject(projectId);
|
||||||
const state = this.getState();
|
const state = this.getState();
|
||||||
this.setState({ projects: state.projects.filter((p) => p.id !== projectId) });
|
this.setState({ projects: state.projects.filter((p) => p.id !== projectId) });
|
||||||
if (state.selectedProject?.id === projectId) this.workspaces.clearSelection();
|
if (state.selectedProject?.id === projectId) this.workspaces.clearSelection();
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { api, type Project, type Workspace } from "../api";
|
import { api, type Project, type Workspace } from "../api";
|
||||||
import type { GetState, RouteTarget, SetState, UpdateUrl } from "./types";
|
import type { GetState, RouteTarget, SetState, UpdateUrl } from "./types";
|
||||||
import type { SessionController } from "./sessionController";
|
import type { SessionController } from "./sessionController";
|
||||||
|
import { InMemoryWorkspaceSelectionMemory, selectPreferredWorkspace, type WorkspaceSelectionMemory } from "./workspaceSelection";
|
||||||
|
|
||||||
export class WorkspaceController {
|
export class WorkspaceController {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -8,6 +9,7 @@ export class WorkspaceController {
|
|||||||
private readonly setState: SetState,
|
private readonly setState: SetState,
|
||||||
private readonly updateUrl: UpdateUrl,
|
private readonly updateUrl: UpdateUrl,
|
||||||
private readonly sessions: SessionController,
|
private readonly sessions: SessionController,
|
||||||
|
private readonly workspaceSelection: WorkspaceSelectionMemory = new InMemoryWorkspaceSelectionMemory(),
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
clearSelection(options?: { updateUrl?: boolean | undefined }) {
|
clearSelection(options?: { updateUrl?: boolean | undefined }) {
|
||||||
@@ -16,13 +18,17 @@ export class WorkspaceController {
|
|||||||
if (options?.updateUrl !== false) this.updateUrl();
|
if (options?.updateUrl !== false) this.updateUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
forgetProject(projectId: string): void {
|
||||||
|
this.workspaceSelection.forgetProject(projectId);
|
||||||
|
}
|
||||||
|
|
||||||
async selectProject(project: Project, target?: RouteTarget) {
|
async selectProject(project: Project, target?: RouteTarget) {
|
||||||
this.sessions.clearActiveSession();
|
this.sessions.clearActiveSession();
|
||||||
this.setState({ selectedProject: project, selectedWorkspace: undefined, sessions: [], workspaces: [], fileTree: [], expandedDirs: {}, selectedFilePath: undefined, selectedFileContent: undefined, fileTreeStale: false, gitStatus: undefined, selectedDiffPath: undefined, selectedDiff: undefined, selectedStagedDiff: undefined, gitStale: false, error: "" });
|
this.setState({ selectedProject: project, selectedWorkspace: undefined, sessions: [], workspaces: [], fileTree: [], expandedDirs: {}, selectedFilePath: undefined, selectedFileContent: undefined, fileTreeStale: false, gitStatus: undefined, selectedDiffPath: undefined, selectedDiff: undefined, selectedStagedDiff: undefined, gitStale: false, error: "" });
|
||||||
try {
|
try {
|
||||||
const workspaces = await api.workspaces(project.id);
|
const workspaces = await api.workspaces(project.id);
|
||||||
this.setState({ workspaces });
|
this.setState({ workspaces });
|
||||||
const workspace = target?.workspaceId !== undefined && target.workspaceId !== "" ? workspaces.find((w) => w.id === target.workspaceId) : workspaces[0];
|
const workspace = selectPreferredWorkspace(workspaces, { targetWorkspaceId: target?.workspaceId, latestWorkspaceId: this.workspaceSelection.latestWorkspaceId(project.id) });
|
||||||
if (workspace) await this.selectWorkspace(workspace, { sessionId: target?.sessionId, updateUrl: target?.updateUrl });
|
if (workspace) await this.selectWorkspace(workspace, { sessionId: target?.sessionId, updateUrl: target?.updateUrl });
|
||||||
else if (target?.updateUrl !== false) this.updateUrl();
|
else if (target?.updateUrl !== false) this.updateUrl();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -31,6 +37,7 @@ export class WorkspaceController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async selectWorkspace(workspace: Workspace, target?: { sessionId?: string | undefined; updateUrl?: boolean | undefined }) {
|
async selectWorkspace(workspace: Workspace, target?: { sessionId?: string | undefined; updateUrl?: boolean | undefined }) {
|
||||||
|
this.workspaceSelection.rememberWorkspace(workspace);
|
||||||
this.sessions.clearActiveSession();
|
this.sessions.clearActiveSession();
|
||||||
this.setState({ selectedWorkspace: workspace, sessions: [], fileTree: [], expandedDirs: {}, selectedFilePath: undefined, selectedFileContent: undefined, fileTreeStale: false, gitStatus: undefined, selectedDiffPath: undefined, selectedDiff: undefined, selectedStagedDiff: undefined, gitStale: false, error: "" });
|
this.setState({ selectedWorkspace: workspace, sessions: [], fileTree: [], expandedDirs: {}, selectedFilePath: undefined, selectedFileContent: undefined, fileTreeStale: false, gitStatus: undefined, selectedDiffPath: undefined, selectedDiff: undefined, selectedStagedDiff: undefined, gitStale: false, error: "" });
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import type { Workspace } from "../api";
|
||||||
|
import { InMemoryWorkspaceSelectionMemory, selectPreferredWorkspace } from "./workspaceSelection";
|
||||||
|
|
||||||
|
describe("selectPreferredWorkspace", () => {
|
||||||
|
it("prefers an explicit target workspace", () => {
|
||||||
|
const workspaces = [testWorkspace("main"), testWorkspace("feature")];
|
||||||
|
|
||||||
|
expect(selectPreferredWorkspace(workspaces, { targetWorkspaceId: "feature", latestWorkspaceId: "main" })?.id).toBe("feature");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("remembers the latest selected workspace when no explicit target is provided", () => {
|
||||||
|
const workspaces = [testWorkspace("main"), testWorkspace("feature")];
|
||||||
|
|
||||||
|
expect(selectPreferredWorkspace(workspaces, { latestWorkspaceId: "feature" })?.id).toBe("feature");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back to the first workspace when the remembered workspace no longer exists", () => {
|
||||||
|
const workspaces = [testWorkspace("main"), testWorkspace("feature")];
|
||||||
|
|
||||||
|
expect(selectPreferredWorkspace(workspaces, { latestWorkspaceId: "old" })?.id).toBe("main");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("preserves explicit invalid target behavior", () => {
|
||||||
|
const workspaces = [testWorkspace("main"), testWorkspace("feature")];
|
||||||
|
|
||||||
|
expect(selectPreferredWorkspace(workspaces, { targetWorkspaceId: "old", latestWorkspaceId: "feature" })).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("InMemoryWorkspaceSelectionMemory", () => {
|
||||||
|
it("remembers and forgets the latest selected workspace per project", () => {
|
||||||
|
const memory = new InMemoryWorkspaceSelectionMemory();
|
||||||
|
|
||||||
|
memory.rememberWorkspace({ ...testWorkspace("feature"), projectId: "p1" });
|
||||||
|
memory.rememberWorkspace({ ...testWorkspace("other"), projectId: "p2" });
|
||||||
|
|
||||||
|
expect(memory.latestWorkspaceId("p1")).toBe("feature");
|
||||||
|
expect(memory.latestWorkspaceId("p2")).toBe("other");
|
||||||
|
|
||||||
|
memory.forgetProject("p1");
|
||||||
|
|
||||||
|
expect(memory.latestWorkspaceId("p1")).toBeUndefined();
|
||||||
|
expect(memory.latestWorkspaceId("p2")).toBe("other");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function testWorkspace(id: string): Workspace {
|
||||||
|
return { id, projectId: "project", path: `/tmp/project/${id}`, label: id, isMain: id === "main", isGitRepo: true, isGitWorktree: id !== "main" };
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import type { Workspace } from "../api";
|
||||||
|
|
||||||
|
export interface WorkspaceSelectionMemory {
|
||||||
|
latestWorkspaceId(projectId: string): string | undefined;
|
||||||
|
rememberWorkspace(workspace: Workspace): void;
|
||||||
|
forgetProject(projectId: string): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class InMemoryWorkspaceSelectionMemory implements WorkspaceSelectionMemory {
|
||||||
|
private readonly workspaceIdsByProject = new Map<string, string>();
|
||||||
|
|
||||||
|
latestWorkspaceId(projectId: string): string | undefined {
|
||||||
|
return this.workspaceIdsByProject.get(projectId);
|
||||||
|
}
|
||||||
|
|
||||||
|
rememberWorkspace(workspace: Workspace): void {
|
||||||
|
this.workspaceIdsByProject.set(workspace.projectId, workspace.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
forgetProject(projectId: string): void {
|
||||||
|
this.workspaceIdsByProject.delete(projectId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function selectPreferredWorkspace(workspaces: Workspace[], options?: { targetWorkspaceId?: string | undefined; latestWorkspaceId?: string | undefined }): Workspace | undefined {
|
||||||
|
const targetWorkspaceId = options?.targetWorkspaceId;
|
||||||
|
if (targetWorkspaceId !== undefined && targetWorkspaceId !== "") return workspaces.find((workspace) => workspace.id === targetWorkspaceId);
|
||||||
|
|
||||||
|
const latestWorkspaceId = options?.latestWorkspaceId;
|
||||||
|
if (latestWorkspaceId !== undefined && latestWorkspaceId !== "") return workspaces.find((workspace) => workspace.id === latestWorkspaceId) ?? workspaces[0];
|
||||||
|
|
||||||
|
return workspaces[0];
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user