@pixel-point/toolcraft 0.0.17 → 0.0.18
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/package.json +1 -1
- package/src/generate-test-doc-assertions.mjs +58 -68
- package/src/generate-test-doc-assertions.test.mjs +43 -0
- package/src/generate-test-doc-policy-assertions.mjs +92 -0
- package/src/generate-test-file-assertions.mjs +26 -75
- package/src/generate-test-product-iteration.test.mjs +17 -0
- package/src/generate-test-runtime-boundary-assertions.mjs +158 -0
- package/src/generate-test-standalone-assertions.mjs +10 -0
- package/templates/runtime/contracts/component-contracts.choices.test.ts +47 -3
- package/templates/runtime/contracts/component-contracts.choices.ts +48 -3
- package/templates/runtime/contracts/component-contracts.inputs.test.ts +6 -6
- package/templates/runtime/contracts/component-contracts.inputs.ts +6 -6
- package/templates/runtime/contracts/component-contracts.media-custom.test.ts +8 -2
- package/templates/runtime/contracts/component-contracts.media-custom.ts +8 -2
- package/templates/runtime/contracts/component-contracts.runtime-acceptance.test.ts +4 -4
- package/templates/runtime/contracts/component-contracts.runtime-labels.test.ts +7 -1
- package/templates/runtime/contracts/component-contracts.runtime-panels.test.ts +3 -0
- package/templates/runtime/contracts/component-contracts.runtime-shell.test.ts +3 -0
- package/templates/runtime/contracts/component-contracts.runtime.ts +12 -8
- package/templates/runtime/contracts/component-contracts.test.ts +2 -0
- package/templates/runtime/contracts/decision-contracts.test.ts +18 -0
- package/templates/runtime/contracts/decision-contracts.ts +10 -10
- package/templates/runtime/export/export-background.ts +0 -19
- package/templates/runtime/export/export.test.ts +45 -42
- package/templates/runtime/export/index.ts +44 -16
- package/templates/runtime/index.ts +2 -0
- package/templates/runtime/model-import/model-import-limits.ts +15 -4
- package/templates/runtime/react/app-shell/runtime-public-api.test.tsx +11 -1
- package/templates/runtime/react/app-shell/toolcraft-app.scene-export.test.tsx +29 -0
- package/templates/runtime/react/app-shell/toolcraft-app.tsx +18 -8
- package/templates/runtime/react/canvas/canvas-shell-rendering.test.tsx +76 -0
- package/templates/runtime/react/canvas/canvas-shell-test-utils.tsx +13 -1
- package/templates/runtime/react/canvas/canvas-shell.tsx +16 -1
- package/templates/runtime/react/canvas/product-scene-surface.test.tsx +195 -0
- package/templates/runtime/react/canvas/product-scene-surface.tsx +155 -0
- package/templates/runtime/react/canvas/runtime-scene-export.ts +1 -1
- package/templates/runtime/react/controls-panel/__tests__/control-conditions.test.ts +61 -0
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.collection-actions.test.tsx +89 -0
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.control-visibility.test.tsx +40 -19
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.scene-export-actions.test.tsx +1 -1
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.source-collection.test.tsx +62 -0
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.source-color-bank.test.tsx +67 -0
- package/templates/runtime/react/controls-panel/actions/controls-panel-actions.ts +2 -4
- package/templates/runtime/react/controls-panel/actions/export-action-runner.ts +10 -4
- package/templates/runtime/react/controls-panel/conditions/control-conditions.ts +32 -119
- package/templates/runtime/react/controls-panel/layout/controls-panel-control-group.tsx +5 -2
- package/templates/runtime/react/controls-panel/layout/controls-panel-layout.tsx +10 -8
- package/templates/runtime/react/controls-panel/layout/controls-panel-section.tsx +3 -3
- package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-item-fields.test.tsx +63 -0
- package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-item-fields.tsx +138 -0
- package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-items.tsx +104 -0
- package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-renderer.tsx +115 -236
- package/templates/runtime/react/controls-panel/renderers/controls-panel-file-drop-item-controls.test.tsx +95 -0
- package/templates/runtime/react/controls-panel/renderers/controls-panel-file-drop-item-controls.tsx +82 -0
- package/templates/runtime/react/controls-panel/renderers/controls-panel-media-renderer.test.tsx +73 -1
- package/templates/runtime/react/controls-panel/renderers/controls-panel-media-renderer.tsx +50 -1
- package/templates/runtime/react/controls-panel/renderers/controls-panel-renderer-registry.test.ts +1 -1
- package/templates/runtime/react/controls-panel/renderers/controls-panel-renderer-registry.ts +2 -1
- package/templates/runtime/react/controls-panel/values/controls-panel-collection-values.ts +38 -6
- package/templates/runtime/react/controls-panel/values/controls-panel-value-labels.ts +1 -0
- package/templates/runtime/react/index.ts +4 -0
- package/templates/runtime/react/model-rendering/model-export.ts +1 -1
- package/templates/runtime/react/orientation-gizmo/orientation-gizmo-selection.ts +5 -5
- package/templates/runtime/react/orientation-gizmo/use-toolcraft-orientation-control-selection.ts +2 -2
- package/templates/runtime/scene/scene-bounds.test.ts +23 -0
- package/templates/runtime/scene/scene-bounds.ts +31 -17
- package/templates/runtime/schema/app-capabilities.test.ts +4 -0
- package/templates/runtime/schema/collection-item-controls.ts +29 -0
- package/templates/runtime/schema/control-applicability.test.ts +127 -0
- package/templates/runtime/schema/control-applicability.ts +433 -0
- package/templates/runtime/schema/control-schema-normalization.ts +177 -11
- package/templates/runtime/schema/controls-panel-actions.ts +2 -2
- package/templates/runtime/schema/controls-panel-section-layout.ts +14 -9
- package/templates/runtime/schema/controls-panel-section-roundtrip.test.ts +59 -0
- package/templates/runtime/schema/define-toolcraft.collection-actions-item-controls.test.ts +127 -0
- package/templates/runtime/schema/define-toolcraft.control-sections.test.ts +10 -4
- package/templates/runtime/schema/define-toolcraft.file-drop-item-controls.test.ts +98 -0
- package/templates/runtime/schema/define-toolcraft.file-drop-normalization.test.ts +27 -0
- package/templates/runtime/schema/define-toolcraft.segmented-control-fit.test.ts +20 -2
- package/templates/runtime/schema/define-toolcraft.setup-canvas.test.ts +16 -3
- package/templates/runtime/schema/define-toolcraft.source-collection.test.ts +79 -0
- package/templates/runtime/schema/runtime-setup-section.ts +24 -5
- package/templates/runtime/schema/types.ts +68 -13
- package/templates/starter/AGENTS.md +12 -10
- package/templates/starter/docs/toolcraft/acceptance-testing.md +17 -31
- package/templates/starter/docs/toolcraft/agent-worklog.md +15 -0
- package/templates/starter/docs/toolcraft/assembly-workflow.md +10 -3
- package/templates/starter/docs/toolcraft/component-rules.md +8 -6
- package/templates/starter/docs/toolcraft/core/control-selection.md +9 -3
- package/templates/starter/docs/toolcraft/core/layout.md +7 -7
- package/templates/starter/docs/toolcraft/core/media-upload.md +2 -0
- package/templates/starter/docs/toolcraft/core/runtime-boundary.md +13 -2
- package/templates/starter/docs/toolcraft/core/setup-export.md +33 -7
- package/templates/starter/docs/toolcraft/custom-controls.md +6 -4
- package/templates/starter/docs/toolcraft/decision-contract.md +3 -1
- package/templates/starter/docs/toolcraft/renderer-technique.md +4 -0
- package/templates/starter/docs/toolcraft/schema-reference.md +21 -19
- package/templates/starter/e2e/app-browser-control-applicability-requirements.spec.ts +163 -0
- package/templates/starter/e2e/app-browser-infinity-canvas-evidence.spec.ts +7 -2
- package/templates/starter/e2e/app-browser-orientation-evidence.spec.ts +174 -21
- package/templates/starter/e2e/app-browser-runtime-requirements.spec.ts +1 -33
- package/templates/starter/e2e/app-browser-semantic-evidence.spec.ts +61 -15
- package/templates/starter/e2e/{browser-conditional-output-evidence-helpers.ts → browser-background-output-evidence.ts} +0 -68
- package/templates/starter/e2e/browser-control-applicability-evidence.ts +114 -0
- package/templates/starter/e2e/browser-infinity-canvas-evidence.ts +20 -11
- package/templates/starter/e2e/browser-orientation-gizmo-evidence-helpers.ts +4 -0
- package/templates/starter/e2e/browser-orientation-gizmo-live-preconditions.ts +86 -0
- package/templates/starter/e2e/browser-runtime-applicability-requirements.ts +84 -0
- package/templates/starter/e2e/browser-runtime-evidence-reporter.ts +9 -2
- package/templates/starter/e2e/browser-runtime-evidence-requirements.ts +29 -26
- package/templates/starter/e2e/browser-semantic-evidence-test-helpers.ts +25 -0
- package/templates/starter/scripts/toolcraft-contract-manifest.json +1 -0
- package/templates/starter/scripts/toolcraft-product-boundary-ast.mjs +85 -115
- package/templates/starter/scripts/toolcraft-product-boundary-module-policy.mjs +101 -0
- package/templates/starter/scripts/toolcraft-product-boundary.test.mjs +0 -72
- package/templates/starter/scripts/toolcraft-product-control-bindings.mjs +182 -0
- package/templates/starter/scripts/toolcraft-product-control-boundary.mjs +146 -0
- package/templates/starter/scripts/toolcraft-product-control-boundary.test.mjs +186 -0
- package/templates/starter/scripts/toolcraft-product-export-boundary.mjs +6 -17
- package/templates/starter/scripts/toolcraft-product-export-boundary.test.mjs +196 -0
- package/templates/starter/scripts/toolcraft-typescript-source-evidence-structure.test.mjs +20 -0
- package/templates/starter/scripts/toolcraft-typescript-source-evidence.mjs +1 -0
- package/templates/starter/scripts/toolcraft-unavailable-resource-evidence-facade.test.mjs +2 -0
- package/templates/starter/src/app/acceptance/artifact-export-intent.ts +130 -0
- package/templates/starter/src/app/acceptance/conditions.ts +1 -1
- package/templates/starter/src/app/acceptance/control-acceptance-context.ts +2 -2
- package/templates/starter/src/app/acceptance/control-acceptance-coverage.ts +6 -20
- package/templates/starter/src/app/acceptance/control-acceptance-kind-rules.ts +24 -5
- package/templates/starter/src/app/acceptance/control-acceptance-policy.ts +2 -13
- package/templates/starter/src/app/acceptance/control-applicability-cases.test.ts +321 -0
- package/templates/starter/src/app/acceptance/control-applicability-cases.ts +290 -0
- package/templates/starter/src/app/acceptance/control-applicability.ts +332 -0
- package/templates/starter/src/app/acceptance/control-layout-dependency-rules.ts +20 -9
- package/templates/starter/src/app/acceptance/control-layout-entity-rules.ts +4 -6
- package/templates/starter/src/app/acceptance/control-layout-model.ts +4 -4
- package/templates/starter/src/app/acceptance/control-layout-section-rules.ts +3 -10
- package/templates/starter/src/app/acceptance/control-parts.ts +8 -0
- package/templates/starter/src/app/acceptance/control-section-entity-cohesion.test.ts +215 -0
- package/templates/starter/src/app/acceptance/control-section-entity-cohesion.ts +132 -0
- package/templates/starter/src/app/acceptance/control-section-inventory.test.ts +87 -0
- package/templates/starter/src/app/acceptance/control-section-inventory.ts +74 -44
- package/templates/starter/src/app/acceptance/custom-controls.ts +11 -3
- package/templates/starter/src/app/acceptance/inline-layout.ts +64 -9
- package/templates/starter/src/app/acceptance/media-upload.ts +5 -1
- package/templates/starter/src/app/acceptance/orientation-gizmo.ts +18 -11
- package/templates/starter/src/app/acceptance/output-background-rules.ts +2 -2
- package/templates/starter/src/app/acceptance/output-export-layout-rules.ts +18 -10
- package/templates/starter/src/app/acceptance/output-export-model.ts +54 -11
- package/templates/starter/src/app/acceptance/output-export.ts +22 -10
- package/templates/starter/src/app/acceptance/output-video-export-rules.ts +99 -0
- package/templates/starter/src/app/acceptance/types.ts +23 -8
- package/templates/starter/src/app/acceptance/validate-coverage.ts +19 -2
- package/templates/starter/src/app/starter-acceptance.artifact-export-intent.test.ts +239 -0
- package/templates/starter/src/app/starter-acceptance.background-export.test.ts +33 -0
- package/templates/starter/src/app/starter-acceptance.canvas-sizing.test.ts +16 -1
- package/templates/starter/src/app/starter-acceptance.compound-part-coverage.test.ts +65 -0
- package/templates/starter/src/app/starter-acceptance.contract-fixtures.test.ts +41 -0
- package/templates/starter/src/app/starter-acceptance.contract-fixtures.ts +112 -16
- package/templates/starter/src/app/starter-acceptance.control-applicability.test.ts +224 -0
- package/templates/starter/src/app/starter-acceptance.control-order.test.ts +58 -3
- package/templates/starter/src/app/starter-acceptance.control-state.test.ts +0 -111
- package/templates/starter/src/app/starter-acceptance.custom-control-collections.test.ts +3 -1
- package/templates/starter/src/app/starter-acceptance.custom-control-valid.test.ts +1 -1
- package/templates/starter/src/app/starter-acceptance.export-test-utils.ts +22 -0
- package/templates/starter/src/app/{starter-acceptance.image-video-export-settings.test.ts → starter-acceptance.image-export-settings.test.ts} +37 -9
- package/templates/starter/src/app/starter-acceptance.interaction-ownership.test.ts +12 -0
- package/templates/starter/src/app/starter-acceptance.media-upload.fixtures.ts +11 -1
- package/templates/starter/src/app/starter-acceptance.media-upload.test.ts +28 -0
- package/templates/starter/src/app/starter-acceptance.output-export-intent.test.ts +384 -0
- package/templates/starter/src/app/starter-acceptance.output-export-rules.test.ts +4 -0
- package/templates/starter/src/app/starter-acceptance.product-output-export.test.ts +112 -105
- package/templates/starter/src/app/starter-acceptance.product-readiness.test.ts +12 -0
- package/templates/starter/src/app/starter-acceptance.section-cohesion.test.ts +1 -1
- package/templates/starter/src/app/starter-acceptance.section-dependencies.test.ts +35 -24
- package/templates/starter/src/app/starter-acceptance.section-size.test.ts +72 -0
- package/templates/starter/src/app/starter-acceptance.ts +11 -2
- package/templates/starter/src/app/starter-acceptance.video-export-settings.test.ts +336 -0
- package/templates/starter/src/app/starter-acceptance.view-interaction.test.ts +8 -0
- package/templates/starter/src/app/test-evidence/browser-runtime-contract.ts +2 -2
- package/templates/starter/src/routes/index.tsx +1 -0
- package/templates/ui/components/composites/tabs.tsx +2 -2
- package/templates/ui/components/controls/collection-actions/collection-item-groups.test.tsx +38 -0
- package/templates/ui/components/controls/collection-actions/collection-item-groups.tsx +36 -0
- package/templates/ui/components/controls/collection-actions/index.ts +2 -0
- package/templates/ui/components/controls/file-drop/file-drop-collection-control.tsx +137 -0
- package/templates/ui/components/controls/file-drop/file-drop-control.test.tsx +189 -0
- package/templates/ui/components/controls/file-drop/file-drop-control.tsx +81 -88
- package/templates/ui/components/controls/file-drop/file-drop-presentation.tsx +131 -9
- package/templates/ui/components/controls/file-drop/file-drop-types.ts +36 -22
- package/templates/ui/components/controls/file-drop/index.ts +1 -0
- package/templates/ui/components/controls/index.ts +3 -0
- package/templates/ui/components/controls/range-slider/range-slider-control.tsx +1 -1
- package/templates/ui/components/controls/range-slider/range-slider-value.test.ts +14 -0
- package/templates/ui/components/controls/range-slider/range-slider-value.ts +3 -2
- package/templates/starter/src/app/acceptance/condition-exclusivity.test.ts +0 -60
- package/templates/starter/src/app/acceptance/condition-exclusivity.ts +0 -221
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
|
|
4
|
+
import { ControlsPanelFileDropItemControls } from "./controls-panel-file-drop-item-controls";
|
|
5
|
+
|
|
6
|
+
describe("controls-panel FileDrop item controls", () => {
|
|
7
|
+
it("renders one file's built-in settings and commits a mediaId-keyed record", () => {
|
|
8
|
+
const setControlValue = vi.fn();
|
|
9
|
+
|
|
10
|
+
render(
|
|
11
|
+
<ControlsPanelFileDropItemControls
|
|
12
|
+
control={{
|
|
13
|
+
assetKind: "file",
|
|
14
|
+
defaultValue: [],
|
|
15
|
+
itemControls: {
|
|
16
|
+
depth: {
|
|
17
|
+
defaultValue: 0.2,
|
|
18
|
+
label: "Strength",
|
|
19
|
+
max: 1,
|
|
20
|
+
min: 0,
|
|
21
|
+
step: 0.1,
|
|
22
|
+
type: "slider",
|
|
23
|
+
},
|
|
24
|
+
invert: {
|
|
25
|
+
defaultValue: false,
|
|
26
|
+
label: "Invert",
|
|
27
|
+
type: "switch",
|
|
28
|
+
},
|
|
29
|
+
levels: {
|
|
30
|
+
defaultValue: [0, 1],
|
|
31
|
+
label: "Levels",
|
|
32
|
+
max: 1,
|
|
33
|
+
min: 0,
|
|
34
|
+
step: 0.01,
|
|
35
|
+
type: "rangeSlider",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
multiple: true,
|
|
39
|
+
target: "media.bumpMaps",
|
|
40
|
+
type: "fileDrop",
|
|
41
|
+
variant: "collection-actions",
|
|
42
|
+
}}
|
|
43
|
+
index={1}
|
|
44
|
+
mediaId="bump-two"
|
|
45
|
+
name="Bump maps"
|
|
46
|
+
setControlValue={setControlValue}
|
|
47
|
+
value={[
|
|
48
|
+
{
|
|
49
|
+
depth: 0.4,
|
|
50
|
+
invert: true,
|
|
51
|
+
levels: [0.1, 0.8],
|
|
52
|
+
mediaId: "bump-one",
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
depth: 0.6,
|
|
56
|
+
invert: false,
|
|
57
|
+
levels: [0.2, 0.9],
|
|
58
|
+
mediaId: "bump-two",
|
|
59
|
+
},
|
|
60
|
+
]}
|
|
61
|
+
/>,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
expect(screen.getByText("Strength")).toBeTruthy();
|
|
65
|
+
expect(screen.getByText("Invert")).toBeTruthy();
|
|
66
|
+
expect(screen.getByText("Levels")).toBeTruthy();
|
|
67
|
+
|
|
68
|
+
fireEvent.click(
|
|
69
|
+
screen.getByRole("button", { name: "Edit Strength value" }),
|
|
70
|
+
);
|
|
71
|
+
const editor = screen.getByRole("textbox", { name: "Strength value" });
|
|
72
|
+
editor.textContent = "0.7";
|
|
73
|
+
fireEvent.keyDown(editor, { key: "Enter" });
|
|
74
|
+
|
|
75
|
+
expect(setControlValue).toHaveBeenCalledWith(
|
|
76
|
+
"media.bumpMaps",
|
|
77
|
+
[
|
|
78
|
+
{
|
|
79
|
+
depth: 0.4,
|
|
80
|
+
invert: true,
|
|
81
|
+
levels: [0.1, 0.8],
|
|
82
|
+
mediaId: "bump-one",
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
depth: 0.7,
|
|
86
|
+
invert: false,
|
|
87
|
+
levels: [0.2, 0.9],
|
|
88
|
+
mediaId: "bump-two",
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
"Bump maps 2 Strength",
|
|
92
|
+
undefined,
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
});
|
package/templates/runtime/react/controls-panel/renderers/controls-panel-file-drop-item-controls.tsx
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { ControlChangeMeta } from "@repo/ui";
|
|
2
|
+
|
|
3
|
+
import type { ToolcraftControlSchema } from "../../../schema/types";
|
|
4
|
+
import { ControlsPanelCollectionItemFields } from "./controls-panel-collection-item-fields";
|
|
5
|
+
|
|
6
|
+
type FileDropItemValue = Readonly<Record<string, unknown>> & {
|
|
7
|
+
mediaId: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type ControlsPanelFileDropItemControlsProps = {
|
|
11
|
+
control: ToolcraftControlSchema;
|
|
12
|
+
index: number;
|
|
13
|
+
mediaId: string;
|
|
14
|
+
name: string;
|
|
15
|
+
setControlValue: (
|
|
16
|
+
target: string,
|
|
17
|
+
value: unknown,
|
|
18
|
+
label?: string,
|
|
19
|
+
meta?: ControlChangeMeta,
|
|
20
|
+
) => void;
|
|
21
|
+
value: unknown;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function asFileDropItemValues(value: unknown): readonly FileDropItemValue[] {
|
|
25
|
+
if (!Array.isArray(value)) {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return value.filter(
|
|
30
|
+
(item): item is FileDropItemValue =>
|
|
31
|
+
typeof item === "object" &&
|
|
32
|
+
item !== null &&
|
|
33
|
+
typeof (item as { mediaId?: unknown }).mediaId === "string",
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function ControlsPanelFileDropItemControls({
|
|
38
|
+
control,
|
|
39
|
+
index,
|
|
40
|
+
mediaId,
|
|
41
|
+
name,
|
|
42
|
+
setControlValue,
|
|
43
|
+
value,
|
|
44
|
+
}: ControlsPanelFileDropItemControlsProps): React.JSX.Element | null {
|
|
45
|
+
const itemControls = control.itemControls;
|
|
46
|
+
|
|
47
|
+
if (!itemControls || Object.keys(itemControls).length === 0) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const itemValues = asFileDropItemValues(value);
|
|
52
|
+
const storedItemValue = itemValues.find((item) => item.mediaId === mediaId);
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<ControlsPanelCollectionItemFields
|
|
56
|
+
controls={itemControls}
|
|
57
|
+
id={`${control.target}:${mediaId}`}
|
|
58
|
+
onChange={(nextValue, fieldName, meta) => {
|
|
59
|
+
const nextItemValue: FileDropItemValue = { ...nextValue, mediaId };
|
|
60
|
+
const existingIndex = itemValues.findIndex(
|
|
61
|
+
(item) => item.mediaId === mediaId,
|
|
62
|
+
);
|
|
63
|
+
const nextItems = [...itemValues];
|
|
64
|
+
|
|
65
|
+
if (existingIndex >= 0) {
|
|
66
|
+
nextItems[existingIndex] = nextItemValue;
|
|
67
|
+
} else {
|
|
68
|
+
nextItems.push(nextItemValue);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
setControlValue(
|
|
72
|
+
control.target,
|
|
73
|
+
nextItems,
|
|
74
|
+
`${name} ${index + 1} ${fieldName}`,
|
|
75
|
+
meta,
|
|
76
|
+
);
|
|
77
|
+
}}
|
|
78
|
+
target={control.target}
|
|
79
|
+
value={storedItemValue}
|
|
80
|
+
/>
|
|
81
|
+
);
|
|
82
|
+
}
|
package/templates/runtime/react/controls-panel/renderers/controls-panel-media-renderer.test.tsx
CHANGED
|
@@ -52,6 +52,7 @@ describe("controls-panel media renderer", () => {
|
|
|
52
52
|
dispatchCommand,
|
|
53
53
|
id: "source-model",
|
|
54
54
|
mediaAssets: [],
|
|
55
|
+
name: "Model",
|
|
55
56
|
sourceAssetCoordinator,
|
|
56
57
|
});
|
|
57
58
|
|
|
@@ -102,6 +103,7 @@ describe("controls-panel media renderer", () => {
|
|
|
102
103
|
dispatchCommand: vi.fn(),
|
|
103
104
|
id: "source-files",
|
|
104
105
|
mediaAssets: [],
|
|
106
|
+
name: "Source files",
|
|
105
107
|
sourceAssetCoordinator,
|
|
106
108
|
})}
|
|
107
109
|
</>,
|
|
@@ -113,7 +115,75 @@ describe("controls-panel media renderer", () => {
|
|
|
113
115
|
undefined,
|
|
114
116
|
);
|
|
115
117
|
expect(legacyFileDrop.mock.calls[0]?.[0]).toEqual(
|
|
116
|
-
expect.objectContaining({
|
|
118
|
+
expect.objectContaining({
|
|
119
|
+
collectionActions: undefined,
|
|
120
|
+
onFolderSelect: undefined,
|
|
121
|
+
}),
|
|
122
|
+
);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("maps the collection-actions variant to compact FileDrop cardinality controls", () => {
|
|
126
|
+
const control = {
|
|
127
|
+
assetKind: "file" as const,
|
|
128
|
+
itemLabel: "bump map",
|
|
129
|
+
multiple: true,
|
|
130
|
+
target: "surface.bumpMaps",
|
|
131
|
+
type: "fileDrop" as const,
|
|
132
|
+
variant: "collection-actions",
|
|
133
|
+
};
|
|
134
|
+
const operation = { phase: "idle" as const, target: control.target };
|
|
135
|
+
const presentation = {
|
|
136
|
+
accept: "image/*",
|
|
137
|
+
allowsFileBatch: true,
|
|
138
|
+
allowsFolderSelection: false,
|
|
139
|
+
emptyDescription: "Drop bump maps",
|
|
140
|
+
emptyTitle: "Upload bump maps",
|
|
141
|
+
items: [{
|
|
142
|
+
assetKind: "file" as const,
|
|
143
|
+
fileName: "grain.png",
|
|
144
|
+
id: "bump-grain",
|
|
145
|
+
}],
|
|
146
|
+
presenter: "file-list" as const,
|
|
147
|
+
secondaryActions: [],
|
|
148
|
+
};
|
|
149
|
+
const sourceAssetCoordinator = {
|
|
150
|
+
cancelTarget: vi.fn(),
|
|
151
|
+
clearPresentationFeedback: vi.fn(),
|
|
152
|
+
dispose: vi.fn(async () => undefined),
|
|
153
|
+
getOperation: vi.fn(() => operation),
|
|
154
|
+
getPresentation: vi.fn(() => presentation),
|
|
155
|
+
hydrateBinaryMedia: vi.fn(async () => undefined),
|
|
156
|
+
hydrateModels: vi.fn(async () => undefined),
|
|
157
|
+
importBatch: vi.fn(),
|
|
158
|
+
repairModel: vi.fn(),
|
|
159
|
+
reportPresentationFeedback: vi.fn(),
|
|
160
|
+
subscribe: () => () => undefined,
|
|
161
|
+
} satisfies ToolcraftSourceAssetCoordinator;
|
|
162
|
+
|
|
163
|
+
render(
|
|
164
|
+
<>
|
|
165
|
+
{renderFileDropControl({
|
|
166
|
+
canvasSize: { height: 720, unit: "px", width: 1280 },
|
|
167
|
+
control,
|
|
168
|
+
dispatchCommand: vi.fn(),
|
|
169
|
+
id: "surface-bump-maps",
|
|
170
|
+
mediaAssets: [],
|
|
171
|
+
name: "Bump maps",
|
|
172
|
+
sourceAssetCoordinator,
|
|
173
|
+
})}
|
|
174
|
+
</>,
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
expect(legacyFileDrop.mock.calls[0]?.[0]).toEqual(
|
|
178
|
+
expect.objectContaining({
|
|
179
|
+
collectionActions: {
|
|
180
|
+
addLabel: "Add bump map",
|
|
181
|
+
canAdd: true,
|
|
182
|
+
itemLabel: "bump map",
|
|
183
|
+
name: "Bump maps",
|
|
184
|
+
removeLabel: "Remove bump map",
|
|
185
|
+
},
|
|
186
|
+
}),
|
|
117
187
|
);
|
|
118
188
|
});
|
|
119
189
|
|
|
@@ -157,6 +227,7 @@ describe("controls-panel media renderer", () => {
|
|
|
157
227
|
dispatchCommand: vi.fn(),
|
|
158
228
|
id: "source-model",
|
|
159
229
|
mediaAssets: [],
|
|
230
|
+
name: "Model",
|
|
160
231
|
sourceAssetCoordinator,
|
|
161
232
|
})}
|
|
162
233
|
</>,
|
|
@@ -226,6 +297,7 @@ describe("controls-panel media renderer", () => {
|
|
|
226
297
|
dispatchCommand: vi.fn(),
|
|
227
298
|
id: "source-model",
|
|
228
299
|
mediaAssets: [],
|
|
300
|
+
name: "Model",
|
|
229
301
|
sourceAssetCoordinator,
|
|
230
302
|
})}
|
|
231
303
|
</>,
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
FileDrop,
|
|
4
|
+
type ControlChangeMeta,
|
|
5
|
+
type FileDropPresentationItem,
|
|
6
|
+
} from "@repo/ui";
|
|
3
7
|
|
|
4
8
|
import type { ToolcraftCanvasSize, ToolcraftControlSchema } from "../../../schema/types";
|
|
5
9
|
import type { ToolcraftSourceAssetCoordinator } from "../../../source-assets/source-asset-coordinator";
|
|
@@ -11,6 +15,7 @@ import type {
|
|
|
11
15
|
} from "../../../state/types";
|
|
12
16
|
import { useToolcraftMediaPresentationUrls } from "../../app-shell/toolcraft-source-asset-context";
|
|
13
17
|
import { useToolcraftSelector } from "../../app-shell/use-toolcraft";
|
|
18
|
+
import { ControlsPanelFileDropItemControls } from "./controls-panel-file-drop-item-controls";
|
|
14
19
|
|
|
15
20
|
export type FileDropControlRenderArgs = {
|
|
16
21
|
canvasSize: ToolcraftCanvasSize;
|
|
@@ -19,7 +24,15 @@ export type FileDropControlRenderArgs = {
|
|
|
19
24
|
id: string;
|
|
20
25
|
layerSelectionEnabled?: boolean;
|
|
21
26
|
mediaAssets: readonly ToolcraftMediaAsset[];
|
|
27
|
+
name: string;
|
|
28
|
+
setControlValue?: (
|
|
29
|
+
target: string,
|
|
30
|
+
value: unknown,
|
|
31
|
+
label?: string,
|
|
32
|
+
meta?: ControlChangeMeta,
|
|
33
|
+
) => void;
|
|
22
34
|
sourceAssetCoordinator: ToolcraftSourceAssetCoordinator;
|
|
35
|
+
value?: unknown;
|
|
23
36
|
};
|
|
24
37
|
|
|
25
38
|
const IMAGE_TRANSFORM_OPERATIONS = new Set<ToolcraftMediaTransformOperation>([
|
|
@@ -43,8 +56,11 @@ function FileDropControlRenderer({
|
|
|
43
56
|
id,
|
|
44
57
|
layerSelectedImageAsset = null,
|
|
45
58
|
mediaAssets,
|
|
59
|
+
name,
|
|
60
|
+
setControlValue = () => undefined,
|
|
46
61
|
sourceAssetCoordinator,
|
|
47
62
|
suppressMediaCollection = false,
|
|
63
|
+
value,
|
|
48
64
|
}: Omit<
|
|
49
65
|
FileDropControlRenderArgs,
|
|
50
66
|
"canvasSize" | "layerSelectionEnabled"
|
|
@@ -112,6 +128,10 @@ function FileDropControlRenderer({
|
|
|
112
128
|
? { ...basePresentation, secondaryActions: [] }
|
|
113
129
|
: basePresentation
|
|
114
130
|
: null;
|
|
131
|
+
const usesCollectionActions = control.variant === "collection-actions";
|
|
132
|
+
const canAddCollectionItem =
|
|
133
|
+
typeof control.hardMaxItems !== "number" ||
|
|
134
|
+
(basePresentation?.items.length ?? 0) < control.hardMaxItems;
|
|
115
135
|
|
|
116
136
|
React.useEffect(() => {
|
|
117
137
|
if (selectedMediaId && !previewMediaIds.includes(selectedMediaId)) {
|
|
@@ -156,6 +176,35 @@ function FileDropControlRenderer({
|
|
|
156
176
|
|
|
157
177
|
return (
|
|
158
178
|
<FileDrop
|
|
179
|
+
collectionActions={
|
|
180
|
+
usesCollectionActions
|
|
181
|
+
? {
|
|
182
|
+
addLabel: control.addLabel ?? `Add ${control.itemLabel ?? "file"}`,
|
|
183
|
+
canAdd: canAddCollectionItem,
|
|
184
|
+
itemLabel: control.itemLabel ?? "file",
|
|
185
|
+
name,
|
|
186
|
+
removeLabel:
|
|
187
|
+
control.removeLabel ?? `Remove ${control.itemLabel ?? "file"}`,
|
|
188
|
+
...(control.itemControls
|
|
189
|
+
? {
|
|
190
|
+
renderItemContent: (
|
|
191
|
+
item: FileDropPresentationItem,
|
|
192
|
+
index: number,
|
|
193
|
+
) => (
|
|
194
|
+
<ControlsPanelFileDropItemControls
|
|
195
|
+
control={control}
|
|
196
|
+
index={index}
|
|
197
|
+
mediaId={item.id}
|
|
198
|
+
name={name}
|
|
199
|
+
setControlValue={setControlValue}
|
|
200
|
+
value={value}
|
|
201
|
+
/>
|
|
202
|
+
),
|
|
203
|
+
}
|
|
204
|
+
: {}),
|
|
205
|
+
}
|
|
206
|
+
: undefined
|
|
207
|
+
}
|
|
159
208
|
key={id}
|
|
160
209
|
onAction={
|
|
161
210
|
activeImageId
|
package/templates/runtime/react/controls-panel/renderers/controls-panel-renderer-registry.test.ts
CHANGED
|
@@ -30,7 +30,7 @@ describe("Toolcraft controls-panel renderer registry", () => {
|
|
|
30
30
|
"canvas-handle": [],
|
|
31
31
|
collection: ["value"],
|
|
32
32
|
compound: ["value"],
|
|
33
|
-
media: ["canvas.size", "mediaAssets"],
|
|
33
|
+
media: ["canvas.size", "mediaAssets", "value"],
|
|
34
34
|
settings: [],
|
|
35
35
|
} as const;
|
|
36
36
|
|
package/templates/runtime/react/controls-panel/renderers/controls-panel-renderer-registry.ts
CHANGED
|
@@ -23,7 +23,7 @@ export const TOOLCRAFT_CONTROL_RENDERER_STATE_DEPENDENCIES = {
|
|
|
23
23
|
"canvas-handle": [],
|
|
24
24
|
collection: ["value"],
|
|
25
25
|
compound: ["value"],
|
|
26
|
-
media: ["canvas.size", "mediaAssets"],
|
|
26
|
+
media: ["canvas.size", "mediaAssets", "value"],
|
|
27
27
|
settings: [],
|
|
28
28
|
} as const satisfies Record<
|
|
29
29
|
ToolcraftControlRendererKind,
|
|
@@ -38,6 +38,7 @@ export const TOOLCRAFT_CONTROL_RENDERER_REGISTRY = {
|
|
|
38
38
|
checkbox: "basic",
|
|
39
39
|
code: "basic",
|
|
40
40
|
collectionActions: "collection",
|
|
41
|
+
sourceCollection: "collection",
|
|
41
42
|
color: "compound",
|
|
42
43
|
colorOpacity: "compound",
|
|
43
44
|
curves: "compound",
|
|
@@ -1,8 +1,18 @@
|
|
|
1
|
+
import type { ToolcraftCollectionItemControlType } from "../../../schema/collection-item-controls";
|
|
1
2
|
import type { ToolcraftControlSchema } from "../../../schema/types";
|
|
2
3
|
import { asFontPickerValue } from "./controls-panel-font-values";
|
|
3
4
|
import { asNumber } from "./controls-panel-value-primitives";
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
function assertNeverCollectionItemControl(value: never): never {
|
|
7
|
+
throw new Error(
|
|
8
|
+
`Unsupported Toolcraft collection item control: ${String(value)}`,
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function asCollectionItems(
|
|
13
|
+
value: unknown,
|
|
14
|
+
fallback: unknown,
|
|
15
|
+
): unknown[] {
|
|
6
16
|
if (Array.isArray(value)) {
|
|
7
17
|
return [...value];
|
|
8
18
|
}
|
|
@@ -17,18 +27,25 @@ export function getCollectionMinItems(control: ToolcraftControlSchema): number {
|
|
|
17
27
|
export function getCollectionHardMaxItems(
|
|
18
28
|
control: ToolcraftControlSchema,
|
|
19
29
|
): number | null {
|
|
20
|
-
if (
|
|
30
|
+
if (
|
|
31
|
+
typeof control.hardMaxItems !== "number" ||
|
|
32
|
+
!Number.isFinite(control.hardMaxItems)
|
|
33
|
+
) {
|
|
21
34
|
return null;
|
|
22
35
|
}
|
|
23
36
|
|
|
24
37
|
return Math.max(0, Math.floor(control.hardMaxItems));
|
|
25
38
|
}
|
|
26
39
|
|
|
27
|
-
export function getCollectionItemType(
|
|
40
|
+
export function getCollectionItemType(
|
|
41
|
+
control: ToolcraftControlSchema,
|
|
42
|
+
): ToolcraftCollectionItemControlType {
|
|
28
43
|
return control.itemControl?.type ?? "color";
|
|
29
44
|
}
|
|
30
45
|
|
|
31
|
-
export function getCollectionItemBaseLabel(
|
|
46
|
+
export function getCollectionItemBaseLabel(
|
|
47
|
+
control: ToolcraftControlSchema,
|
|
48
|
+
): string {
|
|
32
49
|
if (control.itemLabel) {
|
|
33
50
|
return control.itemLabel;
|
|
34
51
|
}
|
|
@@ -54,6 +71,15 @@ export function getCollectionItemName(
|
|
|
54
71
|
export function getCollectionItemDefaultValue(
|
|
55
72
|
control: ToolcraftControlSchema,
|
|
56
73
|
): unknown {
|
|
74
|
+
if (control.itemControls) {
|
|
75
|
+
return Object.fromEntries(
|
|
76
|
+
Object.entries(control.itemControls).map(([id, itemControl]) => [
|
|
77
|
+
id,
|
|
78
|
+
itemControl.defaultValue,
|
|
79
|
+
]),
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
57
83
|
if ("itemDefaultValue" in control) {
|
|
58
84
|
return control.itemDefaultValue;
|
|
59
85
|
}
|
|
@@ -62,7 +88,9 @@ export function getCollectionItemDefaultValue(
|
|
|
62
88
|
return control.itemControl.defaultValue;
|
|
63
89
|
}
|
|
64
90
|
|
|
65
|
-
|
|
91
|
+
const itemType = getCollectionItemType(control);
|
|
92
|
+
|
|
93
|
+
switch (itemType) {
|
|
66
94
|
case "color":
|
|
67
95
|
return { hex: "#C1FF00" };
|
|
68
96
|
case "colorOpacity":
|
|
@@ -74,6 +102,8 @@ export function getCollectionItemDefaultValue(
|
|
|
74
102
|
return false;
|
|
75
103
|
case "rangeInput":
|
|
76
104
|
return { end: "100%", start: "0%" };
|
|
105
|
+
case "rangeSlider":
|
|
106
|
+
return [control.itemControl?.min ?? 0, control.itemControl?.max ?? 100];
|
|
77
107
|
case "select":
|
|
78
108
|
case "segmented":
|
|
79
109
|
return control.itemControl?.options?.[0]?.value ?? "";
|
|
@@ -81,7 +111,9 @@ export function getCollectionItemDefaultValue(
|
|
|
81
111
|
return control.itemControl?.min ?? 0;
|
|
82
112
|
case "text":
|
|
83
113
|
return "";
|
|
114
|
+
case "vector":
|
|
115
|
+
return { x: "0", y: "0" };
|
|
84
116
|
default:
|
|
85
|
-
return
|
|
117
|
+
return assertNeverCollectionItemControl(itemType);
|
|
86
118
|
}
|
|
87
119
|
}
|
|
@@ -38,6 +38,7 @@ export function formatControlValueLabel(
|
|
|
38
38
|
return `${colorOpacityValue.hex} ${colorOpacityValue.opacity}%`;
|
|
39
39
|
}
|
|
40
40
|
case "collectionActions":
|
|
41
|
+
case "sourceCollection":
|
|
41
42
|
return `${asCollectionItems(value, control.defaultValue).length} items`;
|
|
42
43
|
case "fontPicker":
|
|
43
44
|
return asFontPickerValue(value).fontId;
|
|
@@ -6,6 +6,10 @@ export { useToolcraftMediaPresentationUrls } from "./app-shell/toolcraft-media-p
|
|
|
6
6
|
export * from "./app-shell/use-toolcraft-pipeline";
|
|
7
7
|
export * from "./app-shell/use-toolcraft-pipeline-pass";
|
|
8
8
|
export * from "./canvas/canvas-shell";
|
|
9
|
+
export {
|
|
10
|
+
useToolcraftProductSceneFrame,
|
|
11
|
+
type ToolcraftProductSceneFrame,
|
|
12
|
+
} from "./canvas/product-scene-surface";
|
|
9
13
|
export * from "./controls-panel/control-renderers";
|
|
10
14
|
export * from "./controls-panel/controls-panel";
|
|
11
15
|
export * from "./layers/layers-panel";
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
resolveToolcraftExportFrame,
|
|
3
3
|
ToolcraftSceneExportError,
|
|
4
4
|
type ToolcraftExportFrame,
|
|
5
|
-
} from "../../export";
|
|
5
|
+
} from "../../export/export-frame";
|
|
6
6
|
import { getToolcraftCanvasFrame, type ToolcraftCanvasFrame } from "../../state/canvas-frame";
|
|
7
7
|
import type { ToolcraftState } from "../../state/types";
|
|
8
8
|
import { getSceneElementPresentationRect } from "../canvas/scene-element-presentation-rect";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
ResolvedToolcraftControlSchema,
|
|
3
|
+
ResolvedToolcraftControlSectionSchema,
|
|
4
4
|
} from "../../schema/types";
|
|
5
5
|
import type { ToolcraftState } from "../../state/types";
|
|
6
6
|
import {
|
|
@@ -9,13 +9,13 @@ import {
|
|
|
9
9
|
} from "../controls-panel/conditions/control-conditions";
|
|
10
10
|
|
|
11
11
|
export type ToolcraftOrientationControlEntry = {
|
|
12
|
-
control:
|
|
12
|
+
control: ResolvedToolcraftControlSchema;
|
|
13
13
|
id: string;
|
|
14
|
-
section:
|
|
14
|
+
section: ResolvedToolcraftControlSectionSchema;
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
export function getToolcraftOrientationControlEntries(
|
|
18
|
-
sections: readonly
|
|
18
|
+
sections: readonly ResolvedToolcraftControlSectionSchema[],
|
|
19
19
|
): ToolcraftOrientationControlEntry[] {
|
|
20
20
|
return sections.flatMap((section) =>
|
|
21
21
|
Object.entries(section.controls).flatMap(([id, control]) =>
|
package/templates/runtime/react/orientation-gizmo/use-toolcraft-orientation-control-selection.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
|
|
5
|
-
import type {
|
|
5
|
+
import type { ResolvedToolcraftControlSchema } from "../../schema/types";
|
|
6
6
|
import type { ToolcraftStoreDependency } from "../../state/toolcraft-external-store-dependencies";
|
|
7
7
|
import type { ToolcraftState } from "../../state/types";
|
|
8
8
|
import { useToolcraftDependencySelector } from "../app-shell/toolcraft-selectors";
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
} from "./orientation-gizmo-selection";
|
|
19
19
|
|
|
20
20
|
export type ToolcraftOrientationControlSelection = Readonly<{
|
|
21
|
-
control:
|
|
21
|
+
control: ResolvedToolcraftControlSchema | null;
|
|
22
22
|
id: string | null;
|
|
23
23
|
value: unknown;
|
|
24
24
|
}>;
|
|
@@ -6,6 +6,7 @@ import { createToolcraftState } from "../state/create-template-state";
|
|
|
6
6
|
import {
|
|
7
7
|
getToolcraftSceneElementRect,
|
|
8
8
|
outwardRoundToolcraftSceneRect,
|
|
9
|
+
resolveToolcraftProductSceneBounds,
|
|
9
10
|
resolveToolcraftSceneBounds,
|
|
10
11
|
unionToolcraftSceneRects,
|
|
11
12
|
} from "./scene-bounds";
|
|
@@ -17,6 +18,28 @@ function emptyState() {
|
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
describe("scene bounds", () => {
|
|
21
|
+
it("resolves one exact product frame before runtime contributors are added", () => {
|
|
22
|
+
expect(
|
|
23
|
+
resolveToolcraftProductSceneBounds([
|
|
24
|
+
{ height: 20, width: 40, x: -10, y: -5 },
|
|
25
|
+
{ height: 30, width: 10, x: 35, y: 15 },
|
|
26
|
+
]),
|
|
27
|
+
).toEqual({
|
|
28
|
+
bounds: { height: 50, width: 55, x: -10, y: -5 },
|
|
29
|
+
ok: true,
|
|
30
|
+
});
|
|
31
|
+
expect(resolveToolcraftProductSceneBounds([])).toMatchObject({
|
|
32
|
+
code: "empty-scene",
|
|
33
|
+
ok: false,
|
|
34
|
+
});
|
|
35
|
+
expect(
|
|
36
|
+
resolveToolcraftProductSceneBounds([
|
|
37
|
+
{ height: 20, width: 40, x: -10, y: -5 },
|
|
38
|
+
{ height: 30, width: Number.NaN, x: 35, y: 15 },
|
|
39
|
+
]),
|
|
40
|
+
).toMatchObject({ code: "scene-bounds-unavailable", ok: false });
|
|
41
|
+
});
|
|
42
|
+
|
|
20
43
|
it("projects center-anchored frames and unions negative coordinates", () => {
|
|
21
44
|
expect(
|
|
22
45
|
getToolcraftSceneElementRect({
|
|
@@ -48,6 +48,18 @@ export type ToolcraftSceneBoundsResult =
|
|
|
48
48
|
ok: false;
|
|
49
49
|
}>;
|
|
50
50
|
|
|
51
|
+
const emptySceneBoundsResult: ToolcraftSceneBoundsResult = Object.freeze({
|
|
52
|
+
code: "empty-scene",
|
|
53
|
+
message: "The scene has no visible exportable elements.",
|
|
54
|
+
ok: false,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const unavailableSceneBoundsResult: ToolcraftSceneBoundsResult = Object.freeze({
|
|
58
|
+
code: "scene-bounds-unavailable",
|
|
59
|
+
message: "Product scene bounds are unavailable or invalid.",
|
|
60
|
+
ok: false,
|
|
61
|
+
});
|
|
62
|
+
|
|
51
63
|
export function getToolcraftSceneElementRect(
|
|
52
64
|
element: ToolcraftSceneElementFrame,
|
|
53
65
|
): ToolcraftSceneRect {
|
|
@@ -158,32 +170,34 @@ export function outwardRoundToolcraftSceneRect(
|
|
|
158
170
|
};
|
|
159
171
|
}
|
|
160
172
|
|
|
161
|
-
export function
|
|
162
|
-
|
|
163
|
-
productRects: unknown = [],
|
|
164
|
-
visibility: ToolcraftRuntimeSceneVisibility = defaultToolcraftRuntimeSceneVisibility,
|
|
173
|
+
export function resolveToolcraftProductSceneBounds(
|
|
174
|
+
productRects: unknown,
|
|
165
175
|
): ToolcraftSceneBoundsResult {
|
|
166
176
|
if (
|
|
167
177
|
!Array.isArray(productRects) ||
|
|
168
178
|
!productRects.every(isFiniteSceneRect)
|
|
169
179
|
) {
|
|
170
|
-
return
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
180
|
+
return unavailableSceneBoundsResult;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const bounds = unionToolcraftSceneRects(productRects);
|
|
184
|
+
return bounds ? { bounds, ok: true } : emptySceneBoundsResult;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function resolveToolcraftSceneBounds(
|
|
188
|
+
state: ToolcraftState,
|
|
189
|
+
productRects: unknown = [],
|
|
190
|
+
visibility: ToolcraftRuntimeSceneVisibility = defaultToolcraftRuntimeSceneVisibility,
|
|
191
|
+
): ToolcraftSceneBoundsResult {
|
|
192
|
+
const productBounds = resolveToolcraftProductSceneBounds(productRects);
|
|
193
|
+
if (!productBounds.ok && productBounds.code === "scene-bounds-unavailable") {
|
|
194
|
+
return productBounds;
|
|
175
195
|
}
|
|
176
196
|
|
|
177
197
|
const bounds = unionToolcraftSceneRects([
|
|
178
198
|
...getRuntimeSceneRects(state, visibility),
|
|
179
|
-
...
|
|
199
|
+
...(productBounds.ok ? [productBounds.bounds] : []),
|
|
180
200
|
]);
|
|
181
201
|
|
|
182
|
-
return bounds
|
|
183
|
-
? { bounds, ok: true }
|
|
184
|
-
: {
|
|
185
|
-
code: "empty-scene",
|
|
186
|
-
message: "The scene has no visible exportable elements.",
|
|
187
|
-
ok: false,
|
|
188
|
-
};
|
|
202
|
+
return bounds ? { bounds, ok: true } : emptySceneBoundsResult;
|
|
189
203
|
}
|