cozyclay 1.9.0 → 2.0.0
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/CHANGELOG.md +171 -0
- package/bin/agent/agent-routes.mjs +74 -70
- package/bin/agent/agent-runner.mjs +10 -3
- package/bin/agent/motion-runtime.mjs +32 -260
- package/bin/agent/providers.mjs +46 -3
- package/bin/agent/session-store.mjs +41 -5
- package/bin/agent/studio-prompt.mjs +1 -1
- package/bin/agent/studio-tools.mjs +48 -11
- package/bin/cozyclay.mjs +26 -8
- package/bin/live/cli.mjs +22 -3
- package/bin/mcp-runtime.mjs +35 -20
- package/bin/telemetry-state.mjs +5 -2
- package/bin/update-check.mjs +15 -0
- package/dist/app/index.html +6 -6
- package/dist/assets/analytics-MVZOEW8O.js +1 -0
- package/dist/assets/app-C1sdVbg4.css +1 -0
- package/dist/assets/app-CA55tcI3.js +4876 -0
- package/dist/assets/{demo-B-eg1CT8.js → demo-ByeYp-dY.js} +1 -1
- package/dist/assets/{first-shot-handoff-CGgnvNu8.js → first-shot-handoff-DgT28ZiV.js} +1 -1
- package/dist/assets/{landing-IbRp3FnQ.js → landing-BaXkpFip.js} +1 -1
- package/dist/assets/shot-prompt-CbqWcWYi.css +1 -0
- package/dist/assets/shot-prompt-Yrt99wZx.js +17 -0
- package/dist/assets/{ticket-BgIzOBOf.js → ticket-BcmsnHn5.js} +1 -1
- package/dist/assets/workflow-DRdwoDBT.css +1 -0
- package/dist/assets/workflow-dNPGY9xz.js +179 -0
- package/dist/cozyclay-package.json +1 -1
- package/dist/fonts/IBM-Plex-Sans-OFL.txt +92 -0
- package/dist/fonts/JetBrains-Mono-OFL.txt +93 -0
- package/dist/fonts/README.md +13 -9
- package/dist/fonts/ibm-plex-sans-400-latin.woff2 +0 -0
- package/dist/fonts/ibm-plex-sans-500-latin.woff2 +0 -0
- package/dist/fonts/ibm-plex-sans-600-latin.woff2 +0 -0
- package/dist/fonts/jetbrains-mono-400-latin.woff2 +0 -0
- package/dist/fonts/jetbrains-mono-500-latin.woff2 +0 -0
- package/dist/index.html +7 -7
- package/dist/privacy/index.html +13 -8
- package/dist/sitemap.xml +6 -6
- package/dist/workflow/index.html +5 -5
- package/mcp/LIVE-PROTOCOL.md +1 -1
- package/mcp/live-hub.mjs +35 -93
- package/mcp/mesh-file.mjs +48 -0
- package/mcp/server.mjs +6 -77
- package/mcp/tool-handlers.mjs +224 -377
- package/package.json +2 -1
- package/src/App.jsx +1273 -9075
- package/src/analytics.js +393 -9
- package/src/app-context.js +211 -0
- package/src/app-stage.jsx +25 -24
- package/src/ardy/auto-fix-panel.css +108 -0
- package/src/ardy/collision-blockers.js +9 -3
- package/src/ardy/fix-collisions.js +10 -66
- package/src/ardy/ground.js +4 -4
- package/src/ardy/ik-drag.js +101 -0
- package/src/ardy/ik-key-json.js +43 -0
- package/src/ardy/ik.js +122 -76
- package/src/ardy/physics-panel.css +2 -0
- package/src/ardy/physics-panel.jsx +17 -5
- package/src/ardy/platform-fit-panel.css +64 -0
- package/src/ardy/platform-fit-panel.jsx +40 -0
- package/src/ardy/platform-fit.js +326 -0
- package/src/ardy/playback.js +166 -20
- package/src/ardy/range-pin.js +299 -0
- package/src/ardy/timeline-coordinates.js +26 -0
- package/src/ardy/timeline.css +1199 -0
- package/src/ardy/timeline.jsx +149 -41
- package/src/ardy/waypoints.js +2 -2
- package/src/asset-pane.css +462 -0
- package/src/asset-pane.jsx +461 -232
- package/src/command-bus.js +349 -0
- package/src/commands/ai.js +31 -0
- package/src/commands/cast.js +140 -0
- package/src/commands/elements/character.js +13 -0
- package/src/commands/elements/motion.js +14 -0
- package/src/commands/elements/object.js +22 -0
- package/src/commands/elements/scene.js +16 -0
- package/src/commands/elements/shot.js +9 -0
- package/src/commands/elements/stage.js +11 -0
- package/src/commands/elements.js +155 -0
- package/src/commands/export.js +20 -0
- package/src/commands/index.js +40 -0
- package/src/commands/motion.js +231 -0
- package/src/commands/objects.js +174 -0
- package/src/commands/project.js +53 -0
- package/src/commands/scene.js +81 -0
- package/src/commands/shared.js +19 -0
- package/src/commands/shot.js +103 -0
- package/src/commands/stage.js +17 -0
- package/src/commands/view.js +42 -0
- package/src/document-store.js +144 -0
- package/src/domains/cast.js +1007 -0
- package/src/domains/motion.js +3219 -0
- package/src/domains/objects.js +932 -0
- package/src/domains/scenes.js +817 -0
- package/src/domains/shots.js +404 -0
- package/src/domains/stage.js +93 -0
- package/src/dualview.jsx +113 -57
- package/src/facing-marks.js +3 -0
- package/src/first-success-guide.jsx +14 -14
- package/src/grid-view.js +18 -5
- package/src/hierarchy-model.js +24 -16
- package/src/hierarchy-panel.css +386 -0
- package/src/hierarchy-panel.jsx +150 -21
- package/src/{fal-motion-client.js → i2v-motion-client.js} +13 -13
- package/src/i2v-motion-studio.jsx +180 -0
- package/src/ik-camera.js +3 -0
- package/src/main.jsx +6 -0
- package/src/motion/generation.js +105 -0
- package/src/motion-readiness-ui.jsx +4 -4
- package/src/motion-readiness.js +11 -0
- package/src/motion-trail.js +366 -9
- package/src/object-gizmo.jsx +7 -1
- package/src/otio.js +2 -1
- package/src/panels/CameraPanel.jsx +60 -0
- package/src/panels/CharacterTransformPanel.jsx +48 -0
- package/src/panels/EnvironmentPanel.jsx +40 -0
- package/src/panels/Foldout.jsx +32 -0
- package/src/panels/LightPanel.jsx +24 -0
- package/src/panels/ObjectTransformPanel.jsx +554 -0
- package/src/panels/PosePanel.jsx +97 -0
- package/src/panels/ProjectPanel.jsx +33 -0
- package/src/panels/PromptBlocksPanel.jsx +442 -0
- package/src/panels/PropsPanel.jsx +70 -0
- package/src/panels/ReferenceImageField.jsx +91 -0
- package/src/panels/RigControlPanel.jsx +78 -0
- package/src/panels/RigPanel.jsx +37 -0
- package/src/panels/SubjectBox.jsx +47 -0
- package/src/panels/SubjectsPanel.jsx +39 -0
- package/src/panels/VideoCapturePanel.jsx +180 -0
- package/src/panels/details.css +1171 -0
- package/src/panels/motion.css +197 -0
- package/src/panels/pose.css +39 -0
- package/src/planview.jsx +60 -31
- package/src/posestudio.jsx +59 -4
- package/src/project-browser.css +1073 -0
- package/src/project-browser.jsx +284 -123
- package/src/range-pin-object-transform.js +19 -0
- package/src/range-pin-panel.css +518 -0
- package/src/range-pin-panel.jsx +344 -0
- package/src/result-modal.jsx +5 -5
- package/src/room.jsx +79 -51
- package/src/scene-objects.js +37 -1
- package/src/scenes.js +5 -0
- package/src/semantic-edit.js +2 -0
- package/src/settings-menu.jsx +39 -105
- package/src/shell/BottomDock.jsx +450 -0
- package/src/shell/DetailsSlot.jsx +513 -0
- package/src/shell/LibrarySlot.jsx +86 -0
- package/src/shell/MenuBar.jsx +601 -0
- package/src/shell/OutlinerSlot.jsx +50 -0
- package/src/shell/PreferencesDialog.jsx +538 -0
- package/src/shell/PreferencesSlot.jsx +40 -0
- package/src/shell/StatusBar.jsx +125 -0
- package/src/shell/StudioShell.jsx +92 -0
- package/src/shell/TopBar.jsx +129 -0
- package/src/shell/ViewportToolbar.jsx +656 -0
- package/src/shell/agent-glass.css +296 -0
- package/src/shell/dock.css +93 -0
- package/src/shell/glass-regions.css +985 -0
- package/src/shell/glass.css +687 -0
- package/src/shell/log-store.js +52 -0
- package/src/shell/mode.css +9 -0
- package/src/shell/preferences.css +651 -0
- package/src/shell/shell.css +282 -0
- package/src/shell/studio-shell-context.js +19 -0
- package/src/shell/topbar.css +435 -0
- package/src/shell/viewport.css +451 -0
- package/src/store/authored-intent.js +22 -0
- package/src/store/runtime-adapters.js +92 -0
- package/src/store/scene-stage.js +21 -0
- package/src/store/use-document-store.js +14 -0
- package/src/studio-actions.js +210 -0
- package/src/studio-agent-commands.js +41 -271
- package/src/studio-agent-context.js +39 -10
- package/src/studio-agent-motion.js +122 -373
- package/src/studio-agent-protocol.js +67 -31
- package/src/studio-app-binding.js +379 -0
- package/src/studio-contact-sheet.js +73 -0
- package/src/studio-elements.js +58 -51
- package/src/styles/themes.css +200 -0
- package/src/styles/tokens.css +69 -0
- package/src/styles.css +460 -1742
- package/src/theme.js +44 -0
- package/src/timeline-extent.js +16 -0
- package/src/trail-key-conflicts.js +39 -0
- package/src/trail-pick.js +59 -0
- package/src/ui.jsx +8 -8
- package/src/use-case-question.jsx +66 -0
- package/src/workflow/AgentPanel.jsx +82 -69
- package/src/workflow/agent-client.js +12 -2
- package/src/workflow/agent-panel.css +34 -32
- package/src/workflow/cozy-scene-node.css +2 -0
- package/src/workflow/workflow.css +2 -0
- package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
- package/tools/ardy/bridge.mjs +164 -146
- package/tools/ardy/visual-qa.mjs +5 -5
- package/tools/bench/EXP3.md +61 -0
- package/tools/bench/cclay_bench_extract_incam.py +125 -0
- package/tools/bench/cclay_bench_extract_obs.py +204 -0
- package/tools/bench/cclay_bench_runner.py +42 -0
- package/tools/bench/cube-contact.mjs +162 -0
- package/tools/bench/exp3.mjs +152 -0
- package/tools/bench/extract-bench-lib.mjs +216 -0
- package/tools/bench/extract-bench.mjs +302 -0
- package/tools/bench/fal-generate.mjs +88 -0
- package/tools/bench/fit/README.md +189 -0
- package/tools/bench/fit/camera.mjs +59 -0
- package/tools/bench/fit/contact.mjs +419 -0
- package/tools/bench/fit/footlock.mjs +234 -0
- package/tools/bench/fit/motion.mjs +85 -0
- package/tools/bench/fit/pin.mjs +27 -0
- package/tools/bench/fit/remote.mjs +105 -0
- package/tools/bench/fit-bench.mjs +126 -0
- package/tools/bench/fit-sanity.mjs +45 -0
- package/tools/bench/metrics.mjs +313 -0
- package/tools/bench/obs/depth.mjs +258 -0
- package/tools/bench/obs/extrinsics.mjs +175 -0
- package/tools/bench/obs/fit_mannequin_betas.py +340 -0
- package/tools/bench/obs/ground.mjs +294 -0
- package/tools/bench/obs/heading.mjs +151 -0
- package/tools/bench/obs/ladder.mjs +555 -0
- package/tools/bench/obs/mannequin-betas.json +124 -0
- package/tools/bench/obs/remote.mjs +83 -0
- package/tools/bench/obs/rest_joints.py +52 -0
- package/tools/bench/obs/ybot-targets.mjs +49 -0
- package/tools/bench/obs-bench.mjs +520 -0
- package/tools/bench/score.mjs +343 -0
- package/tools/bench/summarize.mjs +86 -0
- package/tools/dev/pages/privacy.html +13 -8
- package/tools/gt-render/browser.mjs +159 -0
- package/tools/gt-render/camera-math.mjs +276 -0
- package/tools/gt-render/page.mjs +267 -0
- package/tools/gt-render/render.mjs +514 -0
- package/tools/gt-render/scene-box.mjs +31 -0
- package/tools/gt-render/take-transform.mjs +87 -0
- package/tools/morphgs/assets/gen_truth.py +39 -0
- package/tools/morphgs/assets/mesh_ori_rig.txt +27 -0
- package/tools/morphgs/assets/playback-check.mjs +30 -0
- package/tools/morphgs/demo-gate.sh +26 -0
- package/tools/morphgs/fbx2morphgs.mjs +68 -0
- package/tools/morphgs/morphgs-to-cskel27.mjs +105 -0
- package/tools/morphgs/patches/preprocess_src-none-mode.patch +76 -0
- package/tools/morphgs/setup-on-cluster.sh +126 -0
- package/tools/qa/css-rule-usage.mjs +283 -0
- package/tools/qa/studio-control-count.mjs +70 -32
- package/tools/run-tests.mjs +134 -12
- package/tools/track/DESIGN.md +513 -0
- package/tools/track/backfill-provenance.mjs +116 -0
- package/tools/track/budget.mjs +8 -0
- package/tools/track/check-rig.mjs +209 -0
- package/tools/track/diagnostics.schema.json +60 -0
- package/tools/track/export-rig.mjs +256 -0
- package/tools/track/fallback.mjs +7 -0
- package/tools/track/fk-parity-fixture.mjs +161 -0
- package/tools/track/gate.mjs +349 -0
- package/tools/track/masks.mjs +318 -0
- package/tools/track/metrics.mjs +201 -0
- package/tools/track/publish-obs.mjs +108 -0
- package/tools/track/py/check_env.py +61 -0
- package/tools/track/py/eval_lr.py +229 -0
- package/tools/track/py/lr_viterbi.py +294 -0
- package/tools/track/py/masks.py +405 -0
- package/tools/track/py/objective.py +382 -0
- package/tools/track/py/rig.py +508 -0
- package/tools/track/py/scene.py +326 -0
- package/tools/track/py/test_joint_indices.py +123 -0
- package/tools/track/py/test_lr_viterbi.py +195 -0
- package/tools/track/py/test_masks.py +229 -0
- package/tools/track/py/test_rig.py +288 -0
- package/tools/track/py/test_scene.py +354 -0
- package/tools/track/py/test_track.py +328 -0
- package/tools/track/py/track.py +411 -0
- package/tools/track/remote.mjs +140 -0
- package/tools/track/rig-dump.mjs +227 -0
- package/tools/track/run-box-tests.mjs +14 -0
- package/tools/track/run-box.mjs +168 -0
- package/tools/track/setup-box.sh +59 -0
- package/tools/track/study-2d.mjs +728 -0
- package/dist/assets/analytics-B1hnH66c.js +0 -1
- package/dist/assets/app-BWusbqgO.js +0 -4861
- package/dist/assets/app-qKDo4PBX.css +0 -1
- package/dist/assets/shot-prompt-CHUtw6af.js +0 -17
- package/dist/assets/shot-prompt-C_g2BHVc.css +0 -1
- package/dist/assets/workflow-CVwzMhz2.js +0 -179
- package/dist/assets/workflow-DsudxKHF.css +0 -1
- package/src/fal-motion-studio.jsx +0 -180
- package/src/scene-history.js +0 -129
package/src/scene-objects.js
CHANGED
|
@@ -363,6 +363,7 @@ export function createSceneObject(kind, existing = [], placement = {}) {
|
|
|
363
363
|
// world-anchored prop — where everything starts, because a fresh object
|
|
364
364
|
// is dropped in front of the lens, not into someone's hand.
|
|
365
365
|
attach: null,
|
|
366
|
+
hidden: false,
|
|
366
367
|
footprint: { ...entry.footprint },
|
|
367
368
|
height: entry.height,
|
|
368
369
|
supportY: Number.isFinite(entry.supportY) ? entry.supportY : entry.height,
|
|
@@ -413,6 +414,7 @@ export function createCutoutObject({ assetId, aspect = 1, height = CUTOUT_DEFAUL
|
|
|
413
414
|
color: CUTOUT_TINT,
|
|
414
415
|
parent: null,
|
|
415
416
|
attach: null,
|
|
417
|
+
hidden: false,
|
|
416
418
|
// Key order matches what `normalizeSceneObject` writes, so a record
|
|
417
419
|
// survives a storage round trip byte-for-byte.
|
|
418
420
|
assetId,
|
|
@@ -493,6 +495,7 @@ export function createMeshObject({ assetId, height = MESH_DEFAULT_HEIGHT, footpr
|
|
|
493
495
|
color: MESH_ENTRY.color,
|
|
494
496
|
parent: null,
|
|
495
497
|
attach: null,
|
|
498
|
+
hidden: false,
|
|
496
499
|
assetId,
|
|
497
500
|
clay: clay === true,
|
|
498
501
|
footprint: meshFootprint,
|
|
@@ -541,6 +544,36 @@ export function descendantsOf(objects, id) {
|
|
|
541
544
|
return out;
|
|
542
545
|
}
|
|
543
546
|
|
|
547
|
+
/** True when this record, a parent above it, or a character carrying any of
|
|
548
|
+
* those records is hidden. A cycle in parent links ends the walk. When the
|
|
549
|
+
* id is in `objects`, that stored record is what the walk reads, so a display
|
|
550
|
+
* copy cannot disagree with the authored flag. Nothing in the lists is changed. */
|
|
551
|
+
export function isEffectivelyHidden(entity, objects = [], characters = []) {
|
|
552
|
+
if (!entity || typeof entity !== "object") return false;
|
|
553
|
+
const byId = new Map();
|
|
554
|
+
for (const object of objects) {
|
|
555
|
+
if (object && typeof object.id === "string") byId.set(object.id, object);
|
|
556
|
+
}
|
|
557
|
+
const characterHidden = (id) => characters.some((item) => item && typeof item === "object" && item.id === id && item.hidden === true);
|
|
558
|
+
const seen = new Set();
|
|
559
|
+
let current = entity;
|
|
560
|
+
while (current && typeof current === "object") {
|
|
561
|
+
const id = typeof current.id === "string" ? current.id : null;
|
|
562
|
+
if (id) {
|
|
563
|
+
if (seen.has(id)) break;
|
|
564
|
+
seen.add(id);
|
|
565
|
+
if (byId.has(id)) current = byId.get(id);
|
|
566
|
+
}
|
|
567
|
+
if (current.hidden === true) return true;
|
|
568
|
+
const characterId = current.attach && typeof current.attach.characterId === "string" ? current.attach.characterId : null;
|
|
569
|
+
if (characterId && characterHidden(characterId)) return true;
|
|
570
|
+
const parentId = typeof current.parent === "string" ? current.parent : null;
|
|
571
|
+
if (!parentId) break;
|
|
572
|
+
current = byId.get(parentId) ?? null;
|
|
573
|
+
}
|
|
574
|
+
return false;
|
|
575
|
+
}
|
|
576
|
+
|
|
544
577
|
export function updateSceneObject(objects, id, patch) {
|
|
545
578
|
let changed = false;
|
|
546
579
|
const target = objects.find((object) => object.id === id);
|
|
@@ -595,6 +628,7 @@ export function updateSceneObject(objects, id, patch) {
|
|
|
595
628
|
if (typeof patch[key] !== "string" || !patch[key] || patch[key] === object[key]) continue;
|
|
596
629
|
update[key] = patch[key];
|
|
597
630
|
}
|
|
631
|
+
if (typeof patch.hidden === "boolean" && patch.hidden !== (object.hidden === true)) update.hidden = patch.hidden;
|
|
598
632
|
// The travel path is authored geometry, not a bounded transform: it is
|
|
599
633
|
// normalized by createObjectPath (which repairs or refuses it) and set
|
|
600
634
|
// wholesale, with null clearing it back to a standing object.
|
|
@@ -820,6 +854,7 @@ export function normalizeSceneObject(record) {
|
|
|
820
854
|
// not exist. A record written before attachment existed has no field at
|
|
821
855
|
// all, and null is exactly what it meant: world-anchored.
|
|
822
856
|
attach: normalizeSceneAttach(record.attach) ?? null,
|
|
857
|
+
hidden: record.hidden === true,
|
|
823
858
|
// Library kinds take their size from the library — a stored footprint is
|
|
824
859
|
// stale data, not a fact. Cutouts and meshes are the exceptions: their
|
|
825
860
|
// size IS per-instance. A cutout rebuilds the footprint from height and
|
|
@@ -1042,10 +1077,11 @@ export function objectFootprintBounds(object) {
|
|
|
1042
1077
|
* The contact height is exact, never snapped to the 5 cm grid, and never
|
|
1043
1078
|
* clamped here: the y clamp stays in updateSceneObject, the single owner.
|
|
1044
1079
|
*/
|
|
1045
|
-
export function dropToSurfacePatch(object, others) {
|
|
1080
|
+
export function dropToSurfacePatch(object, others, characters = []) {
|
|
1046
1081
|
const self = objectFootprintBounds(object);
|
|
1047
1082
|
let highestTop = 0;
|
|
1048
1083
|
for (const other of others) {
|
|
1084
|
+
if (isEffectivelyHidden(other, others, characters)) continue;
|
|
1049
1085
|
const bounds = objectFootprintBounds(other);
|
|
1050
1086
|
if (self.minX >= bounds.maxX - OVERLAP_EPS || bounds.minX >= self.maxX - OVERLAP_EPS) continue;
|
|
1051
1087
|
if (self.minZ >= bounds.maxZ - OVERLAP_EPS || bounds.minZ >= self.maxZ - OVERLAP_EPS) continue;
|
package/src/scenes.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { DEFAULT_SENSOR_FORMAT, SENSOR_FORMATS } from "./shot.js";
|
|
6
6
|
import { normalizeStableItems } from "./stable-items.js";
|
|
7
7
|
import { normalizeMotionCalibration } from "./ardy/motion-calibration.js";
|
|
8
|
+
import { normalizeTrailEdits } from "./motion-trail.js";
|
|
8
9
|
import { elementByPath } from "./studio-elements.js";
|
|
9
10
|
import { wrapAngle } from "./scene-objects.js";
|
|
10
11
|
|
|
@@ -214,6 +215,10 @@ function normalizeMotionRef(ref) {
|
|
|
214
215
|
// boundary for a later scene reload. cloneValue also strips non-finite values
|
|
215
216
|
// and recursively owns the object, so a caller cannot mutate the saved stage.
|
|
216
217
|
if (plainObject(ref.calibration)) normalized.calibration = normalizeMotionCalibration(ref.calibration);
|
|
218
|
+
if (ref.trailEdits != null) normalized.trailEdits = normalizeTrailEdits(ref.trailEdits);
|
|
219
|
+
// An agent-installed take keeps the Studio take id its receipts name, so a
|
|
220
|
+
// reload still reports the same take. Optional, like calibration.
|
|
221
|
+
if (typeof ref.studioTakeId === "string" && ref.studioTakeId) normalized.studioTakeId = ref.studioTakeId;
|
|
217
222
|
return normalized;
|
|
218
223
|
}
|
|
219
224
|
|
package/src/semantic-edit.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createCameraBlock } from "./camera-block.js";
|
|
2
|
+
import { isBusRunActive } from "./command-bus.js";
|
|
2
3
|
|
|
3
4
|
export const FIRST_EDIT_VERSION = 1;
|
|
4
5
|
export const FIRST_EDIT_KINDS = Object.freeze([
|
|
@@ -90,6 +91,7 @@ export function createSemanticState(initial, publish, observe, domain) {
|
|
|
90
91
|
const before = current;
|
|
91
92
|
const after = typeof update === "function" ? update(before) : update;
|
|
92
93
|
current = after;
|
|
94
|
+
if (authored && !isBusRunActive()) console.warn(`[bus] semantic edit for ${domain} called outside a bus run`);
|
|
93
95
|
publish(after);
|
|
94
96
|
if (authored) observe(domain, before, after);
|
|
95
97
|
return after;
|
package/src/settings-menu.jsx
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import { useEffect, useRef, useState } from "react";
|
|
2
|
-
import { getAnalyticsOptOut, setAnalyticsOptOut } from "./analytics.js";
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
2
|
import { LOCALE, ko, localeChosen, setLocale } from "./locale.js";
|
|
3
|
+
import PreferencesDialog from "./shell/PreferencesDialog.jsx";
|
|
4
4
|
|
|
5
5
|
// App settings — language and analytics — behind one labelled topbar trigger
|
|
6
6
|
// (#193, docs/studio-ui-ia.md R4). Neither item edits the document, so they do
|
|
7
7
|
// not belong on the Save/Export row as bare toggles.
|
|
8
8
|
//
|
|
9
|
-
// The
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// the trigger.
|
|
9
|
+
// The trigger now opens the v2 Preferences dialog. The old language strings stay
|
|
10
|
+
// here because the locale contract and the camera-tutorial source contract both
|
|
11
|
+
// treat this file as the Settings entry point.
|
|
13
12
|
const LANGUAGES = [
|
|
14
13
|
{ id: "en", label: "English", action: "Switch to English" },
|
|
15
14
|
{ id: "ko", label: "한국어", action: "한국어로 전환" },
|
|
@@ -17,43 +16,35 @@ const LANGUAGES = [
|
|
|
17
16
|
|
|
18
17
|
export default function SettingsMenu({ motionSetup, motionSetupReveal = 0 }) {
|
|
19
18
|
const [open, setOpen] = useState(false);
|
|
20
|
-
const [setupOpen, setSetupOpen] = useState(false);
|
|
21
|
-
const [optedOut, setOptedOut] = useState(getAnalyticsOptOut);
|
|
22
19
|
const triggerRef = useRef(null);
|
|
23
|
-
const
|
|
20
|
+
const close = useCallback(() => {
|
|
21
|
+
setOpen(false);
|
|
22
|
+
triggerRef.current?.focus();
|
|
23
|
+
}, []);
|
|
24
|
+
const openCameraTutorial = useCallback(() => {
|
|
25
|
+
window.dispatchEvent(new CustomEvent("cozyclay:camera-tutorial", { detail: { open: true } }));
|
|
26
|
+
setOpen(false);
|
|
27
|
+
}, []);
|
|
28
|
+
const cameraTutorial = (
|
|
29
|
+
<>
|
|
30
|
+
<h4>{ko("Help", "도움말")}</h4>
|
|
31
|
+
<button
|
|
32
|
+
type="button"
|
|
33
|
+
data-testid="settings-camera-tutorial"
|
|
34
|
+
title={ko("Learn the camera in seven steps", "일곱 단계로 카메라 익히기")}
|
|
35
|
+
onClick={openCameraTutorial}
|
|
36
|
+
>
|
|
37
|
+
{ko("Camera tutorial", "카메라 튜토리얼")}
|
|
38
|
+
</button>
|
|
39
|
+
</>
|
|
40
|
+
);
|
|
24
41
|
|
|
25
|
-
//
|
|
26
|
-
//
|
|
42
|
+
// A contextual readiness action can reveal Preferences directly on the
|
|
43
|
+
// motion connection page without changing the topbar or adding a second
|
|
44
|
+
// settings surface.
|
|
27
45
|
useEffect(() => {
|
|
28
|
-
if (
|
|
29
|
-
setOpen(true);
|
|
30
|
-
setSetupOpen(true);
|
|
46
|
+
if (motionSetupReveal) setOpen(true);
|
|
31
47
|
}, [motionSetupReveal]);
|
|
32
|
-
useEffect(() => {
|
|
33
|
-
if (open && setupOpen) setupRef.current?.focus();
|
|
34
|
-
}, [open, setupOpen, motionSetupReveal]);
|
|
35
|
-
|
|
36
|
-
// Dismissal mirrors the project menu: listen only while open, ignore
|
|
37
|
-
// presses inside the wrap so the trigger keeps toggling, close on Escape
|
|
38
|
-
// and hand focus back to the control the operator came from.
|
|
39
|
-
useEffect(() => {
|
|
40
|
-
if (!open) return undefined;
|
|
41
|
-
const onPointerDown = (event) => {
|
|
42
|
-
if (event.target instanceof Element && event.target.closest(".settings-menu-wrap")) return;
|
|
43
|
-
setOpen(false);
|
|
44
|
-
};
|
|
45
|
-
const onKeyDown = (event) => {
|
|
46
|
-
if (event.key !== "Escape") return;
|
|
47
|
-
setOpen(false);
|
|
48
|
-
triggerRef.current?.focus();
|
|
49
|
-
};
|
|
50
|
-
document.addEventListener("pointerdown", onPointerDown);
|
|
51
|
-
window.addEventListener("keydown", onKeyDown);
|
|
52
|
-
return () => {
|
|
53
|
-
document.removeEventListener("pointerdown", onPointerDown);
|
|
54
|
-
window.removeEventListener("keydown", onKeyDown);
|
|
55
|
-
};
|
|
56
|
-
}, [open]);
|
|
57
48
|
|
|
58
49
|
// First-run cue: the studio starts in English even on a Korean browser, so
|
|
59
50
|
// the only visible hint that Korean exists used to be the old locale button.
|
|
@@ -68,78 +59,21 @@ export default function SettingsMenu({ motionSetup, motionSetupReveal = 0 }) {
|
|
|
68
59
|
className="topbar-action settings-menu-trigger"
|
|
69
60
|
data-testid="settings-menu-trigger"
|
|
70
61
|
aria-expanded={open}
|
|
71
|
-
aria-haspopup="
|
|
62
|
+
aria-haspopup="dialog"
|
|
72
63
|
title={ko("Language and analytics", "언어 및 사용 통계")}
|
|
73
64
|
ref={triggerRef}
|
|
74
|
-
onClick={() =>
|
|
65
|
+
onClick={() => (open ? close() : setOpen(true))}
|
|
75
66
|
>
|
|
76
67
|
{label}
|
|
77
68
|
<span className="caret">▾</span>
|
|
78
69
|
</button>
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
data-testid="settings-motion-setup"
|
|
87
|
-
aria-expanded={setupOpen}
|
|
88
|
-
ref={setupRef}
|
|
89
|
-
onClick={() => setSetupOpen((value) => !value)}
|
|
90
|
-
>
|
|
91
|
-
{ko("Motion generation", "모션 생성")}
|
|
92
|
-
</button>
|
|
93
|
-
{setupOpen && motionSetup}
|
|
94
|
-
</>
|
|
95
|
-
)}
|
|
96
|
-
<h4>{ko("Language", "언어")}</h4>
|
|
97
|
-
{LANGUAGES.map((language) => (
|
|
98
|
-
<button
|
|
99
|
-
key={language.id}
|
|
100
|
-
type="button"
|
|
101
|
-
data-testid={`settings-locale-${language.id}`}
|
|
102
|
-
aria-pressed={LOCALE === language.id}
|
|
103
|
-
title={language.action}
|
|
104
|
-
onClick={() => setLocale(language.id)}
|
|
105
|
-
>
|
|
106
|
-
{language.label}
|
|
107
|
-
<span className="mark" aria-hidden="true">{LOCALE === language.id ? "✓" : ""}</span>
|
|
108
|
-
</button>
|
|
109
|
-
))}
|
|
110
|
-
<h4>{ko("Privacy", "개인정보")}</h4>
|
|
111
|
-
<button
|
|
112
|
-
type="button"
|
|
113
|
-
data-testid="settings-analytics"
|
|
114
|
-
aria-pressed={!optedOut}
|
|
115
|
-
title={optedOut
|
|
116
|
-
? ko("Turn anonymous analytics on", "익명 사용 통계 켜기")
|
|
117
|
-
: ko("Turn anonymous analytics off", "익명 사용 통계 끄기")}
|
|
118
|
-
onClick={async () => {
|
|
119
|
-
const actual = await setAnalyticsOptOut(!optedOut);
|
|
120
|
-
setOptedOut(actual);
|
|
121
|
-
}}
|
|
122
|
-
>
|
|
123
|
-
{ko("Anonymous analytics", "익명 사용 통계")}
|
|
124
|
-
<span className="mark">{optedOut ? ko("off", "끔") : ko("on", "켬")}</span>
|
|
125
|
-
</button>
|
|
126
|
-
{/* Learning the camera is not a document edit and not a topbar
|
|
127
|
-
button (R4): the tutorial opens from this closed popover, so the
|
|
128
|
-
mode budgets in docs/studio-ui-ia.md §1 are untouched. */}
|
|
129
|
-
<h4>{ko("Help", "도움말")}</h4>
|
|
130
|
-
<button
|
|
131
|
-
type="button"
|
|
132
|
-
data-testid="settings-camera-tutorial"
|
|
133
|
-
title={ko("Learn the camera in seven steps", "일곱 단계로 카메라 익히기")}
|
|
134
|
-
onClick={() => {
|
|
135
|
-
window.dispatchEvent(new CustomEvent("cozyclay:camera-tutorial", { detail: { open: true } }));
|
|
136
|
-
setOpen(false);
|
|
137
|
-
}}
|
|
138
|
-
>
|
|
139
|
-
{ko("Camera tutorial", "카메라 튜토리얼")}
|
|
140
|
-
</button>
|
|
141
|
-
</div>
|
|
142
|
-
)}
|
|
70
|
+
<PreferencesDialog
|
|
71
|
+
open={open}
|
|
72
|
+
onClose={close}
|
|
73
|
+
motionSetup={motionSetup}
|
|
74
|
+
initialSection={motionSetupReveal ? "ARDY Connection" : "Appearance"}
|
|
75
|
+
cameraTutorial={cameraTutorial}
|
|
76
|
+
/>
|
|
143
77
|
</div>
|
|
144
78
|
);
|
|
145
79
|
}
|