@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
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getToolcraftCanvasSizeTargetDimension,
|
|
3
3
|
isToolcraftCanvasAspectRatioTarget,
|
|
4
|
+
isToolcraftTimelinePanelExtendedTarget,
|
|
5
|
+
isToolcraftTimelinePanelVisibleTarget,
|
|
4
6
|
} from "../schema/runtime-targets";
|
|
5
7
|
import { getToolcraftCanvasAspectRatioPreset } from "../schema/canvas-aspect-ratio-presets";
|
|
6
8
|
import {
|
|
@@ -8,6 +10,11 @@ import {
|
|
|
8
10
|
toolcraftCanvasZoomDefault,
|
|
9
11
|
toolcraftCanvasZoomStep,
|
|
10
12
|
} from "./canvas-zoom";
|
|
13
|
+
import {
|
|
14
|
+
cloneToolcraftLayers,
|
|
15
|
+
cloneToolcraftMediaAssets,
|
|
16
|
+
createToolcraftDefaultMediaState,
|
|
17
|
+
} from "./media-defaults";
|
|
11
18
|
import { getMediaReadyTimelineState } from "./timeline-readiness";
|
|
12
19
|
import type {
|
|
13
20
|
ToolcraftCommand,
|
|
@@ -15,6 +22,8 @@ import type {
|
|
|
15
22
|
ToolcraftHistoryMode,
|
|
16
23
|
ToolcraftLayer,
|
|
17
24
|
ToolcraftLayerDraft,
|
|
25
|
+
ToolcraftMediaAsset,
|
|
26
|
+
ToolcraftMediaTransform,
|
|
18
27
|
ToolcraftState,
|
|
19
28
|
ToolcraftTimelineKeyframe,
|
|
20
29
|
ToolcraftTimelineKeyframeGroup,
|
|
@@ -235,7 +244,7 @@ function getResetMediaPatch(
|
|
|
235
244
|
state: ToolcraftState,
|
|
236
245
|
targets?: ReadonlySet<string>,
|
|
237
246
|
): Pick<ToolcraftHistoryPatch, "after" | "before"> | null {
|
|
238
|
-
if (state.schema.panels.layers
|
|
247
|
+
if (state.schema.panels.layers) {
|
|
239
248
|
return null;
|
|
240
249
|
}
|
|
241
250
|
|
|
@@ -245,15 +254,46 @@ function getResetMediaPatch(
|
|
|
245
254
|
return null;
|
|
246
255
|
}
|
|
247
256
|
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
257
|
+
const defaultMediaState = createToolcraftDefaultMediaState(state.schema);
|
|
258
|
+
const defaultTargetMediaAssets = defaultMediaState.mediaAssets.filter((asset) =>
|
|
259
|
+
asset.sourceTarget ? fileDropTargets.has(asset.sourceTarget) : false,
|
|
260
|
+
);
|
|
261
|
+
const defaultTargetLayerIds = new Set(defaultTargetMediaAssets.map((asset) => asset.layerId));
|
|
262
|
+
const currentTargetLayerIds = new Set(
|
|
263
|
+
state.mediaAssets.flatMap((asset) =>
|
|
264
|
+
asset.sourceTarget && fileDropTargets.has(asset.sourceTarget) ? [asset.layerId] : [],
|
|
265
|
+
),
|
|
266
|
+
);
|
|
267
|
+
const mediaAssets = [
|
|
268
|
+
...state.mediaAssets.filter((asset) => {
|
|
269
|
+
if (asset.sourceTarget) {
|
|
270
|
+
return !fileDropTargets.has(asset.sourceTarget);
|
|
271
|
+
}
|
|
255
272
|
|
|
256
|
-
|
|
273
|
+
return false;
|
|
274
|
+
}),
|
|
275
|
+
...cloneToolcraftMediaAssets(defaultTargetMediaAssets),
|
|
276
|
+
];
|
|
277
|
+
const layers = [
|
|
278
|
+
...state.layers.filter(
|
|
279
|
+
(layer) => !currentTargetLayerIds.has(layer.id) && !defaultTargetLayerIds.has(layer.id),
|
|
280
|
+
),
|
|
281
|
+
...cloneToolcraftLayers(
|
|
282
|
+
defaultMediaState.layers.filter((layer) => defaultTargetLayerIds.has(layer.id)),
|
|
283
|
+
),
|
|
284
|
+
];
|
|
285
|
+
const selectedLayerId =
|
|
286
|
+
state.selectedLayerId && layers.some((layer) => layer.id === state.selectedLayerId)
|
|
287
|
+
? state.selectedLayerId
|
|
288
|
+
: (layers[0]?.id ?? null);
|
|
289
|
+
|
|
290
|
+
if (
|
|
291
|
+
mediaAssets.length === state.mediaAssets.length &&
|
|
292
|
+
mediaAssets.every((asset, index) => asset.id === state.mediaAssets[index]?.id) &&
|
|
293
|
+
layers.length === state.layers.length &&
|
|
294
|
+
layers.every((layer, index) => layer.id === state.layers[index]?.id) &&
|
|
295
|
+
selectedLayerId === state.selectedLayerId
|
|
296
|
+
) {
|
|
257
297
|
return null;
|
|
258
298
|
}
|
|
259
299
|
|
|
@@ -262,11 +302,15 @@ function getResetMediaPatch(
|
|
|
262
302
|
|
|
263
303
|
return {
|
|
264
304
|
after: {
|
|
305
|
+
layers,
|
|
265
306
|
mediaAssets,
|
|
307
|
+
selectedLayerId,
|
|
266
308
|
...(shouldCommitTimeline ? { timeline } : {}),
|
|
267
309
|
},
|
|
268
310
|
before: {
|
|
311
|
+
layers: state.layers,
|
|
269
312
|
mediaAssets: state.mediaAssets,
|
|
313
|
+
selectedLayerId: state.selectedLayerId,
|
|
270
314
|
...(shouldCommitTimeline ? { timeline: state.timeline } : {}),
|
|
271
315
|
},
|
|
272
316
|
};
|
|
@@ -533,6 +577,74 @@ function getImportedLayerName(fileName: string): string {
|
|
|
533
577
|
return name || "Material";
|
|
534
578
|
}
|
|
535
579
|
|
|
580
|
+
function normalizeMediaRotation(rotationDeg: number): 0 | 90 | 180 | 270 {
|
|
581
|
+
const normalized = ((Math.round(rotationDeg / 90) * 90) % 360 + 360) % 360;
|
|
582
|
+
|
|
583
|
+
return normalized === 90 || normalized === 180 || normalized === 270 ? normalized : 0;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function compactMediaTransform(
|
|
587
|
+
transform: ToolcraftMediaTransform,
|
|
588
|
+
): ToolcraftMediaTransform | undefined {
|
|
589
|
+
const normalizedRotation = normalizeMediaRotation(transform.rotationDeg ?? 0);
|
|
590
|
+
const nextTransform: ToolcraftMediaTransform = {};
|
|
591
|
+
|
|
592
|
+
if (normalizedRotation !== 0) {
|
|
593
|
+
nextTransform.rotationDeg = normalizedRotation;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
if (transform.flipHorizontal) {
|
|
597
|
+
nextTransform.flipHorizontal = true;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
if (transform.flipVertical) {
|
|
601
|
+
nextTransform.flipVertical = true;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
return Object.keys(nextTransform).length > 0 ? nextTransform : undefined;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function getTransformedMediaAsset(
|
|
608
|
+
mediaAsset: ToolcraftMediaAsset,
|
|
609
|
+
operation: Extract<ToolcraftCommand, { type: "media.transform" }>["operation"],
|
|
610
|
+
): ToolcraftMediaAsset {
|
|
611
|
+
const currentTransform = mediaAsset.transform ?? {};
|
|
612
|
+
const rotationDeg = normalizeMediaRotation(currentTransform.rotationDeg ?? 0);
|
|
613
|
+
const transform: ToolcraftMediaTransform = {
|
|
614
|
+
flipHorizontal: currentTransform.flipHorizontal,
|
|
615
|
+
flipVertical: currentTransform.flipVertical,
|
|
616
|
+
rotationDeg,
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
switch (operation) {
|
|
620
|
+
case "flip-horizontal":
|
|
621
|
+
transform.flipHorizontal = !transform.flipHorizontal;
|
|
622
|
+
break;
|
|
623
|
+
case "flip-vertical":
|
|
624
|
+
transform.flipVertical = !transform.flipVertical;
|
|
625
|
+
break;
|
|
626
|
+
case "rotate-left":
|
|
627
|
+
transform.rotationDeg = normalizeMediaRotation(rotationDeg - 90);
|
|
628
|
+
break;
|
|
629
|
+
case "rotate-right":
|
|
630
|
+
transform.rotationDeg = normalizeMediaRotation(rotationDeg + 90);
|
|
631
|
+
break;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
const compactTransform = compactMediaTransform(transform);
|
|
635
|
+
|
|
636
|
+
if (!compactTransform) {
|
|
637
|
+
const { transform: _transform, ...rest } = mediaAsset;
|
|
638
|
+
|
|
639
|
+
return rest;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
return {
|
|
643
|
+
...mediaAsset,
|
|
644
|
+
transform: compactTransform,
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
|
|
536
648
|
function getNextLayerName(
|
|
537
649
|
layers: readonly ToolcraftLayer[],
|
|
538
650
|
prefix: "Group" | "Layer",
|
|
@@ -640,6 +752,44 @@ export function toolcraftReducer(
|
|
|
640
752
|
case "controls.setValue": {
|
|
641
753
|
const canvasSizeDimension = getToolcraftCanvasSizeTargetDimension(command.target);
|
|
642
754
|
|
|
755
|
+
if (isToolcraftTimelinePanelExtendedTarget(command.target)) {
|
|
756
|
+
const extended = command.value === true;
|
|
757
|
+
|
|
758
|
+
if (state.panels.timeline.extended === extended) {
|
|
759
|
+
return state;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
return {
|
|
763
|
+
...state,
|
|
764
|
+
panels: {
|
|
765
|
+
...state.panels,
|
|
766
|
+
timeline: {
|
|
767
|
+
...state.panels.timeline,
|
|
768
|
+
extended,
|
|
769
|
+
},
|
|
770
|
+
},
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
if (isToolcraftTimelinePanelVisibleTarget(command.target)) {
|
|
775
|
+
const hidden = command.value === false;
|
|
776
|
+
|
|
777
|
+
if (state.panels.timeline.hidden === hidden) {
|
|
778
|
+
return state;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
return {
|
|
782
|
+
...state,
|
|
783
|
+
panels: {
|
|
784
|
+
...state.panels,
|
|
785
|
+
timeline: {
|
|
786
|
+
...state.panels.timeline,
|
|
787
|
+
hidden,
|
|
788
|
+
},
|
|
789
|
+
},
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
|
|
643
793
|
if (isToolcraftCanvasAspectRatioTarget(command.target)) {
|
|
644
794
|
const ratio = normalizeCanvasAspectRatioValue(command.value, state.canvas.size);
|
|
645
795
|
const size =
|
|
@@ -1131,6 +1281,22 @@ export function toolcraftReducer(
|
|
|
1131
1281
|
},
|
|
1132
1282
|
};
|
|
1133
1283
|
|
|
1284
|
+
case "panels.setHidden":
|
|
1285
|
+
if (state.panels[command.panelId].hidden === command.hidden) {
|
|
1286
|
+
return state;
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
return {
|
|
1290
|
+
...state,
|
|
1291
|
+
panels: {
|
|
1292
|
+
...state.panels,
|
|
1293
|
+
[command.panelId]: {
|
|
1294
|
+
...state.panels[command.panelId],
|
|
1295
|
+
hidden: command.hidden,
|
|
1296
|
+
},
|
|
1297
|
+
},
|
|
1298
|
+
};
|
|
1299
|
+
|
|
1134
1300
|
case "panels.resetOffset":
|
|
1135
1301
|
return {
|
|
1136
1302
|
...state,
|
|
@@ -1288,6 +1454,26 @@ export function toolcraftReducer(
|
|
|
1288
1454
|
});
|
|
1289
1455
|
}
|
|
1290
1456
|
|
|
1457
|
+
case "media.transform": {
|
|
1458
|
+
const targetMediaAsset = state.mediaAssets.find((asset) => asset.id === command.mediaId);
|
|
1459
|
+
|
|
1460
|
+
if (!targetMediaAsset || (targetMediaAsset.assetKind ?? "image") !== "image") {
|
|
1461
|
+
return state;
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
const mediaAssets = state.mediaAssets.map((asset) =>
|
|
1465
|
+
asset.id === targetMediaAsset.id
|
|
1466
|
+
? getTransformedMediaAsset(asset, command.operation)
|
|
1467
|
+
: asset,
|
|
1468
|
+
);
|
|
1469
|
+
|
|
1470
|
+
return commitStatePatch(state, {
|
|
1471
|
+
after: { mediaAssets },
|
|
1472
|
+
before: { mediaAssets: state.mediaAssets },
|
|
1473
|
+
label: "Transform media",
|
|
1474
|
+
});
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1291
1477
|
case "timeline.setCurrentTime": {
|
|
1292
1478
|
return {
|
|
1293
1479
|
...state,
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
getToolcraftTimelineLoopProgress,
|
|
5
|
+
getToolcraftTimelineLoopTime,
|
|
6
|
+
} from "./timeline-loop";
|
|
7
|
+
|
|
8
|
+
describe("timeline loop helpers", () => {
|
|
9
|
+
it("wraps timeline time into a forward-only seamless loop", () => {
|
|
10
|
+
expect(
|
|
11
|
+
getToolcraftTimelineLoopTime({
|
|
12
|
+
currentTimeSeconds: 0,
|
|
13
|
+
durationSeconds: 8,
|
|
14
|
+
}),
|
|
15
|
+
).toBe(0);
|
|
16
|
+
expect(
|
|
17
|
+
getToolcraftTimelineLoopTime({
|
|
18
|
+
currentTimeSeconds: 7.5,
|
|
19
|
+
durationSeconds: 8,
|
|
20
|
+
}),
|
|
21
|
+
).toBe(7.5);
|
|
22
|
+
expect(
|
|
23
|
+
getToolcraftTimelineLoopTime({
|
|
24
|
+
currentTimeSeconds: 8,
|
|
25
|
+
durationSeconds: 8,
|
|
26
|
+
}),
|
|
27
|
+
).toBe(0);
|
|
28
|
+
expect(
|
|
29
|
+
getToolcraftTimelineLoopTime({
|
|
30
|
+
currentTimeSeconds: 9.25,
|
|
31
|
+
durationSeconds: 8,
|
|
32
|
+
}),
|
|
33
|
+
).toBe(1.25);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("maps one full product cycle to the current timeline duration", () => {
|
|
37
|
+
expect(
|
|
38
|
+
getToolcraftTimelineLoopProgress({
|
|
39
|
+
currentTimeSeconds: 2,
|
|
40
|
+
durationSeconds: 4,
|
|
41
|
+
}),
|
|
42
|
+
).toBe(0.5);
|
|
43
|
+
expect(
|
|
44
|
+
getToolcraftTimelineLoopProgress({
|
|
45
|
+
currentTimeSeconds: 2,
|
|
46
|
+
durationSeconds: 8,
|
|
47
|
+
}),
|
|
48
|
+
).toBe(0.25);
|
|
49
|
+
expect(
|
|
50
|
+
getToolcraftTimelineLoopProgress({
|
|
51
|
+
currentTimeSeconds: 8,
|
|
52
|
+
durationSeconds: 8,
|
|
53
|
+
}),
|
|
54
|
+
).toBe(0);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("handles invalid timeline values as the first loop frame", () => {
|
|
58
|
+
expect(
|
|
59
|
+
getToolcraftTimelineLoopProgress({
|
|
60
|
+
currentTimeSeconds: Number.NaN,
|
|
61
|
+
durationSeconds: 8,
|
|
62
|
+
}),
|
|
63
|
+
).toBe(0);
|
|
64
|
+
expect(
|
|
65
|
+
getToolcraftTimelineLoopProgress({
|
|
66
|
+
currentTimeSeconds: 4,
|
|
67
|
+
durationSeconds: 0,
|
|
68
|
+
}),
|
|
69
|
+
).toBe(0);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ToolcraftTimelineState } from "./types";
|
|
2
|
+
|
|
3
|
+
export type ToolcraftTimelineLoopOptions = Pick<
|
|
4
|
+
ToolcraftTimelineState,
|
|
5
|
+
"currentTimeSeconds" | "durationSeconds"
|
|
6
|
+
>;
|
|
7
|
+
|
|
8
|
+
function getPositiveModulo(value: number, modulo: number): number {
|
|
9
|
+
return ((value % modulo) + modulo) % modulo;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function getToolcraftTimelineLoopTime({
|
|
13
|
+
currentTimeSeconds,
|
|
14
|
+
durationSeconds,
|
|
15
|
+
}: ToolcraftTimelineLoopOptions): number {
|
|
16
|
+
if (
|
|
17
|
+
!Number.isFinite(currentTimeSeconds) ||
|
|
18
|
+
!Number.isFinite(durationSeconds) ||
|
|
19
|
+
durationSeconds <= 0
|
|
20
|
+
) {
|
|
21
|
+
return 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return getPositiveModulo(currentTimeSeconds, durationSeconds);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getToolcraftTimelineLoopProgress(
|
|
28
|
+
options: ToolcraftTimelineLoopOptions,
|
|
29
|
+
): number {
|
|
30
|
+
if (!Number.isFinite(options.durationSeconds) || options.durationSeconds <= 0) {
|
|
31
|
+
return 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return getToolcraftTimelineLoopTime(options) / options.durationSeconds;
|
|
35
|
+
}
|
|
@@ -37,6 +37,11 @@ export type ToolcraftCommand =
|
|
|
37
37
|
panelId: ToolcraftPanelId;
|
|
38
38
|
type: "panels.setOffset";
|
|
39
39
|
}
|
|
40
|
+
| {
|
|
41
|
+
hidden: boolean;
|
|
42
|
+
panelId: ToolcraftPanelId;
|
|
43
|
+
type: "panels.setHidden";
|
|
44
|
+
}
|
|
40
45
|
| { panelId: ToolcraftPanelId; type: "panels.resetOffset" }
|
|
41
46
|
| {
|
|
42
47
|
asset: Omit<ToolcraftMediaAsset, "id" | "layerId"> & {
|
|
@@ -49,6 +54,11 @@ export type ToolcraftCommand =
|
|
|
49
54
|
}
|
|
50
55
|
| { mediaId: string; type: "media.delete" }
|
|
51
56
|
| { mediaIds: string[]; type: "media.reorder" }
|
|
57
|
+
| {
|
|
58
|
+
mediaId: string;
|
|
59
|
+
operation: ToolcraftMediaTransformOperation;
|
|
60
|
+
type: "media.transform";
|
|
61
|
+
}
|
|
52
62
|
| { currentTimeSeconds: number; type: "timeline.setCurrentTime" }
|
|
53
63
|
| { durationSeconds: number; type: "timeline.setDuration" }
|
|
54
64
|
| { expanded: boolean; type: "timeline.setExpanded" }
|
|
@@ -106,10 +116,12 @@ export const toolcraftRuntimeCommandTypes = [
|
|
|
106
116
|
"canvas.zoomReset",
|
|
107
117
|
"canvas.setViewport",
|
|
108
118
|
"panels.setOffset",
|
|
119
|
+
"panels.setHidden",
|
|
109
120
|
"panels.resetOffset",
|
|
110
121
|
"media.import",
|
|
111
122
|
"media.delete",
|
|
112
123
|
"media.reorder",
|
|
124
|
+
"media.transform",
|
|
113
125
|
"timeline.setCurrentTime",
|
|
114
126
|
"timeline.setDuration",
|
|
115
127
|
"timeline.setExpanded",
|
|
@@ -171,8 +183,21 @@ export type ToolcraftMediaAsset = {
|
|
|
171
183
|
position: ToolcraftPoint;
|
|
172
184
|
size?: ToolcraftCanvasSize;
|
|
173
185
|
sourceTarget?: string;
|
|
186
|
+
transform?: ToolcraftMediaTransform;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
export type ToolcraftMediaTransform = {
|
|
190
|
+
flipHorizontal?: boolean;
|
|
191
|
+
flipVertical?: boolean;
|
|
192
|
+
rotationDeg?: 0 | 90 | 180 | 270;
|
|
174
193
|
};
|
|
175
194
|
|
|
195
|
+
export type ToolcraftMediaTransformOperation =
|
|
196
|
+
| "flip-horizontal"
|
|
197
|
+
| "flip-vertical"
|
|
198
|
+
| "rotate-left"
|
|
199
|
+
| "rotate-right";
|
|
200
|
+
|
|
176
201
|
export type ToolcraftHistoryPatch = {
|
|
177
202
|
after: Record<string, unknown>;
|
|
178
203
|
before: Record<string, unknown>;
|
|
@@ -223,6 +248,8 @@ export type ToolcraftPanelId = "controls" | "layers" | "timeline" | "toolbar";
|
|
|
223
248
|
|
|
224
249
|
export type ToolcraftPanelState = {
|
|
225
250
|
collapsed?: boolean;
|
|
251
|
+
extended?: boolean;
|
|
252
|
+
hidden?: boolean;
|
|
226
253
|
offset: { x: number; y: number };
|
|
227
254
|
};
|
|
228
255
|
|