Add drag-and-drop reordering to sidebar project list using dnd-kit, persisted per-user via localStorage. Use consistent project order in issue properties, new issue dialog, and issue detail mention options. Move projects section below Work section in sidebar. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
71 lines
2.1 KiB
TypeScript
71 lines
2.1 KiB
TypeScript
import type { Project } from "@paperclip/shared";
|
|
|
|
export const PROJECT_ORDER_UPDATED_EVENT = "paperclip:project-order-updated";
|
|
const PROJECT_ORDER_STORAGE_PREFIX = "paperclip.projectOrder";
|
|
const ANONYMOUS_USER_ID = "anonymous";
|
|
|
|
type ProjectOrderUpdatedDetail = {
|
|
storageKey: string;
|
|
orderedIds: string[];
|
|
};
|
|
|
|
function normalizeIdList(value: unknown): string[] {
|
|
if (!Array.isArray(value)) return [];
|
|
return value.filter((item): item is string => typeof item === "string" && item.length > 0);
|
|
}
|
|
|
|
function resolveUserId(userId: string | null | undefined): string {
|
|
if (!userId) return ANONYMOUS_USER_ID;
|
|
const trimmed = userId.trim();
|
|
return trimmed.length > 0 ? trimmed : ANONYMOUS_USER_ID;
|
|
}
|
|
|
|
export function getProjectOrderStorageKey(companyId: string, userId: string | null | undefined): string {
|
|
return `${PROJECT_ORDER_STORAGE_PREFIX}:${companyId}:${resolveUserId(userId)}`;
|
|
}
|
|
|
|
export function readProjectOrder(storageKey: string): string[] {
|
|
try {
|
|
const raw = localStorage.getItem(storageKey);
|
|
if (!raw) return [];
|
|
return normalizeIdList(JSON.parse(raw));
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
export function writeProjectOrder(storageKey: string, orderedIds: string[]) {
|
|
const normalized = normalizeIdList(orderedIds);
|
|
try {
|
|
localStorage.setItem(storageKey, JSON.stringify(normalized));
|
|
} catch {
|
|
// Ignore storage write failures in restricted browser contexts.
|
|
}
|
|
if (typeof window !== "undefined") {
|
|
window.dispatchEvent(
|
|
new CustomEvent<ProjectOrderUpdatedDetail>(PROJECT_ORDER_UPDATED_EVENT, {
|
|
detail: { storageKey, orderedIds: normalized },
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
|
|
export function sortProjectsByStoredOrder(projects: Project[], orderedIds: string[]): Project[] {
|
|
if (projects.length === 0) return [];
|
|
if (orderedIds.length === 0) return projects;
|
|
|
|
const byId = new Map(projects.map((project) => [project.id, project]));
|
|
const sorted: Project[] = [];
|
|
|
|
for (const id of orderedIds) {
|
|
const project = byId.get(id);
|
|
if (!project) continue;
|
|
sorted.push(project);
|
|
byId.delete(id);
|
|
}
|
|
for (const project of byId.values()) {
|
|
sorted.push(project);
|
|
}
|
|
return sorted;
|
|
}
|