Files
openchamber/packages/ui/src/lib/projectMeta.ts
T
00821700de chore: remove dead code (59 unused files + ~125 unused exports) (#1835)
* chore: remove dead/unreferenced files across ui, vscode

Remove 59 unused source files (components, hooks, lib utils, stores,
barrels, and orphaned vscode github modules) that are not imported by
any entry-reachable code. Also drop a stale test mock for the removed
execCommands module.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

* refactor: remove unused exported symbols (types, functions, consts, hooks)

Remove exported symbols whose identifier is referenced nowhere in the
repository (verified via repo-wide search), across ui types/contracts,
lib utilities, sync layer, stores, and components. Also drop the few
imports/private helpers orphaned by these removals.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

* refactor: remove more unused exports (desktop, shortcuts, worktree, vscode)

Continue removing repo-wide unreferenced exported functions, consts and
types across lib/desktop, shortcuts, worktreeSessionCreator, sync, and
vscode gitService, with cascading orphaned helpers/imports cleaned up.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

* chore: add dead-code cleanup tooling

* refactor: checkpoint dead-code cleanup

* refactor: remove dead-code suppressions

---------

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-06-26 19:27:53 +03:00

236 lines
7.8 KiB
TypeScript

import React from 'react';
import type { ProjectEntry } from '@/lib/api/types';
import { runtimeFetch } from '@/lib/runtime-fetch';
import { getRuntimeApiBaseUrl } from '@/lib/runtime-switch';
import type { IconName } from "@/components/icon/icons";
type ThemeVariant = 'light' | 'dark';
type ProjectIconImageOptions = { themeVariant?: ThemeVariant; iconColor?: string };
const PROJECT_ICON_OBJECT_URL_CACHE_LIMIT = 200;
type ProjectIconObjectUrlCacheEntry = {
url?: string;
promise?: Promise<string | null>;
};
type ProjectIconObjectUrlRequest = {
cacheKey: string;
projectId: string;
query: URLSearchParams;
};
const projectIconObjectUrlCache = new Map<string, ProjectIconObjectUrlCacheEntry>();
export const PROJECT_ICONS: Array<{ key: string; Icon: IconName; label: string }> = [
{ key: 'code', Icon: 'code-box', label: 'Code' },
{ key: 'terminal', Icon: 'terminal-box', label: 'Terminal' },
{ key: 'rocket', Icon: 'rocket', label: 'Rocket' },
{ key: 'flask', Icon: 'flask', label: 'Lab' },
{ key: 'gamepad', Icon: 'gamepad', label: 'Game' },
{ key: 'briefcase', Icon: 'briefcase', label: 'Work' },
{ key: 'home', Icon: 'home', label: 'Home' },
{ key: 'globe', Icon: 'global', label: 'Web' },
{ key: 'leaf', Icon: 'leaf', label: 'Nature' },
{ key: 'shield', Icon: 'shield', label: 'Security' },
{ key: 'palette', Icon: 'palette', label: 'Design' },
{ key: 'server', Icon: 'server', label: 'Server' },
{ key: 'phone', Icon: 'smartphone', label: 'Mobile' },
{ key: 'database', Icon: 'database-2', label: 'Data' },
{ key: 'lightbulb', Icon: 'lightbulb', label: 'Idea' },
{ key: 'music', Icon: 'music', label: 'Music' },
{ key: 'camera', Icon: 'camera', label: 'Media' },
{ key: 'book', Icon: 'book-open', label: 'Docs' },
{ key: 'heart', Icon: 'heart', label: 'Favorite' },
];
export const PROJECT_ICON_MAP: Record<string, IconName> = Object.fromEntries(
PROJECT_ICONS.map((i) => [i.key, i.Icon])
);
export const PROJECT_COLORS: Array<{ key: string; label: string; cssVar: string }> = [
{ key: 'keyword', label: 'Purple', cssVar: 'var(--syntax-keyword)' },
{ key: 'string', label: 'Green', cssVar: 'var(--syntax-string)' },
{ key: 'number', label: 'Pink', cssVar: 'var(--syntax-number)' },
{ key: 'type', label: 'Gold', cssVar: 'var(--syntax-type)' },
{ key: 'constant', label: 'Cyan', cssVar: 'var(--syntax-constant)' },
{ key: 'comment', label: 'Muted', cssVar: 'var(--syntax-comment)' },
{ key: 'error', label: 'Red', cssVar: 'var(--status-error)' },
{ key: 'primary', label: 'Blue', cssVar: 'var(--primary)' },
{ key: 'success', label: 'Green', cssVar: 'var(--status-success)' },
];
export const PROJECT_COLOR_MAP: Record<string, string> = Object.fromEntries(
PROJECT_COLORS.map((c) => [c.key, c.cssVar])
);
const buildProjectIconQuery = (updatedAt: number | null | undefined, options?: ProjectIconImageOptions): URLSearchParams | null => {
if (typeof updatedAt !== 'number' || updatedAt <= 0) {
return null;
}
const params = new URLSearchParams({ v: String(updatedAt) });
if (typeof options?.iconColor === 'string' && options.iconColor.trim()) {
params.set('iconColor', options.iconColor.trim());
}
if (options?.themeVariant === 'light' || options?.themeVariant === 'dark') {
params.set('theme', options.themeVariant);
}
return params;
};
const buildProjectIconObjectUrlCacheKey = (
projectId: string,
query: URLSearchParams,
): string | null => {
if (!projectId) return null;
return [
getRuntimeApiBaseUrl() || 'same-origin',
projectId,
query.toString(),
].join('|');
};
const buildProjectIconObjectUrlRequest = (
projectId: string,
updatedAt: number | null | undefined,
options?: ProjectIconImageOptions,
): ProjectIconObjectUrlRequest | null => {
const query = buildProjectIconQuery(updatedAt, options);
if (!query) return null;
const cacheKey = buildProjectIconObjectUrlCacheKey(projectId, query);
if (!cacheKey) return null;
return { cacheKey, projectId, query };
};
const trimProjectIconObjectUrlCache = (): void => {
while (projectIconObjectUrlCache.size > PROJECT_ICON_OBJECT_URL_CACHE_LIMIT) {
const firstKey = projectIconObjectUrlCache.keys().next().value;
if (typeof firstKey !== 'string') return;
const entry = projectIconObjectUrlCache.get(firstKey);
if (entry?.url && typeof URL !== 'undefined' && typeof URL.revokeObjectURL === 'function') {
URL.revokeObjectURL(entry.url);
}
projectIconObjectUrlCache.delete(firstKey);
}
};
const loadProjectIconObjectUrl = (
request: ProjectIconObjectUrlRequest,
): Promise<string | null> => {
const cached = projectIconObjectUrlCache.get(request.cacheKey);
if (cached?.url) return Promise.resolve(cached.url);
if (cached?.promise) return cached.promise;
const promise = runtimeFetch(`/api/projects/${encodeURIComponent(request.projectId)}/icon`, {
method: 'GET',
headers: { Accept: 'image/*' },
query: request.query,
})
.then(async (response) => {
if (!response.ok) return null;
if (typeof URL === 'undefined' || typeof URL.createObjectURL !== 'function') return null;
const blob = await response.blob();
const url = URL.createObjectURL(blob);
projectIconObjectUrlCache.set(request.cacheKey, { url });
trimProjectIconObjectUrlCache();
return url;
})
.catch(() => null)
.finally(() => {
const entry = projectIconObjectUrlCache.get(request.cacheKey);
if (entry?.promise === promise && !entry.url) {
projectIconObjectUrlCache.delete(request.cacheKey);
}
});
projectIconObjectUrlCache.set(request.cacheKey, { promise });
return promise;
};
const useProjectIconImageObjectUrl = (
project: Pick<ProjectEntry, 'id' | 'iconImage'>,
options?: ProjectIconImageOptions,
): string | null => {
const projectId = project.id;
const updatedAt = project.iconImage?.updatedAt;
const themeVariant = options?.themeVariant;
const iconColor = options?.iconColor;
const request = React.useMemo(() => buildProjectIconObjectUrlRequest(projectId, updatedAt, { themeVariant, iconColor }), [
projectId,
updatedAt,
themeVariant,
iconColor,
]);
const [url, setUrl] = React.useState(() => {
return request ? projectIconObjectUrlCache.get(request.cacheKey)?.url ?? null : null;
});
React.useEffect(() => {
if (!request) {
setUrl(null);
return;
}
const cached = projectIconObjectUrlCache.get(request.cacheKey)?.url;
if (cached) {
setUrl(cached);
return;
}
let cancelled = false;
setUrl(null);
void loadProjectIconObjectUrl(request).then((nextUrl) => {
if (!cancelled) setUrl(nextUrl);
});
return () => {
cancelled = true;
};
}, [request]);
return url;
};
export type ProjectIconImageProps = {
project: Pick<ProjectEntry, 'id' | 'iconImage'>;
options?: ProjectIconImageOptions;
className?: string;
alt?: string;
draggable?: boolean;
fallback?: React.ReactNode;
onError?: React.ReactEventHandler<HTMLImageElement>;
};
export const ProjectIconImage: React.FC<ProjectIconImageProps> = ({
project,
options,
className,
alt = '',
draggable = false,
fallback = null,
onError,
}) => {
const src = useProjectIconImageObjectUrl(project, options);
const [failed, setFailed] = React.useState(false);
React.useEffect(() => {
setFailed(false);
}, [src, project.id, project.iconImage?.updatedAt]);
if (!src || failed) return React.createElement(React.Fragment, null, fallback);
return React.createElement('img', {
src,
alt,
className,
draggable,
onError: (event: React.SyntheticEvent<HTMLImageElement>) => {
setFailed(true);
onError?.(event);
},
});
};