102 lines
3.2 KiB
TypeScript
102 lines
3.2 KiB
TypeScript
import { normalizePath } from '@renderer/utils/pathNormalize';
|
|
import { isEphemeralProjectPath } from '@shared/utils/ephemeralProjectPath';
|
|
|
|
import type {
|
|
DashboardRecentProjectFilesystemState,
|
|
DashboardRecentProjectSource,
|
|
} from '@features/recent-projects/contracts';
|
|
import type { ComboboxOption } from '@renderer/components/ui/combobox';
|
|
import type { Project } from '@shared/types';
|
|
|
|
export interface ProjectPathProject extends Project {
|
|
discoverySource?: DashboardRecentProjectSource;
|
|
filesystemState?: DashboardRecentProjectFilesystemState;
|
|
}
|
|
|
|
export interface ProjectPathOptionMeta extends Record<string, unknown> {
|
|
discoverySource?: DashboardRecentProjectSource;
|
|
filesystemState?: DashboardRecentProjectFilesystemState;
|
|
}
|
|
|
|
function toProjectOption(project: ProjectPathProject): ComboboxOption {
|
|
const option: ComboboxOption = {
|
|
value: project.path,
|
|
label: project.name,
|
|
description: project.path,
|
|
};
|
|
|
|
if (project.filesystemState === 'deleted') {
|
|
option.disabled = true;
|
|
}
|
|
|
|
if (project.discoverySource !== undefined || project.filesystemState !== undefined) {
|
|
const meta: ProjectPathOptionMeta = {};
|
|
if (project.discoverySource !== undefined) {
|
|
meta.discoverySource = project.discoverySource;
|
|
}
|
|
if (project.filesystemState !== undefined) {
|
|
meta.filesystemState = project.filesystemState;
|
|
}
|
|
option.meta = meta;
|
|
}
|
|
|
|
return option;
|
|
}
|
|
|
|
export function isSelectableProjectPathProject(
|
|
project: Pick<ProjectPathProject, 'path' | 'filesystemState'>
|
|
): boolean {
|
|
return !isEphemeralProjectPath(project.path) && project.filesystemState !== 'deleted';
|
|
}
|
|
|
|
export function findProjectPathProjectByPath(
|
|
projects: readonly ProjectPathProject[],
|
|
projectPath: string
|
|
): ProjectPathProject | undefined {
|
|
const normalizedPath = normalizePath(projectPath);
|
|
return projects.find((project) => normalizePath(project.path) === normalizedPath);
|
|
}
|
|
|
|
export function isDeletedProjectPathSelection(
|
|
projects: readonly ProjectPathProject[],
|
|
projectPath: string
|
|
): boolean {
|
|
return findProjectPathProjectByPath(projects, projectPath)?.filesystemState === 'deleted';
|
|
}
|
|
|
|
/**
|
|
* Collapse duplicate project entries that resolve to the same filesystem path.
|
|
* This keeps combobox item values unique even when scanner sources overlap.
|
|
*/
|
|
export function buildProjectPathOptions(
|
|
projects: ProjectPathProject[],
|
|
preferredPath?: string
|
|
): ComboboxOption[] {
|
|
const options: ComboboxOption[] = [];
|
|
const optionIndexByNormalizedPath = new Map<string, number>();
|
|
const normalizedPreferredPath = preferredPath ? normalizePath(preferredPath) : null;
|
|
|
|
for (const project of projects) {
|
|
if (isEphemeralProjectPath(project.path)) {
|
|
continue;
|
|
}
|
|
|
|
const normalizedProjectPath = normalizePath(project.path);
|
|
const existingIndex = optionIndexByNormalizedPath.get(normalizedProjectPath);
|
|
|
|
if (existingIndex === undefined) {
|
|
optionIndexByNormalizedPath.set(normalizedProjectPath, options.length);
|
|
options.push(toProjectOption(project));
|
|
continue;
|
|
}
|
|
|
|
const shouldPreferCurrentOption =
|
|
normalizedPreferredPath === normalizedProjectPath && project.path === preferredPath;
|
|
|
|
if (shouldPreferCurrentOption) {
|
|
options[existingIndex] = toProjectOption(project);
|
|
}
|
|
}
|
|
|
|
return options;
|
|
}
|