feat(app): sidebar favorites, drag-reorder, and delete-with-confirm
- FAVORITES section at the top collects pinned workspaces (removed from their group listing); a star toggle on each row pins/unpins via setWorkspaceMeta. - Drag-to-reorder within a section using raw pointer events (HTML5 DnD is unreliable in the macOS WKWebView), with a drop-line indicator; on drop the section's `order` is reassigned sequentially and persisted. Cross-section drops are ignored (group membership unchanged). - Trash icon on row hover opens a ConfirmDelete modal that shows the live terminal count and warns before terminating them, then calls close_workspace and re-points the active workspace. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { COLORS, FONT } from "./theme";
|
||||
|
||||
/** Confirmation modal for deleting a workspace. Warns when live terminals
|
||||
* would be killed, but still allows the delete (per product decision). */
|
||||
export function ConfirmDelete({
|
||||
name,
|
||||
activeCount,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
}: {
|
||||
name: string;
|
||||
activeCount: number;
|
||||
onConfirm: () => void;
|
||||
onCancel: () => void;
|
||||
}) {
|
||||
const confirmRef = useRef<HTMLButtonElement>(null);
|
||||
useEffect(() => { confirmRef.current?.focus(); }, []);
|
||||
|
||||
function onKeyDown(e: React.KeyboardEvent) {
|
||||
e.stopPropagation();
|
||||
if (e.key === "Escape") { e.preventDefault(); onCancel(); }
|
||||
else if (e.key === "Enter") { e.preventDefault(); onConfirm(); }
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
onMouseDown={onCancel}
|
||||
style={{ position: "fixed", inset: 0, zIndex: 2000, background: "#000A", display: "flex", alignItems: "center", justifyContent: "center" }}
|
||||
>
|
||||
<div
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
onKeyDown={onKeyDown}
|
||||
style={{ width: 420, background: COLORS.bgApp, border: `1px solid ${COLORS.borderStrong}`, borderRadius: 14, padding: 24, color: COLORS.textPrimary, fontFamily: FONT.ui }}
|
||||
>
|
||||
<div style={{ fontWeight: 700, fontSize: 16, marginBottom: 12 }}>Delete workspace</div>
|
||||
<div style={{ fontSize: 13, color: COLORS.textSecondary, lineHeight: 1.5 }}>
|
||||
Delete <span style={{ color: COLORS.textPrimary, fontWeight: 600 }}>{name}</span>? This removes the workspace and its layout.
|
||||
</div>
|
||||
{activeCount > 0 && (
|
||||
<div style={{ marginTop: 12, padding: "8px 10px", borderRadius: 8, background: "#2A1414", border: `1px solid ${COLORS.stError}`, color: COLORS.stError, fontSize: 12, fontWeight: 600 }}>
|
||||
{activeCount} active terminal{activeCount === 1 ? "" : "s"} will be terminated.
|
||||
</div>
|
||||
)}
|
||||
<div style={{ display: "flex", justifyContent: "flex-end", gap: 8, marginTop: 20 }}>
|
||||
<button
|
||||
onClick={onCancel}
|
||||
style={{ padding: "7px 14px", background: COLORS.bgElevated, color: COLORS.textPrimary, border: `1px solid ${COLORS.borderStrong}`, borderRadius: 7, fontSize: 13, fontFamily: FONT.ui }}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
ref={confirmRef}
|
||||
onClick={onConfirm}
|
||||
style={{ padding: "7px 14px", background: COLORS.stError, color: "#fff", border: "none", borderRadius: 7, fontSize: 13, fontWeight: 600, fontFamily: FONT.ui }}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user