Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions apps/web/src/hosted.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -379,13 +379,17 @@ function DocumentRouteSwap(
(layer): layer is DocumentRouteLayer => layer !== undefined,
);
let motion = motionContract("content-swap");
let { onDocumentLoaded, onDocumentRouteSettled } = useNavigationDocument();
let ready = useCallback((
key: DocumentRouteIdentity,
resolution?: DocumentRouteResolution,
) => {
if (resolution) aliases.current.set(resolution.routeKey, key);
dispatch({ key, resolution, type: "ready" });
}, []);
if (requestedRoute.current.key === key) {
onDocumentRouteSettled(requestedRoute.current.routeKey);
}
}, [onDocumentRouteSettled]);
let metadataPath = useCallback((
key: DocumentRouteIdentity,
metadataRouteKey: DocumentRouteIdentity,
Expand All @@ -399,7 +403,6 @@ function DocumentRouteSwap(
}
onCanonicalPath(pathname);
}, [onCanonicalPath]);
let { onDocumentLoaded } = useNavigationDocument();
let published = useRef<DocumentRouteResolution | undefined>(undefined);
useEffect(() => {
let resolution = state.current.resolution;
Expand Down
10 changes: 4 additions & 6 deletions apps/web/src/navigation-chrome.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ describe("the Figma navigation chrome", () => {
test("uses a pen for document creation and a plus for adding a Project", () => {
let markup = renderToStaticMarkup(createElement(ProjectSidebar, {
canCreateDocument: true,
creatingNewDocument: false,
creatingProjectIds: new Set<string>(),
pendingCreations: new Map(),
onAccount: () => {},
onAddProject: () => {},
onCollapse: () => {},
Expand Down Expand Up @@ -104,8 +103,7 @@ describe("the Figma navigation chrome", () => {
test("exposes whether the account menu is open", () => {
let props = {
canCreateDocument: false,
creatingNewDocument: false,
creatingProjectIds: new Set<string>(),
pendingCreations: new Map(),
onAccount: () => {},
onAddProject: () => {},
onCollapse: () => {},
Expand All @@ -132,8 +130,7 @@ describe("the Figma navigation chrome", () => {
test("offers explicit pagination when a Project has more documents", () => {
let markup = renderToStaticMarkup(createElement(ProjectSidebar, {
canCreateDocument: true,
creatingNewDocument: false,
creatingProjectIds: new Set<string>(),
pendingCreations: new Map(),
onAccount: () => {},
onAddProject: () => {},
onCollapse: () => {},
Expand All @@ -158,6 +155,7 @@ describe("the Figma navigation chrome", () => {
}));

expect(markup).toContain('aria-label="Load more documents in testing-sql-transcripts"');
expect(markup).not.toContain("No documents yet.");
});

test("keeps the document header to one project icon and document trigger", () => {
Expand Down
80 changes: 71 additions & 9 deletions apps/web/src/navigation-model.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ import { describe, expect, it } from "bun:test";

import {
activeProject,
beginProjectCreation,
canManageProject,
documentCreationTarget,
documentDestination,
finishProjectCreation,
isDocumentWorkspaceRoute,
landingDocument,
navigationMode,
Expand Down Expand Up @@ -209,13 +208,6 @@ describe("navigation model", () => {
});
});

it("keeps one Project creating while another creation settles", () => {
let creating = beginProjectCreation(new Set(), "R_one");
creating = beginProjectCreation(creating, "R_two");

expect([...finishProjectCreation(creating, "R_one")]).toEqual(["R_two"]);
});

it("allows mutations only for push or admin navigation repositories", () => {
let viewerProject = {
...projects[1]!.project,
Expand Down Expand Up @@ -247,3 +239,73 @@ describe("navigation model", () => {
expect(canManageProject(adminProject)).toBe(true);
});
});

function project(id: string, permission: "push" | "admin" | "pull" = "push") {
return {
repositoryId: id,
repositoryOwner: "acme",
repositoryName: id,
position: 0,
available: true,
repository: {
id,
owner: "acme",
name: id,
fullName: `acme/${id}`,
permissions: { pull: true, push: permission === "push", admin: permission === "admin" },
},
};
}

describe("document creation targets", () => {
let first = project("first");
let second = project("second", "admin");
let viewer = project("viewer", "pull");
let unavailable = { ...project("unavailable"), available: false };

it("waits for navigation and unresolved current-document context", () => {
expect(documentCreationTarget(undefined, undefined)).toEqual({ type: "loading" });
expect(documentCreationTarget([first], undefined, true)).toEqual({ type: "loading" });
expect(documentCreationTarget([first], first, true)).toEqual({
type: "project",
project: first,
});
});

it("creates in the sole writable project without any document catalogue", () => {
expect(documentCreationTarget([viewer, first, unavailable], undefined)).toEqual({
type: "project",
project: first,
});
expect(documentCreationTarget([viewer, second], viewer)).toEqual({
type: "project",
project: second,
});
});

it("prefers the current project and asks only when the target is ambiguous", () => {
expect(documentCreationTarget([first, second], second)).toEqual({
type: "project",
project: second,
});
expect(documentCreationTarget([first, viewer, second, unavailable], undefined)).toEqual({
type: "choose",
projects: [first, second],
});
});

it("uses current navigation permissions rather than stale document context", () => {
let revoked = {
...first,
repository: { ...first.repository, permissions: viewer.repository.permissions },
};
expect(documentCreationTarget([revoked, second], first)).toEqual({
type: "project",
project: second,
});
expect(documentCreationTarget([viewer, unavailable], unavailable)).toEqual({
type: "unavailable",
});
expect(documentCreationTarget([], undefined)).toEqual({ type: "unavailable" });
});
});
37 changes: 21 additions & 16 deletions apps/web/src/navigation-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,27 @@ export function canManageProject(project: NavigationProject): boolean {
&& (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,
Expand Down Expand Up @@ -92,22 +113,6 @@ export function researchChildNavigation(
return { destination: researchChildDestination(parent, child), opener };
}

export function beginProjectCreation(
creating: ReadonlySet<string>,
projectId: string,
): Set<string> {
return new Set(creating).add(projectId);
}

export function finishProjectCreation(
creating: ReadonlySet<string>,
projectId: string,
): Set<string> {
let next = new Set(creating);
next.delete(projectId);
return next;
}

export function landingDocument(
projects: ProjectDocuments[],
lastDocumentId?: string,
Expand Down
Loading
Loading