Files
paseo/packages/app/src/utils/sidebar-shortcuts.ts
T
Mohamed BoudraandGitHub 99eca19a76 Keep selected projects when switching hosts (#2700)
* fix(app): preserve project selection across hosts

Rendered project identity and persisted equivalence were conflated after duplicate-project handling. Keep them separate so host changes resolve selectable equivalents deterministically without resetting legacy sidebar identity.

* fix(app): restore selected clone after host round trip

Keep manual origin and current destination placements separately so switching back restores the chosen checkout while archive gaps preserve the active host selection.

* fix(app): key status icons by project view

* fix(app): preserve origin during archive gaps
2026-07-31 15:42:08 +02:00

112 lines
3.3 KiB
TypeScript

import type {
SidebarProjectEntry,
SidebarWorkspacePlacement,
} from "@/hooks/use-sidebar-workspaces-list";
import type { StatusGroup } from "@/hooks/sidebar-status-view-model";
export interface SidebarShortcutWorkspaceTarget {
serverId: string;
workspaceId: string;
}
export interface SidebarShortcutModel {
shortcutTargets: SidebarShortcutWorkspaceTarget[];
shortcutIndexByWorkspaceKey: Map<string, number>;
}
export interface SidebarShortcutSection {
workspaces: readonly SidebarWorkspacePlacement[];
collapsed?: boolean;
}
function createShortcutTarget(
workspace: SidebarWorkspacePlacement,
): SidebarShortcutWorkspaceTarget {
return {
serverId: workspace.serverId,
workspaceId: workspace.workspaceId,
};
}
export function buildSidebarShortcutModel(input: {
projects: SidebarProjectEntry[];
collapsedProjectKeys: ReadonlySet<string>;
shortcutLimit?: number;
}): SidebarShortcutModel {
return buildSidebarShortcutSections({
sections: input.projects.map((project) => ({
workspaces: project.workspaces,
collapsed: input.collapsedProjectKeys.has(project.viewKey),
})),
shortcutLimit: input.shortcutLimit,
});
}
export function buildStatusSidebarShortcutModel(input: {
groups: readonly StatusGroup[];
collapsedStatusGroupKeys?: ReadonlySet<string>;
shortcutLimit?: number;
}): SidebarShortcutModel {
return buildSidebarShortcutSections({
sections: input.groups.map((group) => ({
workspaces: group.rows,
collapsed: input.collapsedStatusGroupKeys?.has(group.bucket),
})),
shortcutLimit: input.shortcutLimit,
});
}
export function buildSidebarShortcutSections(input: {
sections: readonly SidebarShortcutSection[];
shortcutLimit?: number;
}): SidebarShortcutModel {
const maxShortcuts = Math.max(0, Math.floor(input.shortcutLimit ?? 9));
const shortcutTargets: SidebarShortcutWorkspaceTarget[] = [];
const shortcutIndexByWorkspaceKey = new Map<string, number>();
for (const section of input.sections) {
if (section.collapsed) {
continue;
}
for (const workspace of section.workspaces) {
if (shortcutTargets.length >= maxShortcuts) {
break;
}
const shortcutNumber = shortcutTargets.length + 1;
shortcutTargets.push(createShortcutTarget(workspace));
shortcutIndexByWorkspaceKey.set(workspace.workspaceKey, shortcutNumber);
}
}
return { shortcutTargets, shortcutIndexByWorkspaceKey };
}
export function getRelativeSidebarShortcutTarget(input: {
targets: readonly SidebarShortcutWorkspaceTarget[];
currentTarget: SidebarShortcutWorkspaceTarget | null;
delta: 1 | -1;
}): SidebarShortcutWorkspaceTarget | null {
if (input.targets.length === 0) {
return null;
}
if (!input.currentTarget) {
return input.targets[input.delta > 0 ? 0 : input.targets.length - 1] ?? null;
}
const currentTarget = input.currentTarget;
const currentIndex = input.targets.findIndex(
(target) =>
target.serverId === currentTarget.serverId &&
target.workspaceId === currentTarget.workspaceId,
);
if (currentIndex < 0) {
return input.targets[input.delta > 0 ? 0 : input.targets.length - 1] ?? null;
}
const nextIndex = (currentIndex + input.delta + input.targets.length) % input.targets.length;
return input.targets[nextIndex] ?? null;
}