"use client"; import { DndContext, type DragEndEvent, KeyboardSensor, PointerSensor, useSensor, useSensors, } from "@dnd-kit/core"; import { arrayMove, SortableContext, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy, } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; import { useState } from "react"; import Link from "@/components/link"; import type { HousekeepingRouteDefinition, HousekeepingSurface, } from "../contracts"; import type { HousekeepingPreferences } from "../preferences/schema"; import { toHousekeepingHref } from "../routing/href"; import { type HousekeepingPreferenceSaver, preferencesAfterSave, } from "./widget-settings"; export function movePreferenceId( ids: readonly string[], activeId: string, overId: string, ): string[] { const from = ids.indexOf(activeId); const to = ids.indexOf(overId); if (from < 0 || to < 0 || from === to) return [...ids]; return arrayMove([...ids], from, to); } export function togglePinnedRoute( preferences: HousekeepingPreferences, routeId: string, ): HousekeepingPreferences { const pinned = preferences.pinnedRouteIds.includes(routeId); if (pinned) { return { ...preferences, pinnedRouteIds: preferences.pinnedRouteIds.filter((id) => id !== routeId), shortcutOrder: preferences.shortcutOrder.filter((id) => id !== routeId), }; } return { ...preferences, pinnedRouteIds: [...preferences.pinnedRouteIds, routeId], shortcutOrder: preferences.shortcutOrder.includes(routeId) ? [...preferences.shortcutOrder] : [...preferences.shortcutOrder, routeId], }; } function orderedPinnedRouteIds(preferences: HousekeepingPreferences): string[] { const pinned = new Set(preferences.pinnedRouteIds); const ordered = preferences.shortcutOrder.filter((id) => pinned.has(id)); for (const id of preferences.pinnedRouteIds) { if (!ordered.includes(id)) ordered.push(id); } return ordered; } function SortableFavorite({ route, surface, unpin, unpinLabel, }: { readonly route: HousekeepingRouteDefinition; readonly surface: HousekeepingSurface; readonly unpin: () => void; readonly unpinLabel: string; }) { const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id: route.id, }); return (
  • {route.labelKey}
  • ); } export interface FavoritesLabels { readonly title: string; readonly pin: string; readonly unpin: string; readonly error: string; } export function Favorites({ preferences: initialPreferences, routes, surface, savePreferences, labels, }: { readonly preferences: HousekeepingPreferences; readonly routes: readonly HousekeepingRouteDefinition[]; readonly surface: HousekeepingSurface; readonly savePreferences: HousekeepingPreferenceSaver; readonly labels: FavoritesLabels; }) { const [preferences, setPreferences] = useState(initialPreferences); const [error, setError] = useState(false); const [saving, setSaving] = useState(false); const sensors = useSensors( useSensor(PointerSensor), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates, }), ); const routeById = new Map(routes.map((route) => [route.id, route])); const pinnedIds = orderedPinnedRouteIds(preferences).filter((id) => routeById.has(id), ); const unpinned = routes.filter( (route) => !preferences.pinnedRouteIds.includes(route.id), ); const commit = async (proposed: HousekeepingPreferences) => { setSaving(true); setError(false); try { const outcome = preferencesAfterSave( preferences, proposed, await savePreferences(proposed), ); setPreferences(outcome.preferences); setError(outcome.error !== undefined); } catch { setError(true); } finally { setSaving(false); } }; const onDragEnd = (event: DragEndEvent) => { if (!event.over) return; const shortcutOrder = movePreferenceId( pinnedIds, String(event.active.id), String(event.over.id), ); void commit({ ...preferences, shortcutOrder }); }; return (

    {labels.title}

    {error ? (

    {labels.error}

    ) : null}
      {pinnedIds.map((id) => { const route = routeById.get(id); return route ? ( void commit(togglePinnedRoute(preferences, id))} /> ) : null; })}
    ); }