import { useState, type ReactNode } from "react";
import type YProvider from "y-partyserver/provider";
import { useNavigate, useParams } from "@tanstack/react-router";
import { PanelRightOpen } from "lucide-react";
import { useCanonicalPageContext } from "@/client/components/workspace/use-canonical-page-context";
import { useAuthStore } from "@/client/stores/auth-store";
import { deriveWorkspacePageAffordance, type WorkspacePageAffordance } from "@/client/lib/affordance/workspace-page";
import { isActionEnabled, isActionVisible } from "@/client/lib/affordance/action-state";
import type { ActivePageSnapshot } from "@/client/lib/active-page-model";
import { DocumentPage } from "@/client/components/editor/document-page";
import { CanvasPage } from "@/client/components/canvas/canvas-page";
import { PageBreadcrumbs } from "@/client/components/ui/page-breadcrumbs";
import { PageByline } from "@/client/components/ui/page-byline";
import { PageCover } from "@/client/components/ui/page-cover";
import { PageErrorState } from "@/client/components/ui/page-error-state";
import { PageLoadingSkeleton } from "@/client/components/ui/page-loading-skeleton";
import { type CanvasStageLayout } from "@/client/components/ui/page-layout";
import { PageChrome, PageEmojiIcon } from "@/client/components/ui/page-chrome";
import { AvatarStack } from "@/client/components/presence/avatar-stack";
import { SyncStatusDot } from "@/client/components/presence/sync-status";
import { IconPicker } from "@/client/components/icon-picker";
import { CoverPicker } from "@/client/components/cover-picker";
import { ShareDialog } from "@/client/components/workspace/share-dialog";
import { SummarizeSheet } from "@/client/components/workspace/summarize-sheet";
import { useSyncStatus, type SyncStatus } from "@/client/hooks/use-sync";
import { useOnline } from "@/client/hooks/use-online";
import { DEFAULT_PAGE_TITLE } from "@/shared/constants";
import { useDocumentTitle } from "@/client/hooks/use-document-title";
import { CanonicalActivePageBoundary } from "@/client/components/active-page/canonical";
import {
useActivePageActions,
useActivePageState,
useActivePageSync,
} from "@/client/components/active-page/use-active-page";
import { useCanonicalPageActions } from "@/client/components/workspace/use-canonical-page-actions";
import { useWorkspaceLayoutMode } from "@/client/components/workspace/layout-mode";
import type { Page, PageAncestor, Workspace } from "@/shared/types";
export function PageView() {
const { pageId } = useParams({ strict: false }) as { pageId: string };
return (
);
}
function PageViewContent() {
const params = useParams({ strict: false }) as {
workspaceSlug: string;
pageId: string;
};
const navigate = useNavigate();
const activePageState = useActivePageState();
const { syncProvider, setSyncProvider } = useActivePageSync();
const { patchPage } = useActivePageActions();
const {
workspaceId: effectiveWorkspaceId,
workspace,
currentPageMeta,
pages,
members,
accessMode,
workspaceRole,
} = useCanonicalPageContext();
const currentUser = useAuthStore((s) => s.user);
const online = useOnline();
const isSharedMode = accessMode === "shared";
const [summarizeOpen, setSummarizeOpen] = useState(false);
const { status } = useSyncStatus(syncProvider, online);
const { expanded } = useWorkspaceLayoutMode();
const canvasLayout: CanvasStageLayout = expanded ? "stage" : "centered";
const page = activePageState.kind === "ready" ? activePageState.snapshot : null;
const ancestors = activePageState.kind === "ready" ? activePageState.ancestors : [];
const creator =
currentPageMeta && currentPageMeta.created_by
? (members.find((member) => member.user_id === currentPageMeta.created_by)?.user ?? null)
: null;
const docFooterLeading =
creator && currentPageMeta ? : undefined;
useDocumentTitle(page?.title || DEFAULT_PAGE_TITLE);
const { handleTitleChange, handleIconChange, handleCoverChange } = useCanonicalPageActions({
workspace,
page,
syncProvider,
patchPage,
});
if (activePageState.kind === "loading") {
return (
);
}
if (activePageState.kind === "unavailable" || !page || !effectiveWorkspaceId) {
return (
{
if (window.history.length > 1) {
window.history.back();
} else {
navigate({
to: "/$workspaceSlug",
params: { workspaceSlug: params.workspaceSlug },
});
}
},
}}
/>
);
}
const pageAffordance = deriveWorkspacePageAffordance({
accessMode,
workspaceRole: workspaceRole ?? "none",
pageKind: page.kind,
pageAccess: activePageState.access.mode,
ownsPage: currentUser?.id === currentPageMeta?.created_by,
workspaceId: effectiveWorkspaceId,
online,
});
const headerActions = (
<>
{pageAffordance.kind === "doc" && pageAffordance.editor.canSummarizePage && (
)}
{!isSharedMode && workspace && isActionVisible(pageAffordance.shareDialog) && (
)}
>
);
const workspaceChrome = (
);
return (
<>
{pageAffordance.kind === "doc" ? (
) : (
)}
{pageAffordance.kind === "doc" &&
(pageAffordance.editor.canSummarizePage || pageAffordance.editor.canAskPage) && (
setSummarizeOpen(false)}
workspaceId={effectiveWorkspaceId}
pageId={page.id}
canSummarize={pageAffordance.editor.canSummarizePage}
canAsk={pageAffordance.editor.canAskPage}
/>
)}
>
);
}
interface WorkspacePageChromeProps {
page: ActivePageSnapshot;
currentPageMeta: Page | null;
workspace: Workspace | null;
pages: Page[];
ancestors: PageAncestor[];
pageAffordance: WorkspacePageAffordance;
workspaceSlug: string;
syncProvider: YProvider | null;
status: SyncStatus;
onIconChange: (icon: string | null) => void;
onCoverChange: (cover: string | null) => void;
headerActions?: ReactNode;
}
function WorkspacePageChrome({
page,
currentPageMeta,
workspace,
pages,
ancestors,
pageAffordance,
workspaceSlug,
syncProvider,
status,
onIconChange,
onCoverChange,
headerActions,
}: WorkspacePageChromeProps) {
const canEditMetadata = !!workspace && isActionVisible(pageAffordance.editPageMetadata);
const editDisabled = !isActionEnabled(pageAffordance.editPageMetadata);
const editDisabledTitle =
pageAffordance.editPageMetadata.kind === "disabled" ? pageAffordance.editPageMetadata.reason : undefined;
const breadcrumb =
pageAffordance.breadcrumbMode === "restricted" ? (
) : (
);
return (
: null}
coverOverlay={
canEditMetadata && workspace && page.coverUrl ? (
) : null
}
breadcrumb={breadcrumb}
breadcrumbActions={
<>
{headerActions}
>
}
icon={
canEditMetadata && workspace ? (
<>
{!page.coverUrl ? (
) : null}
>
) : page.icon ? (
) : null
}
/>
);
}