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}
+
+
+
+
+
+
+
+
+ );
+}
+
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,