import addProjectIcon from "./assets/figma/navigation/add-project.svg"; import bookBookmarkIcon from "./assets/figma/navigation/book-bookmark.svg"; import boxArchiveIcon from "./assets/figma/navigation/box-archive.svg"; import chopinIcon from "./assets/figma/navigation/chopin.svg"; import collapseIcon from "./assets/icons/panel-close.svg"; import documentActionsIcon from "./assets/figma/navigation/document-actions.svg"; import newDocumentIcon from "./assets/figma/navigation/new-document.svg"; import searchIcon from "./assets/figma/navigation/search.svg"; import { DocumentActionsMenu } from "./document-actions-menu"; import { motionContract } from "./motion-contract"; import { motionImmediately } from "./motion-input"; import { canManageProject } from "./navigation-model"; import { MotionDisclosure, MotionDisclosureIcon } from "@chopin/editor"; import { childDocumentPath, documentPath } from "@chopin/protocol/document-url"; import { useId, useRef, useState } from "react"; import { ChevronIcon } from "@chopin/icons"; import type * as Api from "./api"; import type { DocumentAction } from "./document-actions-menu"; import type { ProjectDocuments } from "./document-actions"; import type { ReactNode } from "react"; export function NavigationIcon( { alt = "", className, src }: { alt?: string; className?: string; src: string }, ) { return ; } export function toggleCollapsedProjectIds( ids: ReadonlySet, repositoryId: string, ): Set { let next = new Set(ids); if (next.has(repositoryId)) next.delete(repositoryId); else next.add(repositoryId); return next; } export function documentGroups( channels: Api.Channel[], archiveMode: boolean, ): Array<{ parent: Api.Channel; children: Api.Channel[] }> { let parents = channels.filter(channel => channel.parentChannelId === undefined && (archiveMode ? channel.archivedAt !== undefined : channel.archivedAt === undefined) ); let parentIds = new Set(parents.map(channel => channel.id)); let children = new Map(); for (let channel of channels) { if (!channel.parentChannelId || !parentIds.has(channel.parentChannelId)) continue; let nested = children.get(channel.parentChannelId) ?? []; nested.push(channel); children.set(channel.parentChannelId, nested); } return parents.map(parent => ({ parent, children: children.get(parent.id) ?? [] })); } function Project( { archiveMode, creatingProjectIds, currentDocumentId, entry, expanded, onCreateDocument, onDocumentAction, onLoadMore, onToggle, }: { archiveMode: boolean; creatingProjectIds: ReadonlySet; currentDocumentId?: string; entry: ProjectDocuments; expanded: boolean; onCreateDocument: (project: Api.NavigationProject) => void; onDocumentAction: (channel: Api.Channel, action: DocumentAction) => void; onLoadMore: (entry: ProjectDocuments) => void; onToggle: () => void; }, ) { let { documents, project } = entry; let groups = documentGroups(documents.channels, archiveMode); let label = project.repository?.name ?? project.repositoryName; let canManage = canManageProject(project); let creating = creatingProjectIds.has(project.repositoryId); let contentId = useId(); let collapseMotion = motionContract("collapse"); let projectContent = ( <> {documents.status === "unavailable" && ( Access unavailable )} {documents.status === "error" && ( {documents.message} )} {documents.status === "loading" && groups.length === 0 && ( Loading documents… )} {groups.length > 0 && ( {groups.map(({ children, parent: channel }) => { let parentCurrent = currentDocumentId === channel.id; let childCurrent = children.some(child => child.id === currentDocumentId); let parentHref = documentPath( channel.repositoryOwner, channel.repositoryName, channel.slug, ); return ( {channel.title} {channel.description && ( {channel.description} )} {canManage && ( onDocumentAction(channel, action)} trigger={ } /> )} {children.length > 0 && ( {children.map(child => { let current = child.id === currentDocumentId; return ( {child.title} {canManage && ( onDocumentAction(child, action)} trigger={ } /> )} ); })} )} ); })} )} {documents.status === "loading" && groups.length > 0 && ( Loading more… )} {documents.status === "ready" && documents.nextCursor && ( onLoadMore(entry)} type="button" > Load more )} > ); return ( } open={expanded} opened={} /> {label} {!archiveMode && project.available && canManage && ( onCreateDocument(project)} type="button" > )} {projectContent} ); } export function ProjectSidebar( { accountMenu, accountMenuOpen, canCreateDocument, creatingNewDocument, creatingProjectIds, currentDocumentId, onAccount, onAddProject, onCollapse, onCreateDocument, onDocumentAction, onLoadMore, onNewDocument, onSearch, onCatalogueModeChange, projects, catalogueMode, user, }: { accountMenu?: ReactNode; accountMenuOpen?: boolean; canCreateDocument: boolean; catalogueMode: "active" | "archived"; creatingNewDocument: boolean; creatingProjectIds: ReadonlySet; currentDocumentId?: string; onAccount: () => void; onAddProject: () => void; onCollapse: () => void; onCreateDocument: (project: Api.NavigationProject) => void; onDocumentAction: (channel: Api.Channel, action: DocumentAction) => void; onLoadMore: (entry: ProjectDocuments) => void; onNewDocument: () => void; onSearch: () => void; onCatalogueModeChange: (mode: "active" | "archived") => void; projects: ProjectDocuments[]; user: Api.User; }, ) { let [collapsedProjectIds, setCollapsedProjectIds] = useState>( () => new Set(), ); let archivedChats = useRef(null); let backToActiveDocs = useRef(null); let archiveMode = catalogueMode === "archived"; let primaryActions = ( {archiveMode ? ( { onCatalogueModeChange("active"); requestAnimationFrame(() => archivedChats.current?.focus({ preventScroll: true })); }} ref={backToActiveDocs} type="button" > ← Back to active docs ) : ( <> New document Search > )} ); let archiveFooter = !archiveMode ? ( { onCatalogueModeChange("archived"); requestAnimationFrame(() => backToActiveDocs.current?.focus({ preventScroll: true })); }} ref={archivedChats} type="button" > Archived chats ) : null; return ( ); }
Access unavailable
{documents.message}
Loading documents…
Loading more…