import addProjectIcon from "./assets/figma/navigation/add-project.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 { 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 { ArchiveIcon, ChevronIcon, DocumentIcon, SearchIcon } from "@chopin/icons"; import type * as Api from "./api"; import type { DocumentAction } from "./document-actions-menu"; import type { ProjectDocuments } from "./document-actions"; import type { DocumentCreationPhase } from "./use-document-creation"; import type { ReactNode } from "react"; export function NavigationIcon( { alt = "", className, src }: { alt?: string; className?: string; src: string }, ) { return {alt}; } 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, pendingCreations, currentDocumentId, entry, expanded, onCreateDocument, onDocumentAction, onLoadMore, onToggle, }: { archiveMode: boolean; pendingCreations: ReadonlyMap; 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 phase = pendingCreations.get(project.repositoryId); let contentId = useId(); let collapseMotion = motionContract("collapse"); let projectContent = ( <> {!archiveMode && documents.status === "ready" && documents.channels.length === 0 && !documents.nextCursor && (

No documents yet.

{project.available && canManage && ( )}
)} {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 && ( )} ); return (
  • {!archiveMode && project.available && canManage && ( )}
    {phase === "creating" ? "Creating document…" : phase ? "Opening document…" : ""}
    {projectContent}
  • ); } export function ProjectSidebar( { accountMenu, accountMenuOpen, canCreateDocument, newDocumentPhase, pendingCreations, currentDocumentId, onAccount, onAddProject, onCollapse, onCreateDocument, onDocumentAction, onLoadMore, onNewDocument, onSearch, onCatalogueModeChange, projects, catalogueMode, user, }: { accountMenu?: ReactNode; accountMenuOpen?: boolean; canCreateDocument: boolean; catalogueMode: "active" | "archived"; newDocumentPhase?: DocumentCreationPhase | "loading"; pendingCreations: ReadonlyMap; 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 ? ( ) : ( <> )}
    ); let archiveFooter = !archiveMode ? (
    ) : null; return ( ); }