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/figma/navigation/collapse.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 } from "@chopin/editor"; import { documentPath, researchWorkspacePath } from "@chopin/protocol/document-url"; import { useId, useRef, useState } from "react"; import type * as Api from "./api"; import type { DocumentAction } from "./document-actions-menu"; import type { ProjectDocuments } from "./document-actions"; import type { ReactNode } from "react"; import type { ResearchChannelGroup } from "./research-navigation"; 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 newestResearchFirst( workspaces: ResearchChannelGroup["workspaces"], ): ResearchChannelGroup["workspaces"] { return [...workspaces].sort((first, second) => second.createdAt.localeCompare(first.createdAt) || second.id.localeCompare(first.id) ); } function Project( { archiveMode, creatingProjectIds, currentDocumentId, currentResearchWorkspaceId, entry, expanded, onCreateDocument, onDocumentAction, onLoadMore, onNewResearch, research, onToggle, }: { archiveMode: boolean; creatingProjectIds: ReadonlySet; currentDocumentId?: string; currentResearchWorkspaceId?: string; entry: ProjectDocuments; expanded: boolean; onCreateDocument: (project: Api.NavigationProject) => void; onDocumentAction: (channel: Api.Channel, action: DocumentAction) => void; onLoadMore: (entry: ProjectDocuments) => void; onNewResearch?: (channel: Api.Channel) => void; research: ReadonlyMap; onToggle: () => void; }, ) { let { documents, project } = entry; let channels = documents.channels.filter(channel => archiveMode ? channel.archivedAt !== undefined : channel.archivedAt === undefined ); 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" && channels.length === 0 && ( Loading documents… )} {channels.length > 0 && ( {channels.map(channel => { let children = newestResearchFirst(research.get(channel.id)?.workspaces ?? []); let parentCurrent = currentDocumentId === channel.id; let researchCurrent = parentCurrent && currentResearchWorkspaceId !== undefined; let parentHref = documentPath( channel.repositoryOwner, channel.repositoryName, channel.slug, ); return ( {channel.title} {channel.description && ( {channel.description} )} {canManage && ( {onNewResearch && !channel.archivedAt && ( onNewResearch(channel)} type="button" > )} onDocumentAction(channel, action)} trigger={ } /> )} {children.length > 0 && ( {children.map(workspace => ( {workspace.title} ))} )} ); })} )} {documents.status === "loading" && channels.length > 0 && ( Loading more… )} {documents.status === "ready" && documents.nextCursor && ( onLoadMore(entry)} type="button" > Load more )} > ); return ( {label} {!archiveMode && project.available && canManage && ( onCreateDocument(project)} type="button" > )} {projectContent} ); } export function ProjectSidebar( { accountMenu, accountMenuOpen, canCreateDocument, creatingNewDocument, creatingProjectIds, currentDocumentId, currentResearchWorkspaceId, onAccount, onAddProject, onCollapse, onCreateDocument, onDocumentAction, onLoadMore, onNewResearch, onNewDocument, onSearch, onCatalogueModeChange, projects, research = new Map(), catalogueMode, user, }: { accountMenu?: ReactNode; accountMenuOpen?: boolean; canCreateDocument: boolean; catalogueMode: "active" | "archived"; creatingNewDocument: boolean; creatingProjectIds: ReadonlySet; currentDocumentId?: string; currentResearchWorkspaceId?: string; onAccount: () => void; onAddProject: () => void; onCollapse: () => void; onCreateDocument: (project: Api.NavigationProject) => void; onDocumentAction: (channel: Api.Channel, action: DocumentAction) => void; onLoadMore: (entry: ProjectDocuments) => void; onNewResearch?: (channel: Api.Channel) => void; onNewDocument: () => void; onSearch: () => void; onCatalogueModeChange: (mode: "active" | "archived") => void; projects: ProjectDocuments[]; research?: ReadonlyMap; 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…