Files
paperclip/ui/src/lib/project-order.ts
Dotta f54f30cb90 feat(ui): drag-to-reorder sidebar projects with persistent order
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>
2026-03-02 14:20:49 -06:00

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;
}