feat: sortable workspace tabs, tab context menus, and terminal auto-create removal
- Add drag-and-drop tab reordering with persistent tab order - Add right-click context menu with copy agent ID, copy resume command, close to right - Switch workspace route encoding from percent-encoding to base64url - Remove terminal auto-creation on directory listing (explicit create only) - Add swipe gestures on terminal emulator for mobile sidebar navigation - Highlight active workspace row in sidebar
This commit is contained in:
@@ -6,6 +6,22 @@ export type WorkspaceTabTarget =
|
||||
| { kind: "agent"; agentId: string }
|
||||
| { kind: "terminal"; terminalId: string };
|
||||
|
||||
function normalizeKeys(keys: string[]): string[] {
|
||||
const seen = new Set<string>();
|
||||
const normalized: string[] = [];
|
||||
|
||||
for (const rawKey of keys) {
|
||||
const key = rawKey.trim();
|
||||
if (!key || seen.has(key)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(key);
|
||||
normalized.push(key);
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function trimNonEmpty(value: string | null | undefined): string | null {
|
||||
if (typeof value !== "string") {
|
||||
return null;
|
||||
@@ -51,6 +67,7 @@ export function buildWorkspaceTabPersistenceKey(input: {
|
||||
|
||||
type WorkspaceTabsState = {
|
||||
lastFocusedTabByWorkspace: Record<string, WorkspaceTabTarget>;
|
||||
tabOrderByWorkspace: Record<string, string[]>;
|
||||
setLastFocusedTab: (input: {
|
||||
serverId: string;
|
||||
workspaceId: string;
|
||||
@@ -60,12 +77,18 @@ type WorkspaceTabsState = {
|
||||
serverId: string;
|
||||
workspaceId: string;
|
||||
}) => WorkspaceTabTarget | null;
|
||||
setTabOrder: (input: {
|
||||
serverId: string;
|
||||
workspaceId: string;
|
||||
keys: string[];
|
||||
}) => void;
|
||||
};
|
||||
|
||||
export const useWorkspaceTabsStore = create<WorkspaceTabsState>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
lastFocusedTabByWorkspace: {},
|
||||
tabOrderByWorkspace: {},
|
||||
setLastFocusedTab: ({ serverId, workspaceId, tab }) => {
|
||||
const key = buildWorkspaceTabPersistenceKey({ serverId, workspaceId });
|
||||
const normalizedTab = normalizeWorkspaceTab(tab);
|
||||
@@ -103,15 +126,45 @@ export const useWorkspaceTabsStore = create<WorkspaceTabsState>()(
|
||||
const value = get().lastFocusedTabByWorkspace[key];
|
||||
return normalizeWorkspaceTab(value);
|
||||
},
|
||||
setTabOrder: ({ serverId, workspaceId, keys }) => {
|
||||
const key = buildWorkspaceTabPersistenceKey({ serverId, workspaceId });
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
const normalized = normalizeKeys(keys);
|
||||
set((state) => {
|
||||
const current = state.tabOrderByWorkspace[key] ?? [];
|
||||
if (current.length === normalized.length) {
|
||||
let isSame = true;
|
||||
for (let index = 0; index < current.length; index += 1) {
|
||||
if (current[index] !== normalized[index]) {
|
||||
isSame = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isSame) {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
tabOrderByWorkspace: {
|
||||
...state.tabOrderByWorkspace,
|
||||
[key]: normalized,
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: "workspace-tabs-state",
|
||||
version: 1,
|
||||
version: 2,
|
||||
storage: createJSONStorage(() => AsyncStorage),
|
||||
migrate: (persistedState) => {
|
||||
const state = persistedState as
|
||||
| {
|
||||
lastFocusedTabByWorkspace?: Record<string, WorkspaceTabTarget>;
|
||||
tabOrderByWorkspace?: Record<string, string[]>;
|
||||
}
|
||||
| undefined;
|
||||
|
||||
@@ -126,9 +179,23 @@ export const useWorkspaceTabsStore = create<WorkspaceTabsState>()(
|
||||
}
|
||||
}
|
||||
|
||||
const rawOrder = state?.tabOrderByWorkspace ?? {};
|
||||
const nextOrder: Record<string, string[]> = {};
|
||||
for (const key in rawOrder) {
|
||||
const list = rawOrder[key];
|
||||
if (!Array.isArray(list)) {
|
||||
continue;
|
||||
}
|
||||
const normalized = normalizeKeys(list.map((value) => String(value)));
|
||||
if (normalized.length > 0) {
|
||||
nextOrder[key] = normalized;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
lastFocusedTabByWorkspace: next,
|
||||
tabOrderByWorkspace: nextOrder,
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user