feat: add mobile project editor with worktree reorder and delete
Replace the up/down arrows in the mobile project reorder list (drag already covers reordering) with an edit button that opens a dedicated project editor surface: rename, pick icon and color, and discover a favicon (no upload). The editor lists the project's worktrees with drag-to-reorder (persisted per project, like project order) and a delete button that opens a mobile confirmation built on the shared worktree primitives — archiving attached sessions and optionally removing the local/remote branch.
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
import type { WorktreeMetadata } from '@/types/worktree';
|
||||
|
||||
/**
|
||||
* Persisted display order for worktrees within a project, mirroring how
|
||||
* projects persist their order (the array position IS the order). Keyed by
|
||||
* project id, the value is an ordered list of normalized worktree paths.
|
||||
*
|
||||
* Worktrees come from git and are otherwise listed alphabetically; this store
|
||||
* lets the user reorder them (e.g. in the mobile project editor) and have that
|
||||
* order stick across restarts.
|
||||
*/
|
||||
type WorktreeOrderStore = {
|
||||
orderByProject: Record<string, string[]>;
|
||||
setWorktreeOrder: (projectId: string, orderedPaths: string[]) => void;
|
||||
};
|
||||
|
||||
export const useWorktreeOrderStore = create<WorktreeOrderStore>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
orderByProject: {},
|
||||
setWorktreeOrder: (projectId, orderedPaths) =>
|
||||
set((state) => ({ orderByProject: { ...state.orderByProject, [projectId]: orderedPaths } })),
|
||||
}),
|
||||
{
|
||||
name: 'mobile-worktree-order',
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
const normalizeWorktreePath = (value: string): string =>
|
||||
value.replace(/\\/g, '/').replace(/\/+$/, '');
|
||||
|
||||
/**
|
||||
* Stable-sort worktrees by a stored order of paths. Worktrees not present in
|
||||
* the stored order keep their incoming (alphabetical) order, appended after
|
||||
* the known ones.
|
||||
*/
|
||||
export const orderWorktrees = (
|
||||
orderedPaths: string[] | undefined,
|
||||
worktrees: WorktreeMetadata[],
|
||||
): WorktreeMetadata[] => {
|
||||
if (!orderedPaths || orderedPaths.length === 0) return worktrees;
|
||||
const rank = new Map(orderedPaths.map((path, index) => [normalizeWorktreePath(path), index] as const));
|
||||
const rankOf = (worktree: WorktreeMetadata): number =>
|
||||
rank.get(normalizeWorktreePath(worktree.path)) ?? Number.MAX_SAFE_INTEGER;
|
||||
return worktrees
|
||||
.map((worktree, index) => ({ worktree, index }))
|
||||
.sort((a, b) => {
|
||||
const byRank = rankOf(a.worktree) - rankOf(b.worktree);
|
||||
// Preserve incoming order for ties (unknown worktrees / equal ranks).
|
||||
return byRank !== 0 ? byRank : a.index - b.index;
|
||||
})
|
||||
.map((entry) => entry.worktree);
|
||||
};
|
||||
Reference in New Issue
Block a user