diff --git a/app/yoke-designer/yoke.css b/app/yoke-designer/yoke.css index 112f76c..221b953 100644 --- a/app/yoke-designer/yoke.css +++ b/app/yoke-designer/yoke.css @@ -23,6 +23,19 @@ font-weight: 600; margin-bottom: 8px; } +.yoke-plan-comparison .yoke-canvas-navigable { + height: min(48vh, 440px); +} +.yoke-plan-zoom { + justify-content: flex-start; + flex-wrap: wrap; + margin-bottom: 8px; + margin-left: 0; +} +.yoke-plan-zoom output { + min-width: 42px; + text-align: center; +} .yoke-transfer-options { padding: 16px; border: 1px solid var(--line); diff --git a/components/use-yoke-navigation.ts b/components/use-yoke-navigation.ts index e060183..4653551 100644 --- a/components/use-yoke-navigation.ts +++ b/components/use-yoke-navigation.ts @@ -66,7 +66,7 @@ export function useYokeNavigation( } const fit = useEffectEvent(() => { const parent = canvas.current?.parentElement; - if (!enabled || !parent || view !== "repeat") return; + if (!enabled || !parent || view === "chart") return; anchor.current = null; options.onZoom?.( fitYokeZoom( diff --git a/components/yoke-plan-dialog.tsx b/components/yoke-plan-dialog.tsx index a7ed7c5..bc4e794 100644 --- a/components/yoke-plan-dialog.tsx +++ b/components/yoke-plan-dialog.tsx @@ -9,7 +9,7 @@ import { type YokeMeasurements, type IncreaseEvent, } from "@/lib/yoke-plan"; -import { createYokeProject } from "@/lib/yoke-project"; +import { createYokeProject, type YokeProject } from "@/lib/yoke-project"; import { reshapeYoke, yokeDocument, @@ -23,6 +23,64 @@ import type { YokeField } from "@/lib/yoke-measurement-fields"; import type { YokeMeasurementMode } from "@/lib/yoke-measurement-mode"; import type { YokeShortRows } from "@/lib/yoke-short-rows"; +const noEdit = () => {}; + +function PlanCanvas({ + project, + view, + label, +}: { + project: YokeProject; + view: "repeat" | "whole"; + label: string; +}) { + const [zoom, setZoom] = useState(25); + const [fitRequest, setFitRequest] = useState(1); + return ( +
+
{label}
+
+ + {zoom}% + + +
+ +
+ ); +} + export function YokePlanDialog({ document, initialMeasurements, @@ -194,27 +252,17 @@ export function YokePlanDialog({ ) : null}
{base?.yoke ? ( -
-
Original · unchanged
- {}} - onStrokeStart={() => {}} - /> -
- ) : null} -
-
{base ? "Proposed" : "Your yoke"}
- {}} - onStrokeStart={() => {}} + -
+ ) : null} +
Neckline short rows diff --git a/e2e/yoke-editor.spec.ts b/e2e/yoke-editor.spec.ts index d9c2fe7..ccfefec 100644 --- a/e2e/yoke-editor.spec.ts +++ b/e2e/yoke-editor.spec.ts @@ -26,7 +26,7 @@ const active = async (page: Page) => { const state = await saved(page); return state.tabs.find((tab) => tab.id === state.activeTabId)!; }; -async function create(page: Page) { +async function previewPlan(page: Page) { await page.addInitScript(() => localStorage.setItem("knitplot-tutorial-v1", "seen"), ); @@ -42,6 +42,10 @@ async function create(page: Page) { await dialog .getByRole("button", { name: "Preview yoke", exact: true }) .click(); + return dialog; +} +async function create(page: Page) { + const dialog = await previewPlan(page); await dialog .getByRole("button", { name: "Create yoke", exact: true }) .click(); @@ -53,6 +57,35 @@ async function create(page: Page) { .toBe(true); } +test("the planning preview fits on a small screen and can zoom while editing short rows", async ({ + page, +}) => { + await page.setViewportSize({ width: 700, height: 600 }); + const dialog = await previewPlan(page); + await dialog + .locator("summary") + .filter({ hasText: "Neckline short rows" }) + .click(); + const zoom = dialog.getByRole("group", { name: "Your yoke zoom" }); + const canvas = dialog.getByRole("img", { name: "Whole yoke drawing canvas" }); + const fits = () => + canvas.evaluate((element) => { + const box = element.getBoundingClientRect(); + const parent = element.parentElement!; + return ( + box.width <= parent.clientWidth && box.height <= parent.clientHeight + ); + }); + const width = async () => (await canvas.boundingBox())!.width; + await expect.poll(fits).toBe(true); + const fitted = await width(); + await zoom.getByRole("button", { name: "Zoom out" }).click(); + expect(await width()).toBeLessThan(fitted); + await zoom.getByRole("button", { name: "Zoom in" }).click(); + await zoom.getByRole("button", { name: "Fit preview" }).click(); + await expect.poll(width).toBeCloseTo(fitted, 1); +}); + for (const custom of [false, true]) { test(`complete yoke workflow: ${custom ? "non-repeating custom shaping" : "identical repeats and short rows"}`, async ({ page,