Skip to content
Merged
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
23 changes: 3 additions & 20 deletions packages/web/e2e/navigation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ const REAL_TOOLS: readonly { label: string; heading: string }[] = [
{ label: "Recent", heading: "Recent files" },
{ label: "Package / JSON", heading: "Package / JSON" },
{ label: ".odb", heading: "Browse an .odb database" },
{ label: "Editors", heading: "Edit a document" },
{ label: ".odm", heading: "Render an .odm master document" },
];

// Sidebar.tsx's own PLANNED_ITEMS -- tracked as their own follow-up (ExaDev/documents.js#1096), deliberately still disabled nav stubs here.
const PLANNED_TOOLS: readonly string[] = ["Editors", ".odm"];
// The former PLANNED_ITEMS (Editors, .odm) are real tools since #1096 -- they sit in REAL_TOOLS above with their routes' own headings.

test("the root route redirects straight into the Convert tool, the flagship page (no separate marketing landing)", async ({
page,
Expand All @@ -33,21 +34,3 @@ for (const { label, heading } of REAL_TOOLS) {
await expect(page.getByRole("heading", { name: heading })).toBeVisible();
});
}

for (const label of PLANNED_TOOLS) {
test(`sidebar item "${label}" is visible but not a working link -- clicking it leaves the URL unchanged`, async ({
page,
}) => {
await page.goto("/");
await expect(page).toHaveURL(/\/convert$/);
// PLANNED_ITEMS render as a disabled Mantine NavLink (component="div", disabled), not an <a> -- no link role and no real navigation to query, so the actual user-facing contract this checks is the disabled state plus a click going nowhere, not a transient tooltip's own hover animation.
const item = page.getByText(label, { exact: true });
await expect(item).toBeVisible();
await expect(item.locator("..").locator("..")).toHaveAttribute(
"data-disabled",
"true",
);
await item.click({ force: true });
await expect(page).toHaveURL(/\/convert$/);
});
}
50 changes: 50 additions & 0 deletions packages/web/src/hooks/useEditorSession.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { useMutation } from "@tanstack/react-query";

import { getRpcClient } from "../rpc/client";

// The Editors tool's five mutations, one per rpc procedure. All four snapshot-returning mutations answer the whole fresh paragraph list (the worker re-reads its live accessors per call), so the page state is nothing but the latest snapshot -- there is no client-side per-paragraph state to keep coherent.
export function useOpenEditor() {
return useMutation({
mutationFn: (
input: Parameters<ReturnType<typeof getRpcClient>["editor"]["open"]>[0],
) => getRpcClient().editor.open(input),
});
}

export function useSetParagraphText() {
return useMutation({
mutationFn: (
input: Parameters<
ReturnType<typeof getRpcClient>["editor"]["setParagraphText"]
>[0],
) => getRpcClient().editor.setParagraphText(input),
});
}

export function useAddParagraph() {
return useMutation({
mutationFn: (
input: Parameters<
ReturnType<typeof getRpcClient>["editor"]["addParagraph"]
>[0],
) => getRpcClient().editor.addParagraph(input),
});
}

export function useRemoveParagraph() {
return useMutation({
mutationFn: (
input: Parameters<
ReturnType<typeof getRpcClient>["editor"]["removeParagraph"]
>[0],
) => getRpcClient().editor.removeParagraph(input),
});
}

export function useSaveEditor() {
return useMutation({
mutationFn: (
input: Parameters<ReturnType<typeof getRpcClient>["editor"]["save"]>[0],
) => getRpcClient().editor.save(input),
});
}
11 changes: 11 additions & 0 deletions packages/web/src/hooks/useOdmRender.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { useMutation } from "@tanstack/react-query";

import { getRpcClient } from "../rpc/client";

export function useOdmRender() {
return useMutation({
mutationFn: (
input: Parameters<ReturnType<typeof getRpcClient>["odm"]["render"]>[0],
) => getRpcClient().odm.render(input),
});
}
42 changes: 42 additions & 0 deletions packages/web/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@
import { Route as rootRouteImport } from './routes/__root'
import { Route as IndexRouteImport } from './routes/index'
import { Route as ConvertRouteImport } from './routes/convert'
import { Route as EditorsRouteImport } from './routes/editors'
import { Route as FontsRouteImport } from './routes/fonts'
import { Route as InspectRouteImport } from './routes/inspect'
import { Route as MetadataRouteImport } from './routes/metadata'
import { Route as OdbRouteImport } from './routes/odb'
import { Route as OdmRouteImport } from './routes/odm'
import { Route as PackageRouteImport } from './routes/package'
import { Route as RecentRouteImport } from './routes/recent'
import { Route as ConvertIndexRouteImport } from './routes/convert.index'
Expand All @@ -30,6 +32,11 @@ const ConvertRoute = ConvertRouteImport.update({
path: '/convert',
getParentRoute: () => rootRouteImport,
} as any)
const EditorsRoute = EditorsRouteImport.update({
id: '/editors',
path: '/editors',
getParentRoute: () => rootRouteImport,
} as any)
const FontsRoute = FontsRouteImport.update({
id: '/fonts',
path: '/fonts',
Expand All @@ -50,6 +57,11 @@ const OdbRoute = OdbRouteImport.update({
path: '/odb',
getParentRoute: () => rootRouteImport,
} as any)
const OdmRoute = OdmRouteImport.update({
id: '/odm',
path: '/odm',
getParentRoute: () => rootRouteImport,
} as any)
const PackageRoute = PackageRouteImport.update({
id: '/package',
path: '/package',
Expand All @@ -74,21 +86,25 @@ const ConvertSourceTargetRoute = ConvertSourceTargetRouteImport.update({
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/convert': typeof ConvertRouteWithChildren
'/editors': typeof EditorsRoute
'/fonts': typeof FontsRoute
'/inspect': typeof InspectRoute
'/metadata': typeof MetadataRoute
'/odb': typeof OdbRoute
'/odm': typeof OdmRoute
'/package': typeof PackageRoute
'/recent': typeof RecentRoute
'/convert/': typeof ConvertIndexRoute
'/convert/$source/$target': typeof ConvertSourceTargetRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/editors': typeof EditorsRoute
'/fonts': typeof FontsRoute
'/inspect': typeof InspectRoute
'/metadata': typeof MetadataRoute
'/odb': typeof OdbRoute
'/odm': typeof OdmRoute
'/package': typeof PackageRoute
'/recent': typeof RecentRoute
'/convert': typeof ConvertIndexRoute
Expand All @@ -98,10 +114,12 @@ export interface FileRoutesById {
__root__: typeof rootRouteImport
'/': typeof IndexRoute
'/convert': typeof ConvertRouteWithChildren
'/editors': typeof EditorsRoute
'/fonts': typeof FontsRoute
'/inspect': typeof InspectRoute
'/metadata': typeof MetadataRoute
'/odb': typeof OdbRoute
'/odm': typeof OdmRoute
'/package': typeof PackageRoute
'/recent': typeof RecentRoute
'/convert/': typeof ConvertIndexRoute
Expand All @@ -112,21 +130,25 @@ export interface FileRouteTypes {
fullPaths:
| '/'
| '/convert'
| '/editors'
| '/fonts'
| '/inspect'
| '/metadata'
| '/odb'
| '/odm'
| '/package'
| '/recent'
| '/convert/'
| '/convert/$source/$target'
fileRoutesByTo: FileRoutesByTo
to:
| '/'
| '/editors'
| '/fonts'
| '/inspect'
| '/metadata'
| '/odb'
| '/odm'
| '/package'
| '/recent'
| '/convert'
Expand All @@ -135,10 +157,12 @@ export interface FileRouteTypes {
| '__root__'
| '/'
| '/convert'
| '/editors'
| '/fonts'
| '/inspect'
| '/metadata'
| '/odb'
| '/odm'
| '/package'
| '/recent'
| '/convert/'
Expand All @@ -148,10 +172,12 @@ export interface FileRouteTypes {
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
ConvertRoute: typeof ConvertRouteWithChildren
EditorsRoute: typeof EditorsRoute
FontsRoute: typeof FontsRoute
InspectRoute: typeof InspectRoute
MetadataRoute: typeof MetadataRoute
OdbRoute: typeof OdbRoute
OdmRoute: typeof OdmRoute
PackageRoute: typeof PackageRoute
RecentRoute: typeof RecentRoute
}
Expand All @@ -172,6 +198,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ConvertRouteImport
parentRoute: typeof rootRouteImport
}
'/editors': {
id: '/editors'
path: '/editors'
fullPath: '/editors'
preLoaderRoute: typeof EditorsRouteImport
parentRoute: typeof rootRouteImport
}
'/fonts': {
id: '/fonts'
path: '/fonts'
Expand Down Expand Up @@ -200,6 +233,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof OdbRouteImport
parentRoute: typeof rootRouteImport
}
'/odm': {
id: '/odm'
path: '/odm'
fullPath: '/odm'
preLoaderRoute: typeof OdmRouteImport
parentRoute: typeof rootRouteImport
}
'/package': {
id: '/package'
path: '/package'
Expand Down Expand Up @@ -247,10 +287,12 @@ const ConvertRouteWithChildren =
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
ConvertRoute: ConvertRouteWithChildren,
EditorsRoute: EditorsRoute,
FontsRoute: FontsRoute,
InspectRoute: InspectRoute,
MetadataRoute: MetadataRoute,
OdbRoute: OdbRoute,
OdmRoute: OdmRoute,
PackageRoute: PackageRoute,
RecentRoute: RecentRoute,
}
Expand Down
2 changes: 0 additions & 2 deletions packages/web/src/routes/-Sidebar.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import { style } from "@vanilla-extract/css";

export const navLink = style({ textDecoration: "none", color: "inherit" });

export const disabledNavItem = style({ cursor: "default" });

export const versionAnchor = style({
alignItems: "center",
gap: 6,
Expand Down
21 changes: 3 additions & 18 deletions packages/web/src/routes/-Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,19 @@ import {
} from "@tabler/icons-react";

import { relativeTime } from "../shared/relativeTime";
import { disabledNavItem, navLink, versionAnchor } from "./-Sidebar.css";
import { navLink, versionAnchor } from "./-Sidebar.css";

// Not a route -- the '-' prefix keeps TanStack Router's file-based generator from treating this as one.
const NAV_ITEMS = [
{ to: "/convert", label: "Convert", icon: IconArrowsExchange },
{ to: "/editors", label: "Editors", icon: IconEdit },
{ to: "/metadata", label: "Metadata", icon: IconTags },
{ to: "/inspect", label: "Inspect", icon: IconFileSearch },
{ to: "/fonts", label: "Fonts", icon: IconTypography },
{ to: "/recent", label: "Recent", icon: IconHistory },
{ to: "/package", label: "Package / JSON", icon: IconJson },
{ to: "/odb", label: ".odb", icon: IconDatabase },
] as const;

// Tools already tracked as follow-up work -- headroom in the nav without inventing empty route files ahead of time.
const PLANNED_ITEMS = [
{ label: "Editors", icon: IconEdit },
{ label: ".odm", icon: IconBooks },
{ to: "/odm", label: ".odm", icon: IconBooks },
] as const;

// Build-time git state (see vite.config.ts's `define` block) rather than a dry-run prediction: whenever this build's HEAD is an exact semantic-release tag, CI's own job graph guarantees that tag already exists on disk (the deploy job checks out `ref: main` fresh, strictly after the release job pushed) -- there is nothing to predict, only real state to read.
Expand Down Expand Up @@ -64,17 +60,6 @@ export function Sidebar() {
)}
</Link>
))}
{PLANNED_ITEMS.map((item) => (
<Tooltip key={item.label} label="Coming soon" position="right">
<NavLink
component="div"
label={item.label}
leftSection={<item.icon size={18} />}
disabled
className={disabledNavItem}
/>
</Tooltip>
))}
</Stack>
<Tooltip label={tooltipLabel} position="right">
<Anchor
Expand Down
Loading