This commit is contained in:
2026-02-19 12:03:45 +00:00
parent dd8ab84df5
commit 111d4c86eb
12 changed files with 174 additions and 183 deletions
@@ -1,3 +1,4 @@
import { Link, useLocation, useNavigate } from 'react-router';
import { LayoutGrid, Plus, Pencil, Trash2 } from 'lucide-react';
import { useQueryClient } from '@tanstack/react-query';
import { useGlobal } from 'hooks/useGlobal';
@@ -14,6 +15,8 @@ import {
} from './constants';
export const WorkspaceListApp = () => {
const location = useLocation();
const navigate = useNavigate();
const client = useClient();
const queryClient = useQueryClient();
const [workspaces, setWorkspaces] = useUserState<WorkspaceDefinition[]>('workspaces', []);
@@ -24,6 +27,8 @@ export const WorkspaceListApp = () => {
const [, setDescription] = useGlobal<string>(NEW_WS_DESC_KEY, '');
const [, setTemplateIdx] = useGlobal<number>(NEW_WS_TEMPLATE_KEY, 0);
const isWorkspacesPage = location.pathname === '/workspaces';
const handleEdit = (ev: React.MouseEvent, ws: WorkspaceDefinition) => {
ev.stopPropagation();
setSelected(null);
@@ -39,7 +44,6 @@ export const WorkspaceListApp = () => {
setWorkspaces((prev) => prev.filter((w) => w.id !== ws.id));
if (selected === ws.id) setSelected(null);
// Clean up persisted layout
const layoutKey = `ws-layout-${ws.id}`;
const currentState = queryClient.getQueryData<Record<string, unknown>>(['USER_STATE']) ?? {};
const { [layoutKey]: _, ...rest } = currentState;
@@ -47,62 +51,77 @@ export const WorkspaceListApp = () => {
client.patch('/user/state', { [layoutKey]: null }).catch(() => {});
};
const handleClick = (ws: WorkspaceDefinition) => {
if (isWorkspacesPage) {
setSelected(ws.id);
setCreating(false);
setEditing(null);
} else {
navigate(`/workspaces/${ws.id}`);
}
};
return (
<div className="flex flex-col h-full overflow-y-auto">
<div className="p-3 pb-0">
<div className="flex items-center gap-2.5 rounded-lg px-3 py-2 text-sm font-medium bg-duck-teal/15 text-duck-teal">
<Link
to="/workspaces"
className="flex items-center gap-2.5 rounded-lg px-3 py-2 text-sm font-medium bg-duck-teal/15 text-duck-teal"
>
<LayoutGrid className="h-4 w-4" />
Workspaces
</div>
</Link>
</div>
<div className="flex flex-col gap-0.5 px-3 pt-3">
{workspaces.map((ws) => (
<div
key={ws.id}
className={`flex items-center gap-2.5 py-2 px-3 rounded-lg text-sm font-medium cursor-pointer group ${
selected === ws.id
isWorkspacesPage && selected === ws.id
? 'bg-duck-teal/10 text-duck-teal'
: 'text-duck-dark/60 hover:bg-duck-dark/5'
}`}
onClick={() => {
setSelected(ws.id);
setCreating(false);
setEditing(null);
}}
onClick={() => handleClick(ws)}
>
<LayoutGrid className="h-4 w-4 shrink-0" />
<span className="flex-1 text-left truncate">{ws.name}</span>
<button
type="button"
onClick={(ev) => handleEdit(ev, ws)}
className="shrink-0 p-1 rounded text-duck-dark/20 md:opacity-0 md:group-hover:opacity-100 hover:text-duck-teal transition-opacity cursor-pointer"
>
<Pencil className="h-3.5 w-3.5" />
</button>
<button
type="button"
onClick={(ev) => handleDelete(ev, ws)}
className="shrink-0 p-1 rounded text-duck-dark/20 md:opacity-0 md:group-hover:opacity-100 hover:text-red-500 transition-opacity cursor-pointer"
>
<Trash2 className="h-3.5 w-3.5" />
</button>
{isWorkspacesPage && (
<>
<button
type="button"
onClick={(ev) => handleEdit(ev, ws)}
className="shrink-0 p-1 rounded text-duck-dark/20 md:opacity-0 md:group-hover:opacity-100 hover:text-duck-teal transition-opacity cursor-pointer"
>
<Pencil className="h-3.5 w-3.5" />
</button>
<button
type="button"
onClick={(ev) => handleDelete(ev, ws)}
className="shrink-0 p-1 rounded text-duck-dark/20 md:opacity-0 md:group-hover:opacity-100 hover:text-red-500 transition-opacity cursor-pointer"
>
<Trash2 className="h-3.5 w-3.5" />
</button>
</>
)}
</div>
))}
{workspaces.length === 0 && (
<p className="text-xs text-duck-dark/40 px-3 py-4 text-center">No workspaces yet</p>
)}
<button
type="button"
onClick={() => {
setSelected(null);
setEditing(null);
setCreating(true);
}}
className="flex items-center gap-2.5 py-2 px-3 rounded-lg text-sm font-medium text-duck-dark/40 hover:bg-duck-dark/5 hover:text-duck-dark transition-all cursor-pointer"
>
<Plus className="h-4 w-4 shrink-0" />
<span className="flex-1 text-left">New workspace</span>
</button>
{isWorkspacesPage && (
<button
type="button"
onClick={() => {
setSelected(null);
setEditing(null);
setCreating(true);
}}
className="flex items-center gap-2.5 py-2 px-3 rounded-lg text-sm font-medium text-duck-dark/40 hover:bg-duck-dark/5 hover:text-duck-dark transition-all cursor-pointer"
>
<Plus className="h-4 w-4 shrink-0" />
<span className="flex-1 text-left">New workspace</span>
</button>
)}
</div>
</div>
);
@@ -6,9 +6,10 @@ import { appRegistry } from './app-registry';
export const WorkspaceScreen = () => {
const { id } = useParams<{ id: string }>();
const [workspaces] = useUserState<WorkspaceDefinition[]>('workspaces', []);
const [workspaces, , isLoaded] = useUserState<WorkspaceDefinition[]>('workspaces', []);
const workspace = workspaces.find((ws) => ws.id === id);
if (!isLoaded) return null;
if (!workspace) return <Navigate to="/workspaces" replace />;
return <WorkspaceScreenInner workspace={workspace} />;