@pixel-point/toolcraft 0.0.8 → 0.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -9
- package/package.json +1 -1
- package/src/generate.mjs +42 -5
- package/src/generate.test.mjs +40 -0
- package/src/package-json.mjs +15 -0
- package/src/package-json.test.mjs +14 -1
- package/templates/runtime/contracts/component-contracts.test.ts +251 -47
- package/templates/runtime/contracts/component-contracts.ts +130 -57
- package/templates/runtime/contracts/decision-contracts.test.ts +38 -1
- package/templates/runtime/contracts/decision-contracts.ts +22 -9
- package/templates/runtime/export/export.test.ts +63 -0
- package/templates/runtime/export/export.ts +55 -0
- package/templates/runtime/index.ts +1 -0
- package/templates/runtime/react/canvas-shell.test.tsx +58 -4
- package/templates/runtime/react/canvas-shell.tsx +31 -9
- package/templates/runtime/react/controls-panel.test.tsx +916 -108
- package/templates/runtime/react/controls-panel.tsx +136 -30
- package/templates/runtime/react/runtime-public-api.test.tsx +1 -1
- package/templates/runtime/react/settings-transfer.test.ts +4 -0
- package/templates/runtime/react/settings-transfer.ts +6 -1
- package/templates/runtime/react/timeline-panel.test.tsx +14 -0
- package/templates/runtime/react/timeline-panel.tsx +44 -7
- package/templates/runtime/react/toolcraft-app.integration.test.tsx +9 -1
- package/templates/runtime/react/toolcraft-app.test.tsx +112 -3
- package/templates/runtime/react/toolcraft-app.tsx +56 -37
- package/templates/runtime/schema/define-toolcraft.test.ts +266 -170
- package/templates/runtime/schema/define-toolcraft.ts +140 -246
- package/templates/runtime/schema/runtime-targets.ts +21 -0
- package/templates/runtime/schema/types.ts +44 -0
- package/templates/runtime/state/create-template-state.test.ts +156 -0
- package/templates/runtime/state/create-template-state.ts +38 -8
- package/templates/runtime/state/media-defaults.ts +105 -0
- package/templates/runtime/state/persistence.test.ts +58 -0
- package/templates/runtime/state/persistence.ts +105 -1
- package/templates/runtime/state/reducer.test.ts +280 -4
- package/templates/runtime/state/reducer.ts +195 -9
- package/templates/runtime/state/timeline-loop.test.ts +71 -0
- package/templates/runtime/state/timeline-loop.ts +35 -0
- package/templates/runtime/state/types.ts +27 -0
- package/templates/runtime/testing/performance.test.ts +810 -21
- package/templates/runtime/testing/performance.ts +823 -53
- package/templates/starter/AGENTS.md +24 -18
- package/templates/starter/docs/toolcraft/README.md +8 -4
- package/templates/starter/docs/toolcraft/acceptance-testing.md +43 -10
- package/templates/starter/docs/toolcraft/agent-worklog.md +1 -0
- package/templates/starter/docs/toolcraft/assembly-workflow.md +55 -20
- package/templates/starter/docs/toolcraft/component-rules.md +75 -42
- package/templates/starter/docs/toolcraft/decision-contract.md +2 -0
- package/templates/starter/docs/toolcraft/performance.md +37 -11
- package/templates/starter/docs/toolcraft/schema-reference.md +171 -41
- package/templates/starter/docs/toolcraft/workflow.md +5 -2
- package/templates/starter/e2e/app-browser-acceptance.spec.ts +3 -3
- package/templates/starter/e2e/app-performance.spec.ts +55 -2
- package/templates/starter/e2e/performance-helpers.ts +45 -0
- package/templates/starter/gitignore +1 -0
- package/templates/starter/index.html +1 -0
- package/templates/starter/package.json +3 -0
- package/templates/starter/playwright.config.ts +1 -1
- package/templates/starter/scripts/check-toolcraft-docs.mjs +1 -0
- package/templates/starter/scripts/run-vite-on-free-port.mjs +114 -13
- package/templates/starter/scripts/toolcraft-port.mjs +280 -0
- package/templates/starter/scripts/toolcraft-port.test.mjs +207 -1
- package/templates/starter/src/app/starter-acceptance.test.ts +3412 -479
- package/templates/starter/src/app/starter-acceptance.ts +1453 -97
- package/templates/starter/src/app/starter-performance.test.ts +111 -7
- package/templates/starter/src/app/starter-performance.ts +5 -0
- package/templates/starter/src/app/starter-schema.test.ts +32 -7
- package/templates/starter/src/app/starter-schema.ts +6 -2
- package/templates/starter/vite.config.ts +58 -2
- package/templates/ui/components/control-layout/index.tsx +8 -3
- package/templates/ui/components/controls/actions/actions-control.tsx +56 -5
- package/templates/ui/components/controls/code-textarea/code-textarea-control.tsx +7 -3
- package/templates/ui/components/controls/color/index.ts +4 -1
- package/templates/ui/components/controls/color/palette-control.tsx +34 -4
- package/templates/ui/components/controls/color/style-guide-color-picker-logic.ts +7 -2
- package/templates/ui/components/controls/color/style-guide-color-picker.tsx +2 -2
- package/templates/ui/components/controls/file-drop/file-drop-control.tsx +186 -14
- package/templates/ui/components/controls/file-drop/index.ts +6 -1
- package/templates/ui/components/controls/index.ts +4 -0
- package/templates/ui/components/controls/range-input/range-input-control.tsx +12 -4
- package/templates/ui/components/controls/range-slider/range-slider-value.ts +3 -1
- package/templates/ui/components/controls/select/select-control.tsx +8 -25
- package/templates/ui/components/controls/slider/slider-value.ts +0 -1
- package/templates/ui/components/controls/text-input/text-input-control.tsx +4 -1
- package/templates/ui/components/controls/vector/index.ts +1 -0
- package/templates/ui/components/controls/vector/vector-control.tsx +109 -12
- package/templates/ui/components/panel/panel-actions.tsx +1 -1
- package/templates/ui/components/panel/panel-section.tsx +29 -5
|
@@ -2,10 +2,12 @@ import type {
|
|
|
2
2
|
ToolcraftLocalStoragePersistenceSchema,
|
|
3
3
|
ResolvedToolcraftAppSchema,
|
|
4
4
|
} from "../schema/types";
|
|
5
|
+
import { isToolcraftTimelinePanelRuntimeTarget } from "../schema/runtime-targets";
|
|
5
6
|
import type {
|
|
6
7
|
ToolcraftCanvasState,
|
|
7
8
|
ToolcraftInitialState,
|
|
8
9
|
ToolcraftLayer,
|
|
10
|
+
ToolcraftMediaAsset,
|
|
9
11
|
ToolcraftPanelId,
|
|
10
12
|
ToolcraftPanelState,
|
|
11
13
|
ToolcraftState,
|
|
@@ -92,6 +94,14 @@ function readPanel(value: unknown): Partial<ToolcraftPanelState> | undefined {
|
|
|
92
94
|
panel.collapsed = value.collapsed;
|
|
93
95
|
}
|
|
94
96
|
|
|
97
|
+
if (typeof value.extended === "boolean") {
|
|
98
|
+
panel.extended = value.extended;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (typeof value.hidden === "boolean") {
|
|
102
|
+
panel.hidden = value.hidden;
|
|
103
|
+
}
|
|
104
|
+
|
|
95
105
|
return Object.keys(panel).length > 0 ? panel : undefined;
|
|
96
106
|
}
|
|
97
107
|
|
|
@@ -161,6 +171,85 @@ function readLayers(value: unknown): ToolcraftLayer[] | undefined {
|
|
|
161
171
|
});
|
|
162
172
|
}
|
|
163
173
|
|
|
174
|
+
function readMediaAsset(value: unknown): ToolcraftMediaAsset | undefined {
|
|
175
|
+
if (
|
|
176
|
+
!isRecord(value) ||
|
|
177
|
+
typeof value.id !== "string" ||
|
|
178
|
+
typeof value.layerId !== "string" ||
|
|
179
|
+
typeof value.dataUrl !== "string" ||
|
|
180
|
+
typeof value.fileName !== "string" ||
|
|
181
|
+
typeof value.mimeType !== "string"
|
|
182
|
+
) {
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const position = readPoint(value.position);
|
|
187
|
+
|
|
188
|
+
if (!position) {
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const mediaAsset: ToolcraftMediaAsset = {
|
|
193
|
+
dataUrl: value.dataUrl,
|
|
194
|
+
fileName: value.fileName,
|
|
195
|
+
id: value.id,
|
|
196
|
+
layerId: value.layerId,
|
|
197
|
+
mimeType: value.mimeType,
|
|
198
|
+
position,
|
|
199
|
+
};
|
|
200
|
+
const size = readCanvasSize(value.size);
|
|
201
|
+
|
|
202
|
+
if (value.assetKind === "file" || value.assetKind === "image") {
|
|
203
|
+
mediaAsset.assetKind = value.assetKind;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (size) {
|
|
207
|
+
mediaAsset.size = size;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (typeof value.sourceTarget === "string") {
|
|
211
|
+
mediaAsset.sourceTarget = value.sourceTarget;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (isRecord(value.transform)) {
|
|
215
|
+
mediaAsset.transform = {};
|
|
216
|
+
|
|
217
|
+
if (typeof value.transform.flipHorizontal === "boolean") {
|
|
218
|
+
mediaAsset.transform.flipHorizontal = value.transform.flipHorizontal;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (typeof value.transform.flipVertical === "boolean") {
|
|
222
|
+
mediaAsset.transform.flipVertical = value.transform.flipVertical;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (
|
|
226
|
+
value.transform.rotationDeg === 0 ||
|
|
227
|
+
value.transform.rotationDeg === 90 ||
|
|
228
|
+
value.transform.rotationDeg === 180 ||
|
|
229
|
+
value.transform.rotationDeg === 270
|
|
230
|
+
) {
|
|
231
|
+
mediaAsset.transform.rotationDeg = value.transform.rotationDeg;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (Object.keys(mediaAsset.transform).length === 0) {
|
|
235
|
+
delete mediaAsset.transform;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return mediaAsset;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function readMediaAssets(value: unknown): ToolcraftMediaAsset[] | undefined {
|
|
243
|
+
if (!Array.isArray(value)) {
|
|
244
|
+
return undefined;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return value.flatMap((item) => {
|
|
248
|
+
const mediaAsset = readMediaAsset(item);
|
|
249
|
+
return mediaAsset ? [mediaAsset] : [];
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
164
253
|
function readBezierControlPoints(
|
|
165
254
|
value: unknown,
|
|
166
255
|
): ToolcraftTimelineBezierControlPoints | undefined {
|
|
@@ -296,7 +385,10 @@ function getKnownValueTargets(schema: ResolvedToolcraftAppSchema): Set<string> {
|
|
|
296
385
|
|
|
297
386
|
for (const section of schema.panels.controls?.sections ?? []) {
|
|
298
387
|
for (const control of Object.values(section.controls)) {
|
|
299
|
-
if (
|
|
388
|
+
if (
|
|
389
|
+
control.type !== "panelActions" &&
|
|
390
|
+
!isToolcraftTimelinePanelRuntimeTarget(control.target)
|
|
391
|
+
) {
|
|
300
392
|
targets.add(control.target);
|
|
301
393
|
}
|
|
302
394
|
}
|
|
@@ -369,6 +461,10 @@ export function createToolcraftPersistenceSnapshot(
|
|
|
369
461
|
initialState.selectedLayerId = state.selectedLayerId;
|
|
370
462
|
}
|
|
371
463
|
|
|
464
|
+
if (included.has("media")) {
|
|
465
|
+
initialState.mediaAssets = state.mediaAssets;
|
|
466
|
+
}
|
|
467
|
+
|
|
372
468
|
return {
|
|
373
469
|
state: initialState,
|
|
374
470
|
version: persistence.version,
|
|
@@ -450,6 +546,14 @@ export function parseToolcraftPersistenceSnapshot(
|
|
|
450
546
|
}
|
|
451
547
|
}
|
|
452
548
|
|
|
549
|
+
if (included.has("media")) {
|
|
550
|
+
const mediaAssets = readMediaAssets(persistedState.mediaAssets);
|
|
551
|
+
|
|
552
|
+
if (mediaAssets) {
|
|
553
|
+
initialState.mediaAssets = mediaAssets;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
453
557
|
return Object.keys(initialState).length > 0 ? initialState : undefined;
|
|
454
558
|
}
|
|
455
559
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
|
|
3
3
|
import { defineToolcraft } from "../schema/define-toolcraft";
|
|
4
|
+
import type { ToolcraftInitialState } from "./types";
|
|
4
5
|
import { createToolcraftState } from "./create-template-state";
|
|
5
6
|
import { toolcraftReducer } from "./reducer";
|
|
6
7
|
|
|
7
|
-
function createState() {
|
|
8
|
+
function createState(initialState?: ToolcraftInitialState) {
|
|
8
9
|
const app = defineToolcraft({
|
|
9
10
|
canvas: {
|
|
10
11
|
enabled: true,
|
|
@@ -28,7 +29,7 @@ function createState() {
|
|
|
28
29
|
},
|
|
29
30
|
});
|
|
30
31
|
|
|
31
|
-
return createToolcraftState(app);
|
|
32
|
+
return createToolcraftState(app, initialState);
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
describe("toolcraftReducer", () => {
|
|
@@ -147,6 +148,70 @@ describe("toolcraftReducer", () => {
|
|
|
147
148
|
});
|
|
148
149
|
});
|
|
149
150
|
|
|
151
|
+
it("restores predefined fileDrop media on global and section reset", () => {
|
|
152
|
+
const app = defineToolcraft({
|
|
153
|
+
canvas: { enabled: true, upload: true },
|
|
154
|
+
media: {
|
|
155
|
+
defaultAssets: [
|
|
156
|
+
{
|
|
157
|
+
dataUrl: "data:image/png;base64,AAAA",
|
|
158
|
+
fileName: "default-background.png",
|
|
159
|
+
id: "default-background",
|
|
160
|
+
layerId: "default-background-layer",
|
|
161
|
+
mimeType: "image/png",
|
|
162
|
+
size: { height: 1080, unit: "px", width: 1920 },
|
|
163
|
+
sourceTarget: "media.background",
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
},
|
|
167
|
+
panels: {
|
|
168
|
+
controls: {
|
|
169
|
+
sections: [
|
|
170
|
+
{
|
|
171
|
+
controls: {
|
|
172
|
+
background: {
|
|
173
|
+
defaultValue: null,
|
|
174
|
+
target: "media.background",
|
|
175
|
+
type: "fileDrop",
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
title: "Background",
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
title: "Controls",
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
const initial = createToolcraftState(app);
|
|
186
|
+
const deleted = toolcraftReducer(initial, {
|
|
187
|
+
mediaId: "default-background",
|
|
188
|
+
type: "media.delete",
|
|
189
|
+
});
|
|
190
|
+
const sectionReset = toolcraftReducer(deleted, {
|
|
191
|
+
label: "Reset Background section",
|
|
192
|
+
targets: ["media.background"],
|
|
193
|
+
type: "controls.resetTargets",
|
|
194
|
+
});
|
|
195
|
+
const deletedAgain = toolcraftReducer(sectionReset, {
|
|
196
|
+
mediaId: "default-background",
|
|
197
|
+
type: "media.delete",
|
|
198
|
+
});
|
|
199
|
+
const globalReset = toolcraftReducer(deletedAgain, { type: "controls.reset" });
|
|
200
|
+
|
|
201
|
+
expect(initial.mediaAssets.map((asset) => asset.id)).toEqual(["default-background"]);
|
|
202
|
+
expect(deleted.mediaAssets).toEqual([]);
|
|
203
|
+
expect(sectionReset.mediaAssets.map((asset) => asset.id)).toEqual([
|
|
204
|
+
"default-background",
|
|
205
|
+
]);
|
|
206
|
+
expect(sectionReset.layers.map((layer) => layer.id)).toEqual([
|
|
207
|
+
"default-background-layer",
|
|
208
|
+
]);
|
|
209
|
+
expect(sectionReset.selectedLayerId).toBe("default-background-layer");
|
|
210
|
+
expect(globalReset.mediaAssets.map((asset) => asset.id)).toEqual([
|
|
211
|
+
"default-background",
|
|
212
|
+
]);
|
|
213
|
+
});
|
|
214
|
+
|
|
150
215
|
it("stores arbitrary fileDrop media without image dimensions and clears it on reset", () => {
|
|
151
216
|
const app = defineToolcraft({
|
|
152
217
|
canvas: { enabled: true, upload: true },
|
|
@@ -194,6 +259,64 @@ describe("toolcraftReducer", () => {
|
|
|
194
259
|
expect(state.mediaAssets).toEqual([]);
|
|
195
260
|
});
|
|
196
261
|
|
|
262
|
+
it("records image media rotate and flip transforms in runtime history", () => {
|
|
263
|
+
const imported = toolcraftReducer(createState(), {
|
|
264
|
+
asset: {
|
|
265
|
+
assetKind: "image",
|
|
266
|
+
dataUrl: "data:image/png;base64,test",
|
|
267
|
+
fileName: "source.png",
|
|
268
|
+
mimeType: "image/png",
|
|
269
|
+
position: { x: 0, y: 0 },
|
|
270
|
+
size: { height: 768, unit: "px", width: 1024 },
|
|
271
|
+
},
|
|
272
|
+
type: "media.import",
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
const rotated = toolcraftReducer(imported, {
|
|
276
|
+
mediaId: imported.mediaAssets[0]?.id ?? "",
|
|
277
|
+
operation: "rotate-right",
|
|
278
|
+
type: "media.transform",
|
|
279
|
+
});
|
|
280
|
+
const flipped = toolcraftReducer(rotated, {
|
|
281
|
+
mediaId: imported.mediaAssets[0]?.id ?? "",
|
|
282
|
+
operation: "flip-horizontal",
|
|
283
|
+
type: "media.transform",
|
|
284
|
+
});
|
|
285
|
+
const undone = toolcraftReducer(flipped, { type: "history.undo" });
|
|
286
|
+
|
|
287
|
+
expect(rotated.mediaAssets[0]?.transform).toEqual({ rotationDeg: 90 });
|
|
288
|
+
expect(flipped.mediaAssets[0]?.transform).toEqual({
|
|
289
|
+
flipHorizontal: true,
|
|
290
|
+
rotationDeg: 90,
|
|
291
|
+
});
|
|
292
|
+
expect(flipped.history.undo.at(-1)).toMatchObject({
|
|
293
|
+
before: { mediaAssets: rotated.mediaAssets },
|
|
294
|
+
label: "Transform media",
|
|
295
|
+
});
|
|
296
|
+
expect(undone.mediaAssets[0]?.transform).toEqual({ rotationDeg: 90 });
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it("ignores image transforms for arbitrary file media", () => {
|
|
300
|
+
const imported = toolcraftReducer(createState(), {
|
|
301
|
+
asset: {
|
|
302
|
+
assetKind: "file",
|
|
303
|
+
dataUrl: "data:text/plain;base64,aGVsbG8=",
|
|
304
|
+
fileName: "notes.txt",
|
|
305
|
+
mimeType: "text/plain",
|
|
306
|
+
position: { x: 0, y: 0 },
|
|
307
|
+
},
|
|
308
|
+
type: "media.import",
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
const transformed = toolcraftReducer(imported, {
|
|
312
|
+
mediaId: imported.mediaAssets[0]?.id ?? "",
|
|
313
|
+
operation: "rotate-right",
|
|
314
|
+
type: "media.transform",
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
expect(transformed).toBe(imported);
|
|
318
|
+
});
|
|
319
|
+
|
|
197
320
|
it("clears only selected fileDrop media on section reset", () => {
|
|
198
321
|
const app = defineToolcraft({
|
|
199
322
|
canvas: { enabled: true, upload: true },
|
|
@@ -554,6 +677,52 @@ describe("toolcraftReducer", () => {
|
|
|
554
677
|
expect(next.values).toBe(state.values);
|
|
555
678
|
});
|
|
556
679
|
|
|
680
|
+
it("stores timeline extended presentation outside control values", () => {
|
|
681
|
+
const state = createState();
|
|
682
|
+
|
|
683
|
+
const extended = toolcraftReducer(state, {
|
|
684
|
+
target: "panels.timeline.extended",
|
|
685
|
+
type: "controls.setValue",
|
|
686
|
+
value: true,
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
expect(extended.panels.timeline.extended).toBe(true);
|
|
690
|
+
expect(extended.values).toBe(state.values);
|
|
691
|
+
expect(extended.history).toBe(state.history);
|
|
692
|
+
|
|
693
|
+
const compact = toolcraftReducer(extended, {
|
|
694
|
+
target: "panels.timeline.extended",
|
|
695
|
+
type: "controls.setValue",
|
|
696
|
+
value: false,
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
expect(compact.panels.timeline.extended).toBe(false);
|
|
700
|
+
expect(compact.values).toBe(state.values);
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
it("keeps legacy timeline panel visibility outside control values", () => {
|
|
704
|
+
const state = createState();
|
|
705
|
+
|
|
706
|
+
const hidden = toolcraftReducer(state, {
|
|
707
|
+
target: "panels.timeline.visible",
|
|
708
|
+
type: "controls.setValue",
|
|
709
|
+
value: false,
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
expect(hidden.panels.timeline.hidden).toBe(true);
|
|
713
|
+
expect(hidden.values).toBe(state.values);
|
|
714
|
+
expect(hidden.history).toBe(state.history);
|
|
715
|
+
|
|
716
|
+
const shown = toolcraftReducer(hidden, {
|
|
717
|
+
hidden: false,
|
|
718
|
+
panelId: "timeline",
|
|
719
|
+
type: "panels.setHidden",
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
expect(shown.panels.timeline.hidden).toBe(false);
|
|
723
|
+
expect(shown.values).toBe(state.values);
|
|
724
|
+
});
|
|
725
|
+
|
|
557
726
|
it("resets a panel offset to its default position", () => {
|
|
558
727
|
const moved = toolcraftReducer(createState(), {
|
|
559
728
|
offset: { x: 40, y: 80 },
|
|
@@ -630,9 +799,85 @@ describe("toolcraftReducer", () => {
|
|
|
630
799
|
expect(next.history.undo.at(-1)?.label).toBe("Delete media");
|
|
631
800
|
});
|
|
632
801
|
|
|
633
|
-
it("
|
|
802
|
+
it("keeps the current editable-output canvas size when importing background/source images", () => {
|
|
634
803
|
const app = defineToolcraft({
|
|
635
|
-
canvas: {
|
|
804
|
+
canvas: {
|
|
805
|
+
enabled: true,
|
|
806
|
+
size: { height: 720, unit: "px", width: 1280 },
|
|
807
|
+
upload: true,
|
|
808
|
+
},
|
|
809
|
+
panels: {},
|
|
810
|
+
});
|
|
811
|
+
const state = createToolcraftState(app);
|
|
812
|
+
const imported = toolcraftReducer(state, {
|
|
813
|
+
asset: {
|
|
814
|
+
dataUrl: "data:image/png;base64,wide",
|
|
815
|
+
fileName: "wide-source.png",
|
|
816
|
+
mimeType: "image/png",
|
|
817
|
+
position: { x: 42, y: 24 },
|
|
818
|
+
size: { height: 1000, unit: "px", width: 4000 },
|
|
819
|
+
},
|
|
820
|
+
type: "media.import",
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
expect(imported.schema.canvas.sizing).toEqual({ mode: "editable-output" });
|
|
824
|
+
expect(imported.canvas.size).toEqual({ height: 720, unit: "px", width: 1280 });
|
|
825
|
+
expect(imported.mediaAssets[0]).toMatchObject({
|
|
826
|
+
fileName: "wide-source.png",
|
|
827
|
+
position: { x: 42, y: 24 },
|
|
828
|
+
size: { height: 1000, unit: "px", width: 4000 },
|
|
829
|
+
});
|
|
830
|
+
expect(imported.history.undo.at(-1)?.after).not.toHaveProperty("canvas.size");
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
it("keeps manually edited editable-output canvas size when importing source images", () => {
|
|
834
|
+
const app = defineToolcraft({
|
|
835
|
+
canvas: {
|
|
836
|
+
enabled: true,
|
|
837
|
+
upload: true,
|
|
838
|
+
},
|
|
839
|
+
panels: {
|
|
840
|
+
controls: {
|
|
841
|
+
sections: [],
|
|
842
|
+
title: "Controls",
|
|
843
|
+
},
|
|
844
|
+
},
|
|
845
|
+
});
|
|
846
|
+
const widthChanged = toolcraftReducer(createToolcraftState(app), {
|
|
847
|
+
target: "canvas.size.width",
|
|
848
|
+
type: "controls.setValue",
|
|
849
|
+
value: "1440",
|
|
850
|
+
});
|
|
851
|
+
const heightChanged = toolcraftReducer(widthChanged, {
|
|
852
|
+
target: "canvas.size.height",
|
|
853
|
+
type: "controls.setValue",
|
|
854
|
+
value: "900",
|
|
855
|
+
});
|
|
856
|
+
const imported = toolcraftReducer(heightChanged, {
|
|
857
|
+
asset: {
|
|
858
|
+
dataUrl: "data:image/png;base64,tall",
|
|
859
|
+
fileName: "tall-source.png",
|
|
860
|
+
mimeType: "image/png",
|
|
861
|
+
position: { x: 0, y: 0 },
|
|
862
|
+
size: { height: 4000, unit: "px", width: 1000 },
|
|
863
|
+
},
|
|
864
|
+
type: "media.import",
|
|
865
|
+
});
|
|
866
|
+
|
|
867
|
+
expect(imported.schema.canvas.sizing).toEqual({ mode: "editable-output" });
|
|
868
|
+
expect(imported.canvas.size).toEqual({ height: 900, unit: "px", width: 1440 });
|
|
869
|
+
expect(imported.values["canvas.size.width"]).toBe(1440);
|
|
870
|
+
expect(imported.values["canvas.size.height"]).toBe(900);
|
|
871
|
+
expect(imported.mediaAssets[0]).toMatchObject({
|
|
872
|
+
fileName: "tall-source.png",
|
|
873
|
+
size: { height: 4000, unit: "px", width: 1000 },
|
|
874
|
+
});
|
|
875
|
+
expect(imported.history.undo.at(-1)?.after).not.toHaveProperty("canvas.size");
|
|
876
|
+
});
|
|
877
|
+
|
|
878
|
+
it("sizes explicit intrinsic media apps from the imported image and replaces single-layer media", () => {
|
|
879
|
+
const app = defineToolcraft({
|
|
880
|
+
canvas: { enabled: true, sizing: { mode: "intrinsic-media" }, upload: true },
|
|
636
881
|
panels: {},
|
|
637
882
|
});
|
|
638
883
|
const state = createToolcraftState(app);
|
|
@@ -1236,6 +1481,37 @@ describe("toolcraftReducer", () => {
|
|
|
1236
1481
|
expect(scrubbed.history.undo).toHaveLength(0);
|
|
1237
1482
|
});
|
|
1238
1483
|
|
|
1484
|
+
it("keeps loop state when timeline duration changes", () => {
|
|
1485
|
+
const state = toolcraftReducer(
|
|
1486
|
+
createState({
|
|
1487
|
+
timeline: {
|
|
1488
|
+
currentTimeSeconds: 9,
|
|
1489
|
+
durationSeconds: 10,
|
|
1490
|
+
isLooping: true,
|
|
1491
|
+
isPlaying: true,
|
|
1492
|
+
},
|
|
1493
|
+
}),
|
|
1494
|
+
{
|
|
1495
|
+
target: "selectedLayer.opacity",
|
|
1496
|
+
type: "controls.setValue",
|
|
1497
|
+
value: 42,
|
|
1498
|
+
},
|
|
1499
|
+
);
|
|
1500
|
+
|
|
1501
|
+
const resized = toolcraftReducer(state, {
|
|
1502
|
+
durationSeconds: 6,
|
|
1503
|
+
type: "timeline.setDuration",
|
|
1504
|
+
});
|
|
1505
|
+
|
|
1506
|
+
expect(resized.timeline.durationSeconds).toBe(6);
|
|
1507
|
+
expect(resized.timeline.currentTimeSeconds).toBe(6);
|
|
1508
|
+
expect(resized.timeline.isPlaying).toBe(true);
|
|
1509
|
+
expect(resized.timeline.isLooping).toBe(true);
|
|
1510
|
+
expect(resized.values).toEqual(state.values);
|
|
1511
|
+
expect(resized.values["selectedLayer.opacity"]).toBe(42);
|
|
1512
|
+
expect(resized.defaults).toEqual(state.defaults);
|
|
1513
|
+
});
|
|
1514
|
+
|
|
1239
1515
|
it("restarts playback from the beginning when play is pressed at the non-looping end", () => {
|
|
1240
1516
|
const paused = toolcraftReducer(createState(), { type: "timeline.togglePlayback" });
|
|
1241
1517
|
const loopDisabled = toolcraftReducer(paused, { type: "timeline.toggleLoop" });
|