import { childDocumentPath, documentPath } from "@chopin/protocol/document-url"; import type { NavigationProject, ResearchParentChannel } from "./api"; import type { ResearchOpener } from "@chopin/editor"; import type { Research } from "@chopin/protocol"; import type { ProjectDocuments } from "./document-actions"; import type { DocumentRouteIdentitySource } from "./document-route-swap"; export type NavigationMode = "drawer" | "inline"; export type NavigationRoute = | DocumentRouteIdentitySource | { page: "repositories" } | { page: "repository"; owner: string; repository: string } | { page: "missing" }; export const NAVIGATION_INLINE_MIN = 1198; export const NAVIGATION_MEDIA = `(max-width: ${NAVIGATION_INLINE_MIN - 1}px)`; export function isDocumentWorkspaceRoute( route: NavigationRoute, ): route is DocumentRouteIdentitySource { return route.page === "channel" || route.page === "document" || route.page === "child"; } export function navigationMode( matchMedia: (query: string) => { matches: boolean }, ): NavigationMode { return matchMedia(NAVIGATION_MEDIA).matches ? "drawer" : "inline"; } export function activeProject( projects: ProjectDocuments[], documentId: string | undefined, repositoryId?: string, ): NavigationProject | undefined { if (repositoryId) { return projects.find(({ project }) => project.repositoryId === repositoryId)?.project; } if (!documentId) return undefined; return projects.find(({ documents, project }) => project.available && documents.channels.some(channel => channel.id === documentId) )?.project; } export function canManageProject(project: NavigationProject): boolean { return !!project.repository && (project.repository.permissions.push || project.repository.permissions.admin); } export type DocumentCreationTarget = | { type: "loading" } | { type: "project"; project: NavigationProject } | { type: "choose"; projects: NavigationProject[] } | { type: "unavailable" }; export function documentCreationTarget( projects: NavigationProject[] | undefined, current: NavigationProject | undefined, resolvingDocument = false, ): DocumentCreationTarget { if (!projects || (resolvingDocument && !current)) return { type: "loading" }; let eligible = projects.filter(project => project.available && canManageProject(project)); let active = eligible.find(project => project.repositoryId === current?.repositoryId); if (active) return { type: "project", project: active }; if (eligible.length === 1) return { type: "project", project: eligible[0]! }; return eligible.length > 1 ? { type: "choose", projects: eligible } : { type: "unavailable" }; } export function documentDestination( projects: ProjectDocuments[], documentId: string, path?: string, ): string { if (path) return path; for (let { documents, project } of projects) { let channel = documents.channels.find(candidate => candidate.id === documentId); if (channel) { if (channel.parentChannelId) { let parent = documents.channels.find(candidate => candidate.id === channel.parentChannelId); return parent ? childDocumentPath( project.repositoryOwner, project.repositoryName, parent.slug, channel.slug, ) : `/channels/${encodeURIComponent(documentId)}`; } return documentPath(project.repositoryOwner, project.repositoryName, channel.slug); } } return `/channels/${encodeURIComponent(documentId)}`; } export function researchChildDestination( parent: Pick, child: Pick, ): string { return childDocumentPath( parent.repositoryOwner, parent.repositoryName, parent.slug, child.slug, ); } export function researchChildNavigation( parent: Pick, child: Pick, opener: ResearchOpener, ): { destination: string; opener: ResearchOpener } { return { destination: researchChildDestination(parent, child), opener }; } export function landingDocument( projects: ProjectDocuments[], lastDocumentId?: string, ): string | undefined { let available = projects.filter(({ documents }) => documents.status !== "unavailable"); let channels = available.flatMap(({ documents }) => documents.channels); if (lastDocumentId) { let last = channels.find(channel => channel.id === lastDocumentId); if (last && !last.archivedAt) return last.id; if (!last && available.some(({ documents }) => documents.status === "loading")) { return lastDocumentId; } } if (available.some(({ documents }) => documents.status === "loading")) return undefined; return channels.find(channel => !channel.archivedAt && !channel.parentChannelId)?.id; }