* 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
78 lines
2.3 KiB
TypeScript
78 lines
2.3 KiB
TypeScript
import type { ProjectSummary } from "@/utils/projects";
|
|
import { shortenPath } from "@/utils/shorten-path";
|
|
|
|
export const PROJECT_OPTION_PREFIX = "project:";
|
|
|
|
export interface ScheduleProjectTarget {
|
|
optionId: string;
|
|
serverId: string;
|
|
serverName: string;
|
|
projectViewKey: string;
|
|
projectName: string;
|
|
cwd: string;
|
|
isGit: boolean;
|
|
}
|
|
|
|
export function buildProjectOptionId(serverId: string, projectViewKey: string): string {
|
|
return `${PROJECT_OPTION_PREFIX}${serverId}:${projectViewKey}`;
|
|
}
|
|
|
|
/**
|
|
* The project roots the schedule form can target: one per online host of each
|
|
* project, keyed by (serverId, cwd). The schedules list reuses this set to name
|
|
* a schedule's stored cwd; the two surfaces must agree on what "a project" is.
|
|
*/
|
|
export function buildScheduleProjectTargets(
|
|
projects: readonly ProjectSummary[],
|
|
): ScheduleProjectTarget[] {
|
|
const targets: ScheduleProjectTarget[] = [];
|
|
for (const project of projects) {
|
|
for (const host of project.hosts) {
|
|
const cwd = host.repoRoot.trim();
|
|
if (!host.isOnline || !cwd) {
|
|
continue;
|
|
}
|
|
targets.push({
|
|
optionId: buildProjectOptionId(host.serverId, project.viewKey),
|
|
serverId: host.serverId,
|
|
serverName: host.serverName,
|
|
projectViewKey: project.viewKey,
|
|
projectName: host.projectName,
|
|
cwd,
|
|
isGit: Boolean(host.gitRuntime),
|
|
});
|
|
}
|
|
}
|
|
return targets;
|
|
}
|
|
|
|
function projectNameKey(serverId: string, cwd: string): string {
|
|
return `${serverId}:${cwd.trim()}`;
|
|
}
|
|
|
|
/** Map (serverId, cwd) -> project name for naming a schedule's stored cwd. */
|
|
export function buildProjectNameByCwd(
|
|
targets: readonly ScheduleProjectTarget[],
|
|
): Map<string, string> {
|
|
const byCwd = new Map<string, string>();
|
|
for (const target of targets) {
|
|
byCwd.set(projectNameKey(target.serverId, target.cwd), target.projectName);
|
|
}
|
|
return byCwd;
|
|
}
|
|
|
|
/**
|
|
* Name a stored cwd for display: the matching project name when the client
|
|
* knows this root on this host, otherwise the shortened path itself. Never
|
|
* blank, never a claim the client cannot back up.
|
|
*/
|
|
export function describeScheduleCwd(input: {
|
|
serverId: string;
|
|
cwd: string;
|
|
projectNameByCwd: ReadonlyMap<string, string>;
|
|
}): string {
|
|
return (
|
|
input.projectNameByCwd.get(projectNameKey(input.serverId, input.cwd)) ?? shortenPath(input.cwd)
|
|
);
|
|
}
|