mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-05-16 01:12:46 +00:00
feat: use workspace ids instead of paths for workspace operations
This commit is contained in:
@@ -16,17 +16,17 @@ const getTrimmedString = (value: unknown): string => {
|
||||
return value.trim();
|
||||
};
|
||||
|
||||
const parseWorkspacePathFromBody = (req: Request): string => {
|
||||
const parseWorkspaceIdFromBody = (req: Request): string => {
|
||||
const body = req.body as Record<string, unknown> | undefined;
|
||||
const workspacePath = getTrimmedString(body?.workspacePath);
|
||||
if (!workspacePath) {
|
||||
throw new AppError('workspacePath is required.', {
|
||||
code: 'WORKSPACE_PATH_REQUIRED',
|
||||
const workspaceId = getTrimmedString(body?.workspaceId);
|
||||
if (!workspaceId) {
|
||||
throw new AppError('workspaceId is required.', {
|
||||
code: 'WORKSPACE_ID_REQUIRED',
|
||||
statusCode: 400,
|
||||
});
|
||||
}
|
||||
|
||||
return workspacePath;
|
||||
return workspaceId;
|
||||
};
|
||||
|
||||
const parseWorkspaceCustomNameFromBody = (req: Request): string | null => {
|
||||
@@ -46,27 +46,27 @@ router.get(
|
||||
router.patch(
|
||||
'/star',
|
||||
asyncHandler(async (req: Request, res: Response) => {
|
||||
const workspacePath = parseWorkspacePathFromBody(req);
|
||||
const isStarred = workspaceService.toggleWorkspaceStar(workspacePath);
|
||||
res.json(createApiSuccessResponse({ workspacePath, isStarred }));
|
||||
const workspaceId = parseWorkspaceIdFromBody(req);
|
||||
const isStarred = workspaceService.toggleWorkspaceStar(workspaceId);
|
||||
res.json(createApiSuccessResponse({ workspaceId, isStarred }));
|
||||
}),
|
||||
);
|
||||
|
||||
router.patch(
|
||||
'/name',
|
||||
asyncHandler(async (req: Request, res: Response) => {
|
||||
const workspacePath = parseWorkspacePathFromBody(req);
|
||||
const workspaceId = parseWorkspaceIdFromBody(req);
|
||||
const workspaceCustomName = parseWorkspaceCustomNameFromBody(req);
|
||||
workspaceService.updateWorkspaceCustomName(workspacePath, workspaceCustomName);
|
||||
res.json(createApiSuccessResponse({ workspacePath, workspaceCustomName }));
|
||||
workspaceService.updateWorkspaceCustomName(workspaceId, workspaceCustomName);
|
||||
res.json(createApiSuccessResponse({ workspaceId, workspaceCustomName }));
|
||||
}),
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/',
|
||||
asyncHandler(async (req: Request, res: Response) => {
|
||||
const workspacePath = parseWorkspacePathFromBody(req);
|
||||
const result = await workspaceService.deleteWorkspace(workspacePath);
|
||||
const workspaceId = parseWorkspaceIdFromBody(req);
|
||||
const result = await workspaceService.deleteWorkspace(workspaceId);
|
||||
res.json(createApiSuccessResponse(result));
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -19,6 +19,7 @@ export type WorkspaceSessionRecord = {
|
||||
};
|
||||
|
||||
export type WorkspaceRecord = {
|
||||
workspaceId: string;
|
||||
workspaceOriginalPath: string;
|
||||
workspaceCustomName: string | null;
|
||||
workspaceDisplayName: string;
|
||||
@@ -107,6 +108,7 @@ const buildWorkspaceSessionCollection = (): WorkspaceRecord[] => {
|
||||
const lastActivity = sessions[0]?.lastActivity || null;
|
||||
|
||||
return {
|
||||
workspaceId: workspaceRow.workspace_id,
|
||||
workspaceOriginalPath: workspaceRow.workspace_path,
|
||||
workspaceCustomName: workspaceRow.custom_workspace_name,
|
||||
workspaceDisplayName:
|
||||
@@ -130,8 +132,8 @@ export const workspaceService = {
|
||||
return buildWorkspaceSessionCollection();
|
||||
},
|
||||
|
||||
toggleWorkspaceStar(workspacePath: string): boolean {
|
||||
const workspaceRow = workspaceOriginalPathsDb.getWorkspacePath(workspacePath);
|
||||
toggleWorkspaceStar(workspaceId: string): boolean {
|
||||
const workspaceRow = workspaceOriginalPathsDb.getWorkspaceById(workspaceId);
|
||||
if (!workspaceRow) {
|
||||
throw new AppError('Workspace not found.', {
|
||||
code: 'WORKSPACE_NOT_FOUND',
|
||||
@@ -140,22 +142,40 @@ export const workspaceService = {
|
||||
}
|
||||
|
||||
const nextIsStarred = workspaceRow.isStarred !== 1;
|
||||
workspaceOriginalPathsDb.updateWorkspaceIsStarred(workspacePath, nextIsStarred);
|
||||
workspaceOriginalPathsDb.updateWorkspaceIsStarredById(workspaceId, nextIsStarred);
|
||||
|
||||
return nextIsStarred;
|
||||
},
|
||||
|
||||
updateWorkspaceCustomName(workspacePath: string, workspaceCustomName: string | null): void {
|
||||
workspaceOriginalPathsDb.updateCustomWorkspaceName(workspacePath, workspaceCustomName);
|
||||
updateWorkspaceCustomName(workspaceId: string, workspaceCustomName: string | null): void {
|
||||
const workspaceRow = workspaceOriginalPathsDb.getWorkspaceById(workspaceId);
|
||||
if (!workspaceRow) {
|
||||
throw new AppError('Workspace not found.', {
|
||||
code: 'WORKSPACE_NOT_FOUND',
|
||||
statusCode: 404,
|
||||
});
|
||||
}
|
||||
|
||||
workspaceOriginalPathsDb.updateCustomWorkspaceNameById(workspaceId, workspaceCustomName);
|
||||
},
|
||||
|
||||
async deleteWorkspace(workspacePath: string): Promise<{
|
||||
async deleteWorkspace(workspaceId: string): Promise<{
|
||||
workspaceId: string;
|
||||
workspacePath: string;
|
||||
deletedWorkspace: boolean;
|
||||
deletedSessionCount: number;
|
||||
jsonlDeletedCount: number;
|
||||
failedSessionFileDeletes: string[];
|
||||
}> {
|
||||
const workspaceRow = workspaceOriginalPathsDb.getWorkspaceById(workspaceId);
|
||||
if (!workspaceRow) {
|
||||
throw new AppError('Workspace not found.', {
|
||||
code: 'WORKSPACE_NOT_FOUND',
|
||||
statusCode: 404,
|
||||
});
|
||||
}
|
||||
|
||||
const workspacePath = workspaceRow.workspace_path;
|
||||
const sessionRows = sessionsDb.getSessionsByWorkspacePath(workspacePath);
|
||||
const failedSessionFileDeletes: string[] = [];
|
||||
let jsonlDeletedCount = 0;
|
||||
@@ -175,9 +195,10 @@ export const workspaceService = {
|
||||
}
|
||||
}
|
||||
|
||||
workspaceOriginalPathsDb.deleteWorkspacePath(workspacePath);
|
||||
workspaceOriginalPathsDb.deleteWorkspaceById(workspaceId);
|
||||
|
||||
return {
|
||||
workspaceId,
|
||||
workspacePath,
|
||||
deletedWorkspace: true,
|
||||
deletedSessionCount: sessionRows.length,
|
||||
|
||||
@@ -10,6 +10,14 @@ import {
|
||||
} from "@/shared/database/schema.js";
|
||||
import { logger } from "@/shared/utils/logger.js";
|
||||
|
||||
const SQLITE_UUID_SQL = `
|
||||
lower(hex(randomblob(4))) || '-' ||
|
||||
lower(hex(randomblob(2))) || '-' ||
|
||||
lower(hex(randomblob(2))) || '-' ||
|
||||
lower(hex(randomblob(2))) || '-' ||
|
||||
lower(hex(randomblob(6)))
|
||||
`;
|
||||
|
||||
const addColumnToTableIfNotExists = (
|
||||
db: Database,
|
||||
tableName: string,
|
||||
@@ -61,7 +69,9 @@ export const runMigrations = (db: Database) => {
|
||||
db.exec("UPDATE sessions SET updated_at = COALESCE(updated_at, CURRENT_TIMESTAMP)");
|
||||
|
||||
db.exec(WORK_SPACE_PATH_SQL);
|
||||
const workspaceOriginalPathsTableInfo = db.prepare("PRAGMA table_info(workspace_original_paths)").all() as { name: string }[];
|
||||
const workspaceOriginalPathsTableInfo = db
|
||||
.prepare("PRAGMA table_info(workspace_original_paths)")
|
||||
.all() as { name: string; pk: number }[];
|
||||
const workspaceOriginalPathsColumnNames = workspaceOriginalPathsTableInfo.map((col) => col.name);
|
||||
addColumnToTableIfNotExists(
|
||||
db,
|
||||
@@ -70,6 +80,13 @@ export const runMigrations = (db: Database) => {
|
||||
"custom_workspace_name",
|
||||
"TEXT DEFAULT NULL",
|
||||
);
|
||||
addColumnToTableIfNotExists(
|
||||
db,
|
||||
"workspace_original_paths",
|
||||
workspaceOriginalPathsColumnNames,
|
||||
"workspace_id",
|
||||
"TEXT",
|
||||
);
|
||||
addColumnToTableIfNotExists(
|
||||
db,
|
||||
"workspace_original_paths",
|
||||
@@ -77,9 +94,61 @@ export const runMigrations = (db: Database) => {
|
||||
"isStarred",
|
||||
"BOOLEAN DEFAULT 0",
|
||||
);
|
||||
db.exec(`
|
||||
UPDATE workspace_original_paths
|
||||
SET workspace_id = ${SQLITE_UUID_SQL}
|
||||
WHERE workspace_id IS NULL OR trim(workspace_id) = ''
|
||||
`);
|
||||
const workspaceOriginalPathsPrimaryKeyColumn =
|
||||
workspaceOriginalPathsTableInfo.find((column) => column.pk === 1)?.name ?? null;
|
||||
if (workspaceOriginalPathsPrimaryKeyColumn !== "workspace_id") {
|
||||
logger.info(
|
||||
"Running migration: Rebuilding workspace_original_paths to set workspace_id as primary key",
|
||||
);
|
||||
db.exec("PRAGMA foreign_keys = OFF");
|
||||
try {
|
||||
db.exec("BEGIN TRANSACTION");
|
||||
db.exec("DROP TABLE IF EXISTS workspace_original_paths__new");
|
||||
db.exec(`
|
||||
CREATE TABLE workspace_original_paths__new (
|
||||
workspace_id TEXT PRIMARY KEY NOT NULL,
|
||||
workspace_path TEXT NOT NULL UNIQUE,
|
||||
custom_workspace_name TEXT DEFAULT NULL,
|
||||
isStarred BOOLEAN DEFAULT 0
|
||||
)
|
||||
`);
|
||||
db.exec(`
|
||||
INSERT INTO workspace_original_paths__new (
|
||||
workspace_id,
|
||||
workspace_path,
|
||||
custom_workspace_name,
|
||||
isStarred
|
||||
)
|
||||
SELECT
|
||||
CASE
|
||||
WHEN workspace_id IS NULL OR trim(workspace_id) = ''
|
||||
THEN ${SQLITE_UUID_SQL}
|
||||
ELSE workspace_id
|
||||
END,
|
||||
workspace_path,
|
||||
custom_workspace_name,
|
||||
COALESCE(isStarred, 0)
|
||||
FROM workspace_original_paths
|
||||
`);
|
||||
db.exec("DROP TABLE workspace_original_paths");
|
||||
db.exec("ALTER TABLE workspace_original_paths__new RENAME TO workspace_original_paths");
|
||||
db.exec("COMMIT");
|
||||
} catch (migrationError) {
|
||||
db.exec("ROLLBACK");
|
||||
throw migrationError;
|
||||
} finally {
|
||||
db.exec("PRAGMA foreign_keys = ON");
|
||||
}
|
||||
}
|
||||
db.exec(
|
||||
"CREATE INDEX IF NOT EXISTS idx_workspace_original_paths_is_starred ON workspace_original_paths(isStarred)"
|
||||
);
|
||||
db.exec("DROP INDEX IF EXISTS idx_workspace_original_paths_workspace_id");
|
||||
|
||||
db.exec(LAST_SCANNED_AT_SQL);
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { randomUUID } from 'node:crypto';
|
||||
|
||||
import { getConnection } from '@/shared/database/connection.js';
|
||||
import type { WorkspaceOriginalPathRow } from '@/shared/database/types.js';
|
||||
|
||||
@@ -5,21 +7,21 @@ export const workspaceOriginalPathsDb = {
|
||||
createWorkspacePath(workspacePath: string, customWorkspaceName: string | null = null): void {
|
||||
const db = getConnection();
|
||||
db.prepare(`
|
||||
INSERT INTO workspace_original_paths (workspace_path, custom_workspace_name)
|
||||
VALUES (?, ?)
|
||||
INSERT INTO workspace_original_paths (workspace_id, workspace_path, custom_workspace_name)
|
||||
VALUES (?, ?, ?)
|
||||
ON CONFLICT(workspace_path) DO UPDATE SET
|
||||
custom_workspace_name = CASE
|
||||
WHEN workspace_original_paths.custom_workspace_name IS NULL OR workspace_original_paths.custom_workspace_name = ''
|
||||
THEN excluded.custom_workspace_name
|
||||
ELSE workspace_original_paths.custom_workspace_name
|
||||
END
|
||||
`).run(workspacePath, customWorkspaceName);
|
||||
`).run(randomUUID(), workspacePath, customWorkspaceName);
|
||||
},
|
||||
|
||||
getWorkspacePath(workspacePath: string): WorkspaceOriginalPathRow | null {
|
||||
const db = getConnection();
|
||||
const row = db.prepare(`
|
||||
SELECT workspace_path, custom_workspace_name, isStarred
|
||||
SELECT workspace_id, workspace_path, custom_workspace_name, isStarred
|
||||
FROM workspace_original_paths
|
||||
WHERE workspace_path = ?
|
||||
`).get(workspacePath) as WorkspaceOriginalPathRow | undefined;
|
||||
@@ -27,10 +29,21 @@ export const workspaceOriginalPathsDb = {
|
||||
return row ?? null;
|
||||
},
|
||||
|
||||
getWorkspaceById(workspaceId: string): WorkspaceOriginalPathRow | null {
|
||||
const db = getConnection();
|
||||
const row = db.prepare(`
|
||||
SELECT workspace_id, workspace_path, custom_workspace_name, isStarred
|
||||
FROM workspace_original_paths
|
||||
WHERE workspace_id = ?
|
||||
`).get(workspaceId) as WorkspaceOriginalPathRow | undefined;
|
||||
|
||||
return row ?? null;
|
||||
},
|
||||
|
||||
getWorkspacePaths(): WorkspaceOriginalPathRow[] {
|
||||
const db = getConnection();
|
||||
return db.prepare(`
|
||||
SELECT workspace_path, custom_workspace_name, isStarred
|
||||
SELECT workspace_id, workspace_path, custom_workspace_name, isStarred
|
||||
FROM workspace_original_paths
|
||||
`).all() as WorkspaceOriginalPathRow[];
|
||||
},
|
||||
@@ -49,10 +62,19 @@ export const workspaceOriginalPathsDb = {
|
||||
updateCustomWorkspaceName(workspacePath: string, customWorkspaceName: string | null): void {
|
||||
const db = getConnection();
|
||||
db.prepare(`
|
||||
INSERT INTO workspace_original_paths (workspace_path, custom_workspace_name)
|
||||
VALUES (?, ?)
|
||||
INSERT INTO workspace_original_paths (workspace_id, workspace_path, custom_workspace_name)
|
||||
VALUES (?, ?, ?)
|
||||
ON CONFLICT(workspace_path) DO UPDATE SET custom_workspace_name = excluded.custom_workspace_name
|
||||
`).run(workspacePath, customWorkspaceName);
|
||||
`).run(randomUUID(), workspacePath, customWorkspaceName);
|
||||
},
|
||||
|
||||
updateCustomWorkspaceNameById(workspaceId: string, customWorkspaceName: string | null): void {
|
||||
const db = getConnection();
|
||||
db.prepare(`
|
||||
UPDATE workspace_original_paths
|
||||
SET custom_workspace_name = ?
|
||||
WHERE workspace_id = ?
|
||||
`).run(customWorkspaceName, workspaceId);
|
||||
},
|
||||
|
||||
updateWorkspaceIsStarred(workspacePath: string, isStarred: boolean): void {
|
||||
@@ -64,6 +86,15 @@ export const workspaceOriginalPathsDb = {
|
||||
`).run(isStarred ? 1 : 0, workspacePath);
|
||||
},
|
||||
|
||||
updateWorkspaceIsStarredById(workspaceId: string, isStarred: boolean): void {
|
||||
const db = getConnection();
|
||||
db.prepare(`
|
||||
UPDATE workspace_original_paths
|
||||
SET isStarred = ?
|
||||
WHERE workspace_id = ?
|
||||
`).run(isStarred ? 1 : 0, workspaceId);
|
||||
},
|
||||
|
||||
deleteWorkspacePath(workspacePath: string): void {
|
||||
const db = getConnection();
|
||||
db.prepare(`
|
||||
@@ -71,4 +102,12 @@ export const workspaceOriginalPathsDb = {
|
||||
WHERE workspace_path = ?
|
||||
`).run(workspacePath);
|
||||
},
|
||||
|
||||
deleteWorkspaceById(workspaceId: string): void {
|
||||
const db = getConnection();
|
||||
db.prepare(`
|
||||
DELETE FROM workspace_original_paths
|
||||
WHERE workspace_id = ?
|
||||
`).run(workspaceId);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -86,7 +86,8 @@ CREATE TABLE IF NOT EXISTS sessions (
|
||||
|
||||
export const WORK_SPACE_PATH_SQL = `
|
||||
CREATE TABLE IF NOT EXISTS workspace_original_paths (
|
||||
workspace_path TEXT PRIMARY KEY NOT NULL,
|
||||
workspace_id TEXT PRIMARY KEY NOT NULL,
|
||||
workspace_path TEXT NOT NULL UNIQUE,
|
||||
custom_workspace_name TEXT DEFAULT NULL,
|
||||
isStarred BOOLEAN DEFAULT 0
|
||||
);
|
||||
|
||||
@@ -129,6 +129,7 @@ export type SessionWithSummary = {
|
||||
};
|
||||
|
||||
export type WorkspaceOriginalPathRow = {
|
||||
workspace_id: string;
|
||||
workspace_path: string;
|
||||
custom_workspace_name: string | null;
|
||||
isStarred: number; // SQLite boolean: 0 | 1
|
||||
|
||||
@@ -7,6 +7,7 @@ import { RootLayout } from '@/components/refactored/shared/RootLayout';
|
||||
|
||||
// Mock page components
|
||||
const Home = () => <div className="p-8"><h1>Home Page</h1><p>Select a session or create a new project.</p></div>;
|
||||
const WorkspaceContent = () => <div className="p-8"><h1>Workspace View</h1><p>Select a session or start a new one.</p></div>;
|
||||
const SessionContent = () => <div className="p-8"><h1>Session View</h1><p>Chat interface goes here.</p></div>;
|
||||
|
||||
const router = createBrowserRouter([
|
||||
@@ -26,6 +27,10 @@ const router = createBrowserRouter([
|
||||
path: "/sessions/:sessionId",
|
||||
element: <SessionContent />,
|
||||
},
|
||||
{
|
||||
path: "/workspace/:workspaceId",
|
||||
element: <WorkspaceContent />,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -57,16 +57,16 @@ export const getWorkspaceSessions = async (): Promise<WorkspaceRecord[]> => {
|
||||
};
|
||||
|
||||
export const updateWorkspaceStar = async (
|
||||
workspacePath: string,
|
||||
): Promise<{ workspacePath: string; isStarred: boolean }> => {
|
||||
workspaceId: string,
|
||||
): Promise<{ workspaceId: string; isStarred: boolean }> => {
|
||||
const response = await authenticatedFetch(SIDEBAR_ENDPOINTS.updateWorkspaceStar, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({ workspacePath }),
|
||||
body: JSON.stringify({ workspaceId }),
|
||||
});
|
||||
const payload = await parseJsonSafely<{
|
||||
success?: boolean;
|
||||
data?: {
|
||||
workspacePath?: string;
|
||||
workspaceId?: string;
|
||||
isStarred?: boolean;
|
||||
};
|
||||
error?: { message?: string };
|
||||
@@ -80,18 +80,18 @@ export const updateWorkspaceStar = async (
|
||||
}
|
||||
|
||||
return {
|
||||
workspacePath: payload?.data?.workspacePath || workspacePath,
|
||||
workspaceId: payload?.data?.workspaceId || workspaceId,
|
||||
isStarred: Boolean(payload?.data?.isStarred),
|
||||
};
|
||||
};
|
||||
|
||||
export const updateWorkspaceCustomName = async (
|
||||
workspacePath: string,
|
||||
workspaceId: string,
|
||||
workspaceCustomName: string | null,
|
||||
): Promise<void> => {
|
||||
const response = await authenticatedFetch(SIDEBAR_ENDPOINTS.updateWorkspaceCustomName, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({ workspacePath, workspaceCustomName }),
|
||||
body: JSON.stringify({ workspaceId, workspaceCustomName }),
|
||||
});
|
||||
const payload = await parseJsonSafely<{ error?: { message?: string } }>(response);
|
||||
|
||||
@@ -103,10 +103,10 @@ export const updateWorkspaceCustomName = async (
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteWorkspaceByPath = async (workspacePath: string): Promise<void> => {
|
||||
export const deleteWorkspaceById = async (workspaceId: string): Promise<void> => {
|
||||
const response = await authenticatedFetch(SIDEBAR_ENDPOINTS.deleteWorkspace, {
|
||||
method: 'DELETE',
|
||||
body: JSON.stringify({ workspacePath }),
|
||||
body: JSON.stringify({ workspaceId }),
|
||||
});
|
||||
const payload = await parseJsonSafely<{ error?: { message?: string } }>(response);
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useLocation, useNavigate } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
deleteSessionById,
|
||||
deleteWorkspaceByPath,
|
||||
deleteWorkspaceById,
|
||||
getWorkspaceSessions,
|
||||
updateSessionCustomName,
|
||||
updateWorkspaceCustomName,
|
||||
@@ -95,7 +95,7 @@ export const useWorkspaces = () => {
|
||||
[filteredWorkspaces],
|
||||
);
|
||||
|
||||
const toggleWorkspace = useCallback((workspacePath: string) => {
|
||||
const toggleWorkspace = useCallback((workspaceId: string, workspacePath: string) => {
|
||||
setExpandedWorkspaces((previousSet) => {
|
||||
const nextSet = new Set(previousSet);
|
||||
|
||||
@@ -107,7 +107,8 @@ export const useWorkspaces = () => {
|
||||
|
||||
return nextSet;
|
||||
});
|
||||
}, []);
|
||||
navigate(`/workspace/${encodeURIComponent(workspaceId)}`);
|
||||
}, [navigate]);
|
||||
|
||||
const openSession = useCallback(
|
||||
(workspacePath: string, sessionId: string) => {
|
||||
@@ -125,9 +126,9 @@ export const useWorkspaces = () => {
|
||||
navigate('/');
|
||||
}, [navigate]);
|
||||
|
||||
const toggleWorkspaceStar = useCallback(async (workspacePath: string) => {
|
||||
const toggleWorkspaceStar = useCallback(async (workspaceId: string) => {
|
||||
try {
|
||||
await updateWorkspaceStar(workspacePath);
|
||||
await updateWorkspaceStar(workspaceId);
|
||||
await refreshWorkspaces();
|
||||
} catch (error) {
|
||||
console.error('Failed to update workspace star:', error);
|
||||
@@ -149,10 +150,17 @@ export const useWorkspaces = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const editingWorkspace = workspaces.find(
|
||||
(workspace) => workspace.workspaceOriginalPath === editingWorkspacePath,
|
||||
);
|
||||
if (!editingWorkspace) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSavingWorkspaceName(true);
|
||||
try {
|
||||
const trimmedName = editingWorkspaceName.trim();
|
||||
await updateWorkspaceCustomName(editingWorkspacePath, trimmedName || null);
|
||||
await updateWorkspaceCustomName(editingWorkspace.workspaceId, trimmedName || null);
|
||||
await refreshWorkspaces();
|
||||
cancelWorkspaceRename();
|
||||
} catch (error) {
|
||||
@@ -165,10 +173,12 @@ export const useWorkspaces = () => {
|
||||
editingWorkspaceName,
|
||||
editingWorkspacePath,
|
||||
refreshWorkspaces,
|
||||
workspaces,
|
||||
]);
|
||||
|
||||
const requestWorkspaceDelete = useCallback((workspace: WorkspaceRecord) => {
|
||||
setWorkspaceDeleteTarget({
|
||||
workspaceId: workspace.workspaceId,
|
||||
workspacePath: workspace.workspaceOriginalPath,
|
||||
workspaceName: getWorkspaceDisplayName(workspace),
|
||||
sessionCount: workspace.sessions.length,
|
||||
@@ -184,10 +194,11 @@ export const useWorkspaces = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const deletingWorkspaceId = workspaceDeleteTarget.workspaceId;
|
||||
const deletingWorkspacePath = workspaceDeleteTarget.workspacePath;
|
||||
setWorkspaceDeleteTarget(null);
|
||||
try {
|
||||
await deleteWorkspaceByPath(deletingWorkspacePath);
|
||||
await deleteWorkspaceById(deletingWorkspaceId);
|
||||
|
||||
// If the current session belonged to the deleted workspace, reset to root.
|
||||
const hadSelectedSession = workspaces.some(
|
||||
|
||||
@@ -15,6 +15,7 @@ export type WorkspaceSession = {
|
||||
};
|
||||
|
||||
export type WorkspaceRecord = {
|
||||
workspaceId: string;
|
||||
workspaceOriginalPath: string;
|
||||
workspaceCustomName: string | null;
|
||||
workspaceDisplayName: string;
|
||||
@@ -24,6 +25,7 @@ export type WorkspaceRecord = {
|
||||
};
|
||||
|
||||
export type WorkspaceDeleteTarget = {
|
||||
workspaceId: string;
|
||||
workspacePath: string;
|
||||
workspaceName: string;
|
||||
sessionCount: number;
|
||||
|
||||
@@ -29,8 +29,8 @@ type SidebarWorkspaceItemProps = {
|
||||
isSavingSessionName: boolean;
|
||||
onEditingWorkspaceNameChange: (name: string) => void;
|
||||
onEditingSessionNameChange: (name: string) => void;
|
||||
onToggleWorkspace: (workspacePath: string) => void;
|
||||
onToggleWorkspaceStar: (workspacePath: string) => void;
|
||||
onToggleWorkspace: (workspaceId: string, workspacePath: string) => void;
|
||||
onToggleWorkspaceStar: (workspaceId: string) => void;
|
||||
onStartWorkspaceRename: (workspace: WorkspaceRecord) => void;
|
||||
onCancelWorkspaceRename: () => void;
|
||||
onSaveWorkspaceRename: () => void;
|
||||
@@ -95,7 +95,7 @@ export function SidebarWorkspaceItem({
|
||||
!hasSelectedSession &&
|
||||
'border-yellow-200/30 bg-yellow-50/50 dark:border-yellow-800/30 dark:bg-yellow-900/5',
|
||||
)}
|
||||
onClick={() => onToggleWorkspace(workspace.workspaceOriginalPath)}
|
||||
onClick={() => onToggleWorkspace(workspace.workspaceId, workspace.workspaceOriginalPath)}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex min-w-0 flex-1 items-center gap-3">
|
||||
@@ -179,7 +179,7 @@ export function SidebarWorkspaceItem({
|
||||
)}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onToggleWorkspaceStar(workspace.workspaceOriginalPath);
|
||||
onToggleWorkspaceStar(workspace.workspaceId);
|
||||
}}
|
||||
title={workspace.isStarred ? 'Remove from Starred' : 'Add to Starred'}
|
||||
>
|
||||
@@ -236,7 +236,7 @@ export function SidebarWorkspaceItem({
|
||||
!hasSelectedSession &&
|
||||
'bg-yellow-50/50 hover:bg-yellow-100/50 dark:bg-yellow-900/10 dark:hover:bg-yellow-900/20',
|
||||
)}
|
||||
onClick={() => onToggleWorkspace(workspace.workspaceOriginalPath)}
|
||||
onClick={() => onToggleWorkspace(workspace.workspaceId, workspace.workspaceOriginalPath)}
|
||||
>
|
||||
<div className="flex min-w-0 flex-1 items-center gap-3">
|
||||
{isExpanded ? (
|
||||
@@ -318,7 +318,7 @@ export function SidebarWorkspaceItem({
|
||||
)}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onToggleWorkspaceStar(workspace.workspaceOriginalPath);
|
||||
onToggleWorkspaceStar(workspace.workspaceId);
|
||||
}}
|
||||
title={workspace.isStarred ? 'Remove from Starred' : 'Add to Starred'}
|
||||
>
|
||||
|
||||
@@ -16,8 +16,8 @@ type SidebarWorkspaceListProps = {
|
||||
isSavingSessionName: boolean;
|
||||
onEditingWorkspaceNameChange: (name: string) => void;
|
||||
onEditingSessionNameChange: (name: string) => void;
|
||||
onToggleWorkspace: (workspacePath: string) => void;
|
||||
onToggleWorkspaceStar: (workspacePath: string) => void;
|
||||
onToggleWorkspace: (workspaceId: string, workspacePath: string) => void;
|
||||
onToggleWorkspaceStar: (workspaceId: string) => void;
|
||||
onStartWorkspaceRename: (workspace: WorkspaceRecord) => void;
|
||||
onCancelWorkspaceRename: () => void;
|
||||
onSaveWorkspaceRename: () => void;
|
||||
|
||||
Reference in New Issue
Block a user