/** Home's collection browser, with revision-bound archive and restore choices. */ import { computed, eventKey, handler, NAME, pattern, type PerSession, Stream, UI, type VNode, Writable, } from "commonfabric"; import { changeSharedSpaceMembershipIn, type SharedSpaceCatalogStorage, type SharedSpaceMembershipResult, } from "./shared-space-catalog.ts"; import type { SharedSpaceCollectionView } from "./shared-space-interface.ts"; /** The rendered control's snapshot of the choice the person saw. */ export interface CollectionChoiceEvent { target: { dataset: { space: string; revision: string; state: string }; }; } /** Home-local state for applying a rendered membership choice. */ interface ChoiceState { catalog: Writable; conflict: Writable; } /** Applies the control's observed revision, reporting a refusal in its view. */ export const chooseCollectionMembership = handler< CollectionChoiceEvent, ChoiceState, SharedSpaceMembershipResult >((event, { catalog, conflict }) => { const { space, revision, state } = event.target.dataset; if (state !== "saved" && state !== "archived") { throw new TypeError("Collection choice requires a known membership state."); } const result = changeSharedSpaceMembershipIn(catalog, { space, expectedRevision: revision, state, id: eventKey(), }); conflict.set(result.status === "conflict" ? result.reason : ""); return result; }); /** The owned catalog and its validated observation, supplied by Home. */ export interface SharedSpaceCollectionInput { catalog: Writable; view: SharedSpaceCollectionView; } /** The browser's UI and session-local controls. */ export interface SharedSpaceCollectionOutput { [NAME]: string; [UI]: VNode; search: PerSession>; kind: PerSession>; membership: PerSession>; chooseMembership: Stream; } /** Labels the built-in collection kinds and retains extension-kind names. */ const kindLabel = (kind: string): string => kind === "loom" ? "Looms" : kind === "fabrichat-room" ? "Chats" : kind; /** Browses Home's collection without resolving or reading any target space. */ const SharedSpaceCollection = pattern< SharedSpaceCollectionInput, SharedSpaceCollectionOutput >(({ catalog, view }) => { const search = new Writable.perSession(""); const kind = new Writable.perSession(""); const membership = new Writable.perSession("saved"); const conflict = new Writable.perSession(""); const chooseMembership = chooseCollectionMembership({ catalog, conflict }); const unavailable = computed(() => view.status !== "ready"); const unavailableText = computed(() => view.status === "unavailable" && view.reason === "malformed" ? "The collection could not be read. Its stored data needs attention." : "Loading your collection…" ); const kindItems = computed(() => [ { label: "All kinds", value: "" }, ...(view.status === "ready" ? Object.keys(view.byKind).map((value) => ({ label: kindLabel(value), value, })) : []), ]); const rows = computed(() => { if (view.status !== "ready") return []; const state = membership.get(); const entries = state === "saved" ? view.saved : state === "archived" ? view.archived : view.other; const selectedKind = kind.get(); const query = search.get().trim().toLowerCase(); return entries.filter((entry) => (selectedKind === "" || entry.kind === selectedKind) && (query === "" || [entry.title ?? "", entry.space, entry.host, entry.kind].some((text) => text.toLowerCase().includes(query) )) ); }); const empty = computed(() => view.status === "ready" && rows.length === 0); const conflictText = computed(() => conflict.get() === "revision" ? "The collection changed after this choice was shown. Review it and choose again." : conflict.get() === "" ? "" : "This membership choice could not be applied. Review the collection before trying again." ); return { [NAME]: "Social spaces", [UI]: ( Social spaces Spaces you have saved, including looms and chats. Archiving puts a space away; your access and everyone else's access stay the same. {unavailable ? {unavailableText} : null} {conflictText !== "" ? {conflictText} : null} {empty ?

No social spaces match this view.

: null} {rows.map((entry) => ( {entry.title || entry.space} {kindLabel(entry.kind)} · {entry.state} {entry.space} {entry.host} {entry.state === "saved" || entry.state === "archived" ? ( {entry.state === "saved" ? "Archive" : "Restore"} ) : null} ))}
), search, kind, membership, chooseMembership, }; }); export default SharedSpaceCollection;