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
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
.v2-outliner {
|
|
2
|
+
min-height: 0;
|
|
3
|
+
max-height: none;
|
|
4
|
+
flex: 1 1 auto;
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
background: var(--surface-panel, #151517);
|
|
8
|
+
border: 0;
|
|
9
|
+
color: var(--text-1, #e4e4e7);
|
|
10
|
+
font-family: var(--sans, 'IBM Plex Sans');
|
|
11
|
+
font-size: var(--outliner-ui-size, 12px);
|
|
12
|
+
line-height: 1.2;
|
|
13
|
+
overflow: hidden;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.v2-outliner .hierarchy-heading {
|
|
17
|
+
height: var(--outliner-header-height, 36px);
|
|
18
|
+
min-height: var(--outliner-header-height, 36px);
|
|
19
|
+
padding: 0 var(--sp-2, 12px);
|
|
20
|
+
border-bottom: var(--outliner-border-width, 1px) solid var(--row-divider, #1d1d20);
|
|
21
|
+
background: var(--surface-panel, #151517);
|
|
22
|
+
color: var(--text-1, #e4e4e7);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.v2-outliner .hierarchy-heading strong {
|
|
26
|
+
margin: 0;
|
|
27
|
+
color: var(--text-1, #e4e4e7);
|
|
28
|
+
font-size: var(--outliner-title-size, 12px);
|
|
29
|
+
font-weight: 500;
|
|
30
|
+
letter-spacing: 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.v2-outliner .hierarchy-kicker,
|
|
34
|
+
.v2-outliner .hierarchy-frame-status {
|
|
35
|
+
display: none;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.v2-outliner .v2-outliner-count {
|
|
39
|
+
color: var(--text-3, #636369);
|
|
40
|
+
font-family: var(--mono, 'JetBrains Mono');
|
|
41
|
+
font-size: var(--outliner-secondary-size, 11px);
|
|
42
|
+
font-weight: 400;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.v2-outliner .v2-outliner-search-wrap {
|
|
46
|
+
position: relative;
|
|
47
|
+
display: flex;
|
|
48
|
+
align-items: center;
|
|
49
|
+
height: var(--h-control, 28px);
|
|
50
|
+
min-height: var(--h-control, 28px);
|
|
51
|
+
margin: 0 var(--sp-2, 12px) var(--sp-1, 8px);
|
|
52
|
+
border-radius: var(--r-1, 4px);
|
|
53
|
+
background: var(--field, #0f0f11);
|
|
54
|
+
color: var(--text-3, #636369);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.v2-outliner .v2-outliner-search-icon {
|
|
58
|
+
position: absolute;
|
|
59
|
+
left: var(--sp-1, 8px);
|
|
60
|
+
color: var(--text-3, #636369);
|
|
61
|
+
font-size: var(--outliner-search-icon-size, 14px);
|
|
62
|
+
line-height: 1;
|
|
63
|
+
pointer-events: none;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.v2-outliner .v2-outliner-search {
|
|
67
|
+
width: 100%;
|
|
68
|
+
height: var(--h-control, 28px);
|
|
69
|
+
min-height: var(--h-control, 28px);
|
|
70
|
+
padding: 0 var(--sp-1, 8px) 0 var(--sp-3, 16px);
|
|
71
|
+
border: 0;
|
|
72
|
+
border-radius: var(--r-1, 4px);
|
|
73
|
+
outline: 0;
|
|
74
|
+
background: none;
|
|
75
|
+
color: var(--text-1, #e4e4e7);
|
|
76
|
+
font: inherit;
|
|
77
|
+
font-size: var(--outliner-secondary-size, 11px);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.v2-outliner .v2-outliner-search::placeholder {
|
|
81
|
+
color: var(--text-3, #636369);
|
|
82
|
+
opacity: 1;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.v2-outliner .v2-outliner-search:focus-visible {
|
|
86
|
+
outline: var(--outliner-border-width, 1px) solid var(--select, #e8a33d);
|
|
87
|
+
outline-offset: calc(var(--outliner-border-width, 1px) * -1);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.v2-outliner .hierarchy-tree {
|
|
91
|
+
flex: 1 1 auto;
|
|
92
|
+
min-height: 0;
|
|
93
|
+
padding: 0 var(--sp-1, 8px) var(--sp-1, 8px);
|
|
94
|
+
overflow: auto;
|
|
95
|
+
background: var(--surface-panel, #151517);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.v2-outliner .hierarchy-row-wrap {
|
|
99
|
+
position: relative;
|
|
100
|
+
height: var(--h-row, 24px);
|
|
101
|
+
min-height: var(--h-row, 24px);
|
|
102
|
+
margin-left: calc(var(--hierarchy-depth, 0) * var(--outliner-indent, 14px));
|
|
103
|
+
padding-right: 0;
|
|
104
|
+
border-radius: var(--r-1, 4px);
|
|
105
|
+
background: none;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.v2-outliner .hierarchy-row-wrap:hover {
|
|
109
|
+
background: var(--surface-raised, #1e1e21);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.v2-outliner .hierarchy-row-wrap.selected {
|
|
113
|
+
background: var(--select-tint, rgba(232,163,61,.14));
|
|
114
|
+
box-shadow: none;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.v2-outliner .hierarchy-row-wrap[data-own-hidden="true"] {
|
|
118
|
+
opacity: .52;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.v2-outliner .hierarchy-row-wrap:before {
|
|
122
|
+
display: none;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.v2-outliner .hierarchy-toggle,
|
|
126
|
+
.v2-outliner .hierarchy-fold-space {
|
|
127
|
+
flex: 0 0 var(--outliner-chevron-slot, 12px);
|
|
128
|
+
width: var(--outliner-chevron-slot, 12px);
|
|
129
|
+
height: var(--h-row, 24px);
|
|
130
|
+
padding: 0;
|
|
131
|
+
border: 0;
|
|
132
|
+
background: none;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.v2-outliner .hierarchy-toggle {
|
|
136
|
+
display: grid;
|
|
137
|
+
place-items: center;
|
|
138
|
+
color: var(--text-3, #636369);
|
|
139
|
+
font-size: var(--outliner-chevron-size, 8px);
|
|
140
|
+
font-weight: 500;
|
|
141
|
+
line-height: 1;
|
|
142
|
+
cursor: pointer;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.v2-outliner .hierarchy-toggle:hover,
|
|
146
|
+
.v2-outliner .hierarchy-toggle:focus-visible {
|
|
147
|
+
color: var(--text-1, #e4e4e7);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.v2-outliner .hierarchy-toggle.placeholder,
|
|
151
|
+
.v2-outliner .hierarchy-fold-space {
|
|
152
|
+
pointer-events: none;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.v2-outliner .hierarchy-row {
|
|
156
|
+
width: 100%;
|
|
157
|
+
min-width: 0;
|
|
158
|
+
height: var(--h-row, 24px);
|
|
159
|
+
padding: 0 var(--sp-1, 8px) 0 0;
|
|
160
|
+
border: 0;
|
|
161
|
+
background: none;
|
|
162
|
+
color: var(--text-row, #c9c9ce);
|
|
163
|
+
font: inherit;
|
|
164
|
+
font-size: var(--outliner-ui-size, 12px);
|
|
165
|
+
font-weight: 400;
|
|
166
|
+
text-align: left;
|
|
167
|
+
cursor: default;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.v2-outliner .hierarchy-row:not(.icon-only) {
|
|
171
|
+
display: flex;
|
|
172
|
+
align-items: center;
|
|
173
|
+
gap: var(--sp-1, 8px);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.v2-outliner .hierarchy-row.icon-only {
|
|
177
|
+
flex: 0 0 auto;
|
|
178
|
+
display: inline-flex;
|
|
179
|
+
align-items: center;
|
|
180
|
+
gap: var(--sp-1, 8px);
|
|
181
|
+
width: auto;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.v2-outliner .v2-outliner-chip {
|
|
185
|
+
flex: 0 0 var(--outliner-chip-size, 6px);
|
|
186
|
+
width: var(--outliner-chip-size, 6px);
|
|
187
|
+
height: var(--outliner-chip-size, 6px);
|
|
188
|
+
border-radius: var(--outliner-chip-radius, 2px);
|
|
189
|
+
background: var(--chip-folder, #8a7a55);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.v2-outliner .v2-outliner-chip.folder { background: var(--chip-folder, #8a7a55); }
|
|
193
|
+
.v2-outliner .v2-outliner-chip.mesh { background: var(--chip-mesh, #7e8b99); }
|
|
194
|
+
.v2-outliner .v2-outliner-chip.cast { background: var(--chip-cast, #5fb584); }
|
|
195
|
+
.v2-outliner .v2-outliner-chip.cam { background: var(--camera, #a78bfa); }
|
|
196
|
+
.v2-outliner .v2-outliner-chip.light { background: var(--chip-light, #d9b84a); }
|
|
197
|
+
|
|
198
|
+
.v2-outliner .hierarchy-label {
|
|
199
|
+
flex: 1 1 auto;
|
|
200
|
+
min-width: 0;
|
|
201
|
+
overflow: hidden;
|
|
202
|
+
color: var(--text-row, #c9c9ce);
|
|
203
|
+
font-size: var(--outliner-ui-size, 12px);
|
|
204
|
+
font-weight: 400;
|
|
205
|
+
text-overflow: ellipsis;
|
|
206
|
+
white-space: nowrap;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.v2-outliner .hierarchy-row-wrap.selected .hierarchy-label {
|
|
210
|
+
color: var(--text-1, #e4e4e7);
|
|
211
|
+
font-weight: 500;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.v2-outliner .v2-outliner-type {
|
|
215
|
+
flex: 0 0 auto;
|
|
216
|
+
margin-left: auto;
|
|
217
|
+
color: var(--text-off, #4f4f55);
|
|
218
|
+
font-size: var(--outliner-type-size, 10.5px);
|
|
219
|
+
font-weight: 400;
|
|
220
|
+
white-space: nowrap;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.v2-outliner .hierarchy-status,
|
|
224
|
+
.v2-outliner .hierarchy-badge {
|
|
225
|
+
display: none;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.v2-outliner .hierarchy-eye {
|
|
229
|
+
flex: 0 0 var(--outliner-eye-closed-width, 0px);
|
|
230
|
+
width: var(--outliner-eye-closed-width, 0px);
|
|
231
|
+
height: var(--h-row, 24px);
|
|
232
|
+
padding: 0;
|
|
233
|
+
border: 0;
|
|
234
|
+
background: none;
|
|
235
|
+
color: var(--text-2, #9b9ba1);
|
|
236
|
+
opacity: 0;
|
|
237
|
+
overflow: hidden;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.v2-outliner .hierarchy-eye:focus-visible {
|
|
241
|
+
flex-basis: var(--outliner-eye-width, 24px);
|
|
242
|
+
width: var(--outliner-eye-width, 24px);
|
|
243
|
+
opacity: 1;
|
|
244
|
+
outline: var(--outliner-border-width, 1px) solid var(--select, #e8a33d);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.v2-outliner .hierarchy-row-wrap[data-drop="over"] {
|
|
248
|
+
border: var(--outliner-border-width, 1px) dashed var(--select, #e8a33d);
|
|
249
|
+
background: var(--select-tint, rgba(232,163,61,.14));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.v2-outliner .hierarchy-row-wrap[data-drop="target"] {
|
|
253
|
+
border: var(--outliner-border-width, 1px) solid var(--row-divider, #1d1d20);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.v2-outliner .hierarchy-row-wrap .hierarchy-scene-pill {
|
|
257
|
+
flex: 1 1 auto;
|
|
258
|
+
min-width: 0;
|
|
259
|
+
max-width: none;
|
|
260
|
+
height: var(--h-row, 24px);
|
|
261
|
+
margin: 0 var(--sp-1, 8px) 0 var(--sp-1, 8px);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.v2-outliner .hierarchy-row-wrap .hierarchy-scene-pill .dropdown,
|
|
265
|
+
.v2-outliner .hierarchy-row-wrap .hierarchy-scene-pill .trigger {
|
|
266
|
+
width: 100%;
|
|
267
|
+
min-width: 0;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.v2-outliner .hierarchy-row-wrap .hierarchy-scene-pill .trigger {
|
|
271
|
+
height: var(--h-row, 24px);
|
|
272
|
+
gap: var(--outliner-trigger-gap, 6px);
|
|
273
|
+
padding: 0 var(--sp-1, 8px);
|
|
274
|
+
border: 0;
|
|
275
|
+
border-radius: var(--r-1, 4px);
|
|
276
|
+
background: var(--surface-active, #26262a);
|
|
277
|
+
color: var(--text-row, #c9c9ce);
|
|
278
|
+
font: inherit;
|
|
279
|
+
font-size: var(--outliner-ui-size, 12px);
|
|
280
|
+
text-align: left;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.v2-outliner .hierarchy-row-wrap .hierarchy-scene-pill .trigger:hover,
|
|
284
|
+
.v2-outliner .hierarchy-row-wrap .hierarchy-scene-pill .dropdown.open .trigger {
|
|
285
|
+
background: var(--surface-raised, #1e1e21);
|
|
286
|
+
color: var(--text-1, #e4e4e7);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.v2-outliner .hierarchy-row-wrap .hierarchy-scene-pill .dd-caret {
|
|
290
|
+
flex: 0 0 var(--outliner-chevron-slot, 12px);
|
|
291
|
+
width: var(--outliner-chevron-slot, 12px);
|
|
292
|
+
height: var(--outliner-chevron-slot, 12px);
|
|
293
|
+
color: var(--text-3, #636369);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.v2-outliner .hierarchy-scene-hint {
|
|
297
|
+
flex: none;
|
|
298
|
+
margin: 0;
|
|
299
|
+
padding: 0 var(--sp-2, 12px) var(--sp-1, 8px);
|
|
300
|
+
color: var(--text-2, #9b9ba1);
|
|
301
|
+
font-size: var(--outliner-secondary-size, 11px);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.v2-outliner .v2-outliner-empty {
|
|
305
|
+
min-height: var(--h-row, 24px);
|
|
306
|
+
padding: var(--sp-1, 8px);
|
|
307
|
+
color: var(--text-3, #636369);
|
|
308
|
+
font-size: var(--outliner-secondary-size, 11px);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.v2-outliner .hierarchy-rename-input {
|
|
312
|
+
flex: 1 1 auto;
|
|
313
|
+
min-width: 0;
|
|
314
|
+
height: var(--h-control, 28px);
|
|
315
|
+
padding: 0 var(--sp-1, 8px);
|
|
316
|
+
border: 0;
|
|
317
|
+
border-radius: var(--r-1, 4px);
|
|
318
|
+
background: var(--field, #0f0f11);
|
|
319
|
+
color: var(--text-1, #e4e4e7);
|
|
320
|
+
font: inherit;
|
|
321
|
+
font-size: var(--outliner-ui-size, 12px);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.v2-outliner .hierarchy-rename-input:focus-visible {
|
|
325
|
+
outline: var(--outliner-border-width, 1px) solid var(--select, #e8a33d);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.v2-outliner .hierarchy-context-menu {
|
|
329
|
+
min-width: var(--outliner-menu-width, 220px);
|
|
330
|
+
padding: var(--outliner-menu-padding, 4px);
|
|
331
|
+
border: var(--outliner-border-width, 1px) solid var(--row-divider, #1d1d20);
|
|
332
|
+
border-radius: var(--r-2, 6px);
|
|
333
|
+
background: var(--surface-raised, #1e1e21);
|
|
334
|
+
box-shadow: 0 var(--sp-1, 8px) var(--sp-3, 16px) var(--overlay, rgba(12,12,13,.78));
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.v2-outliner .hierarchy-context-item {
|
|
338
|
+
min-height: var(--h-control, 28px);
|
|
339
|
+
padding: 0 var(--sp-1, 8px);
|
|
340
|
+
border: 0;
|
|
341
|
+
border-radius: var(--r-1, 4px);
|
|
342
|
+
background: none;
|
|
343
|
+
color: var(--text-row, #c9c9ce);
|
|
344
|
+
font: inherit;
|
|
345
|
+
font-size: var(--outliner-secondary-size, 11px);
|
|
346
|
+
text-align: left;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.v2-outliner .hierarchy-context-item:hover,
|
|
350
|
+
.v2-outliner .hierarchy-context-item:focus-visible {
|
|
351
|
+
background: var(--surface-active, #26262a);
|
|
352
|
+
color: var(--text-1, #e4e4e7);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.v2-outliner .hierarchy-context-item.danger {
|
|
356
|
+
color: var(--danger, #e5484d);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.v2-outliner .add-object-item {
|
|
360
|
+
min-height: var(--h-control, 28px);
|
|
361
|
+
padding: 0 var(--sp-1, 8px);
|
|
362
|
+
border: 0;
|
|
363
|
+
border-radius: var(--r-1, 4px);
|
|
364
|
+
background: none;
|
|
365
|
+
color: var(--text-row, #c9c9ce);
|
|
366
|
+
font: inherit;
|
|
367
|
+
font-size: var(--outliner-secondary-size, 11px);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.v2-outliner .add-object-item:hover,
|
|
371
|
+
.v2-outliner .add-object-item:focus-visible {
|
|
372
|
+
background: var(--surface-active, #26262a);
|
|
373
|
+
color: var(--text-1, #e4e4e7);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
.v2-outliner .add-object-heading {
|
|
377
|
+
padding: var(--sp-1, 8px) var(--sp-1, 8px) calc(var(--sp-1, 8px) / 2);
|
|
378
|
+
color: var(--text-3, #636369);
|
|
379
|
+
font-size: var(--outliner-type-size, 10.5px);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
@media (max-width: 700px) {
|
|
383
|
+
.v2-outliner .v2-outliner-type {
|
|
384
|
+
display: none;
|
|
385
|
+
}
|
|
386
|
+
}
|
package/src/hierarchy-panel.jsx
CHANGED
|
@@ -2,8 +2,9 @@ import { useEffect, useMemo, useRef, useState } from "react";
|
|
|
2
2
|
import { ko, isKo } from "./locale.js";
|
|
3
3
|
import { buildHierarchyNodes } from "./hierarchy-model.js";
|
|
4
4
|
import { sceneObjectIdFromHierarchy } from "./scene-objects.js";
|
|
5
|
-
import
|
|
5
|
+
import { CatalogueEntries, displayObjectLabel } from "./object-catalog.jsx";
|
|
6
6
|
import { Dropdown } from "./ui.jsx";
|
|
7
|
+
import "./hierarchy-panel.css";
|
|
7
8
|
|
|
8
9
|
const HIERARCHY_LABELS_KO = {
|
|
9
10
|
"SCENE 01": "장면 01",
|
|
@@ -35,6 +36,8 @@ const HIERARCHY_LABELS_KO = {
|
|
|
35
36
|
Environment: "환경",
|
|
36
37
|
Props: "소품",
|
|
37
38
|
Light: "조명",
|
|
39
|
+
"Scene structure": "장면 구조",
|
|
40
|
+
"Scene hierarchy": "장면 계층",
|
|
38
41
|
};
|
|
39
42
|
|
|
40
43
|
const FALLBACK_SCENES = [{ id: "current-scene", name: "SCENE 01" }];
|
|
@@ -198,6 +201,57 @@ function displayHierarchyLabel(node) {
|
|
|
198
201
|
return isKo ? (HIERARCHY_LABELS_KO[node.label] ?? node.label) : node.label;
|
|
199
202
|
}
|
|
200
203
|
|
|
204
|
+
const OUTLINER_TYPES = {
|
|
205
|
+
folder: "Folder",
|
|
206
|
+
mesh: "Mesh",
|
|
207
|
+
cast: "Cast",
|
|
208
|
+
cam: "Cam",
|
|
209
|
+
light: "Light",
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
function outlinerTypeFor(node) {
|
|
213
|
+
const key = node.kind === "object"
|
|
214
|
+
? "mesh"
|
|
215
|
+
: node.kind === "character" || node.kind === "bone"
|
|
216
|
+
? "cast"
|
|
217
|
+
: node.kind === "camera"
|
|
218
|
+
? "cam"
|
|
219
|
+
: node.kind === "light"
|
|
220
|
+
? "light"
|
|
221
|
+
: "folder";
|
|
222
|
+
const label = key === "folder"
|
|
223
|
+
? ko(OUTLINER_TYPES[key], "폴더")
|
|
224
|
+
: key === "mesh"
|
|
225
|
+
? ko(OUTLINER_TYPES[key], "메시")
|
|
226
|
+
: key === "cast"
|
|
227
|
+
? ko(OUTLINER_TYPES[key], "캐스트")
|
|
228
|
+
: key === "cam"
|
|
229
|
+
? ko(OUTLINER_TYPES[key], "카메라")
|
|
230
|
+
: ko(OUTLINER_TYPES[key], "조명");
|
|
231
|
+
return { key, label };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function OutlinerChip({ type }) {
|
|
235
|
+
return <span className={`v2-outliner-chip ${type}`} aria-hidden="true" />;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function countHierarchyNodes(nodes) {
|
|
239
|
+
return nodes.reduce((count, node) => count + 1 + countHierarchyNodes(node.children ?? []), 0);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function filterHierarchyNodes(nodes, query, showB) {
|
|
243
|
+
const normalized = query.trim().toLocaleLowerCase();
|
|
244
|
+
const filterNode = (node) => {
|
|
245
|
+
if (node.optional === "showB" && !showB) return null;
|
|
246
|
+
if (!normalized) return node;
|
|
247
|
+
const label = displayHierarchyLabel(node).toLocaleLowerCase();
|
|
248
|
+
const children = (node.children ?? []).map(filterNode).filter(Boolean);
|
|
249
|
+
if (!label.includes(normalized) && children.length === 0) return null;
|
|
250
|
+
return children.length ? { ...node, children } : { ...node, children: undefined };
|
|
251
|
+
};
|
|
252
|
+
return nodes.map(filterNode).filter(Boolean);
|
|
253
|
+
}
|
|
254
|
+
|
|
201
255
|
function indexParents(nodes, parent = null, parents = new Map()) {
|
|
202
256
|
for (const node of nodes) {
|
|
203
257
|
if (parent) parents.set(node.id, parent);
|
|
@@ -210,6 +264,15 @@ function indexParents(nodes, parent = null, parents = new Map()) {
|
|
|
210
264
|
* (Rename / Duplicate / Delete / Frame) or the create catalogue, matching
|
|
211
265
|
* Unity's Hierarchy right-click menu (docs/unity-reference.md §9.7).
|
|
212
266
|
* Closes on Escape, on any outside mousedown, and after a pick. */
|
|
267
|
+
function findHierarchyNode(nodes, id) {
|
|
268
|
+
for (const node of nodes ?? []) {
|
|
269
|
+
if (node.id === id) return node;
|
|
270
|
+
const nested = findHierarchyNode(node.children, id);
|
|
271
|
+
if (nested) return nested;
|
|
272
|
+
}
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
|
|
213
276
|
function RowContextMenu({ menu, onClose, onAction, onAddObject }) {
|
|
214
277
|
const rootRef = useRef(null);
|
|
215
278
|
// Deleting a scene document is not undoable, so Delete arms first and only
|
|
@@ -275,8 +338,15 @@ function RowContextMenu({ menu, onClose, onAction, onAddObject }) {
|
|
|
275
338
|
{ko("+ New scene", "+ 새 장면")}
|
|
276
339
|
</button>
|
|
277
340
|
</>
|
|
341
|
+
) : menu.kind === "character" ? (
|
|
342
|
+
<button type="button" role="menuitem" className="hierarchy-context-item" onClick={() => onAction("visibility", menu.id)}>
|
|
343
|
+
{menu.hidden ? ko("Show", "표시") : ko("Hide", "숨기기")}
|
|
344
|
+
</button>
|
|
278
345
|
) : menu.kind === "object" ? (
|
|
279
346
|
<>
|
|
347
|
+
<button type="button" role="menuitem" className="hierarchy-context-item" onClick={() => onAction("visibility", menu.id)}>
|
|
348
|
+
{menu.hidden ? ko("Show", "표시") : ko("Hide", "숨기기")}
|
|
349
|
+
</button>
|
|
280
350
|
<button type="button" role="menuitem" className="hierarchy-context-item" onClick={() => onAction("rename", menu.id)}>
|
|
281
351
|
{ko("Rename", "이름 바꾸기")}
|
|
282
352
|
</button>
|
|
@@ -310,6 +380,7 @@ function TreeRow({
|
|
|
310
380
|
onRenameCommit,
|
|
311
381
|
onRenameCancel,
|
|
312
382
|
onRowContextMenu,
|
|
383
|
+
onToggleHidden,
|
|
313
384
|
onRenameStart,
|
|
314
385
|
// The scene root row hands its name to the pill in `rowExtra`; printing it
|
|
315
386
|
// twice on one row is the duplication this panel just removed.
|
|
@@ -361,6 +432,14 @@ function TreeRow({
|
|
|
361
432
|
...(draggableRow
|
|
362
433
|
? {
|
|
363
434
|
onDragStart: (event) => {
|
|
435
|
+
// The eye and the fold caret sit inside the draggable row.
|
|
436
|
+
// A press on either must not start a move, or the click is
|
|
437
|
+
// swallowed and a small slip reparents the row.
|
|
438
|
+
if (event.target instanceof Element && event.target.closest(".hierarchy-eye, .hierarchy-toggle")) {
|
|
439
|
+
event.preventDefault();
|
|
440
|
+
event.stopPropagation();
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
364
443
|
event.stopPropagation();
|
|
365
444
|
event.dataTransfer.setData(HIERARCHY_DRAG_MIME, node.id);
|
|
366
445
|
event.dataTransfer.effectAllowed = "move";
|
|
@@ -410,6 +489,7 @@ function TreeRow({
|
|
|
410
489
|
// only exists when the caller asked for reparenting.
|
|
411
490
|
const dropEvents = rowDrag || drop || null;
|
|
412
491
|
const label = displayHierarchyLabel(node);
|
|
492
|
+
const outlinerType = outlinerTypeFor(node);
|
|
413
493
|
const rowWrapRef = useRef(null);
|
|
414
494
|
const inputRef = useRef(null);
|
|
415
495
|
// A commit/cancel may race the blur that follows the input unmounting;
|
|
@@ -441,6 +521,7 @@ function TreeRow({
|
|
|
441
521
|
className={"hierarchy-row-wrap" + (selectedId === node.id ? " selected" : "")}
|
|
442
522
|
style={{ "--hierarchy-depth": depth }}
|
|
443
523
|
data-node-id={node.id}
|
|
524
|
+
data-own-hidden={node.hidden ? "true" : undefined}
|
|
444
525
|
data-drop={drop || rowDropTarget ? (dropOver || rowDropOver ? "over" : "target") : undefined}
|
|
445
526
|
draggable={draggableRow || undefined}
|
|
446
527
|
{...(dropEvents ?? {})}
|
|
@@ -469,7 +550,7 @@ function TreeRow({
|
|
|
469
550
|
// blur commits (docs/unity-reference.md §9.7). A div, not the
|
|
470
551
|
// row button — an input inside a button is invalid HTML.
|
|
471
552
|
<div className={"hierarchy-row" + (touched ? " agent-touched" : "")}>
|
|
472
|
-
<
|
|
553
|
+
<OutlinerChip type={outlinerType.key} />
|
|
473
554
|
<input
|
|
474
555
|
ref={inputRef}
|
|
475
556
|
className="hierarchy-rename-input"
|
|
@@ -498,10 +579,31 @@ function TreeRow({
|
|
|
498
579
|
onClick={() => onSelect(node.id)}
|
|
499
580
|
onDoubleClick={onRenameStart ?? undefined}
|
|
500
581
|
>
|
|
501
|
-
<
|
|
582
|
+
<OutlinerChip type={outlinerType.key} />
|
|
502
583
|
{showLabel && <span className="hierarchy-label">{label}</span>}
|
|
503
584
|
{status && <span className="hierarchy-status">{status}</span>}
|
|
504
585
|
{badge !== null && badge !== undefined && badge !== 0 && <span className="hierarchy-badge">{badge}</span>}
|
|
586
|
+
<span className="v2-outliner-type">{outlinerType.label}</span>
|
|
587
|
+
</button>
|
|
588
|
+
)}
|
|
589
|
+
{(node.kind === "object" || node.kind === "character") && onToggleHidden && (
|
|
590
|
+
<button
|
|
591
|
+
type="button"
|
|
592
|
+
className="hierarchy-eye"
|
|
593
|
+
data-hidden={node.hidden ? "true" : undefined}
|
|
594
|
+
aria-pressed={node.hidden === true}
|
|
595
|
+
aria-label={node.hidden ? (isKo ? `${label} 표시` : `Show ${label}`) : (isKo ? `${label} 숨기기` : `Hide ${label}`)}
|
|
596
|
+
onClick={(event) => {
|
|
597
|
+
event.preventDefault();
|
|
598
|
+
event.stopPropagation();
|
|
599
|
+
onToggleHidden(node.id);
|
|
600
|
+
}}
|
|
601
|
+
>
|
|
602
|
+
<svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true">
|
|
603
|
+
<path d="M1.5 8s2.5-4 6.5-4 6.5 4 6.5 4-2.5 4-6.5 4S1.5 8 1.5 8z" fill="none" stroke="currentColor" strokeWidth="1.3" />
|
|
604
|
+
<circle cx="8" cy="8" r="1.7" fill="currentColor" />
|
|
605
|
+
{node.hidden && <path d="M3 13 13 3" stroke="currentColor" strokeWidth="1.3" />}
|
|
606
|
+
</svg>
|
|
505
607
|
</button>
|
|
506
608
|
)}
|
|
507
609
|
{rowExtra}
|
|
@@ -525,6 +627,7 @@ export default function HierarchyPanel({
|
|
|
525
627
|
onDuplicateObject,
|
|
526
628
|
onDeleteObject,
|
|
527
629
|
onFrameObject,
|
|
630
|
+
onToggleHidden,
|
|
528
631
|
propsDrop = null,
|
|
529
632
|
reparent = null,
|
|
530
633
|
// Hierarchy ids an agent receipt just changed. The host owns how long they
|
|
@@ -539,6 +642,7 @@ export default function HierarchyPanel({
|
|
|
539
642
|
onSceneDelete,
|
|
540
643
|
}) {
|
|
541
644
|
const [expanded, setExpanded] = useState(() => new Set(["shot", "characterA"]));
|
|
645
|
+
const [search, setSearch] = useState("");
|
|
542
646
|
const [contextMenu, setContextMenu] = useState(null);
|
|
543
647
|
// Row currently in in-place rename. The panel owns it: F2/Return and the
|
|
544
648
|
// row context menu are the only ways in, so app state stays out of it.
|
|
@@ -574,6 +678,14 @@ export default function HierarchyPanel({
|
|
|
574
678
|
}));
|
|
575
679
|
}, [activeSceneName, sceneObjects, characters]);
|
|
576
680
|
const parents = useMemo(() => indexParents(hierarchyNodes), [hierarchyNodes]);
|
|
681
|
+
const filteredHierarchyNodes = useMemo(
|
|
682
|
+
() => filterHierarchyNodes(hierarchyNodes, search, showB),
|
|
683
|
+
[hierarchyNodes, search, showB],
|
|
684
|
+
);
|
|
685
|
+
const outlinerCount = useMemo(
|
|
686
|
+
() => countHierarchyNodes(filteredHierarchyNodes),
|
|
687
|
+
[filteredHierarchyNodes],
|
|
688
|
+
);
|
|
577
689
|
|
|
578
690
|
useEffect(() => {
|
|
579
691
|
setExpanded((current) => {
|
|
@@ -650,8 +762,16 @@ export default function HierarchyPanel({
|
|
|
650
762
|
const openRowMenu = (event, id) => {
|
|
651
763
|
event.preventDefault();
|
|
652
764
|
event.stopPropagation(); // a row pick must not also open the create menu
|
|
653
|
-
|
|
654
|
-
|
|
765
|
+
const node = findHierarchyNode(hierarchyNodes, id);
|
|
766
|
+
if (node?.kind === "object" || node?.kind === "character") {
|
|
767
|
+
setContextMenu({
|
|
768
|
+
x: event.clientX,
|
|
769
|
+
y: event.clientY,
|
|
770
|
+
height: node.kind === "object" ? 180 : 44,
|
|
771
|
+
kind: node.kind,
|
|
772
|
+
id,
|
|
773
|
+
hidden: node.hidden === true,
|
|
774
|
+
});
|
|
655
775
|
} else if (id === SCENE_ROOT_ID) {
|
|
656
776
|
// The root row is the scene document: its own verbs, never the
|
|
657
777
|
// Add-Object catalogue.
|
|
@@ -700,6 +820,10 @@ export default function HierarchyPanel({
|
|
|
700
820
|
onSceneCreate?.();
|
|
701
821
|
return;
|
|
702
822
|
}
|
|
823
|
+
if (action === "visibility") {
|
|
824
|
+
onToggleHidden?.(hierarchyId);
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
703
827
|
const objectId = sceneObjectIdFromHierarchy(hierarchyId);
|
|
704
828
|
if (!objectId) return;
|
|
705
829
|
if (action === "duplicate") onDuplicateObject?.(objectId);
|
|
@@ -736,9 +860,9 @@ export default function HierarchyPanel({
|
|
|
736
860
|
nodes.flatMap((node) => {
|
|
737
861
|
if (node.optional === "showB" && !showB) return [];
|
|
738
862
|
const sceneRoot = node.id === SCENE_ROOT_ID;
|
|
739
|
-
//
|
|
740
|
-
//
|
|
741
|
-
const open = sceneRoot || expanded.has(node.id);
|
|
863
|
+
// Search opens only the filtered branches. The stored expansion set is
|
|
864
|
+
// left untouched so clearing the query restores the author's tree.
|
|
865
|
+
const open = sceneRoot || expanded.has(node.id) || Boolean(search.trim());
|
|
742
866
|
const editing = editingId === node.id;
|
|
743
867
|
return [
|
|
744
868
|
<TreeRow
|
|
@@ -756,6 +880,7 @@ export default function HierarchyPanel({
|
|
|
756
880
|
onRenameCommit={(name) => commitRename(node.id, name)}
|
|
757
881
|
onRenameCancel={cancelRename}
|
|
758
882
|
onRowContextMenu={openRowMenu}
|
|
883
|
+
onToggleHidden={onToggleHidden}
|
|
759
884
|
// The root row names the scene document, and a double-click on a
|
|
760
885
|
// document name is where every file browser puts rename.
|
|
761
886
|
onRenameStart={sceneRoot ? () => setEditingId(node.id) : null}
|
|
@@ -786,22 +911,26 @@ export default function HierarchyPanel({
|
|
|
786
911
|
});
|
|
787
912
|
|
|
788
913
|
return (
|
|
789
|
-
<section className="hierarchy-pane" aria-label={ko("
|
|
914
|
+
<section className="hierarchy-pane v2-outliner" aria-label={ko("Outliner", "아웃라이너")}>
|
|
790
915
|
<div className="hierarchy-heading">
|
|
791
|
-
<
|
|
792
|
-
|
|
793
|
-
<strong>{ko("Scene structure", "장면 구조")}</strong>
|
|
794
|
-
</div>
|
|
795
|
-
<span className="hierarchy-frame-status">{motionFrames ? (isKo ? `${motionFrames}프레임` : `${motionFrames} frames`) : ko("Blocking", "블로킹")}</span>
|
|
916
|
+
<strong>{ko("Outliner", "아웃라이너")}</strong>
|
|
917
|
+
<span className="v2-outliner-count" data-testid="outliner-count">{outlinerCount}</span>
|
|
796
918
|
</div>
|
|
797
|
-
|
|
798
|
-
<
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
919
|
+
<label className="v2-outliner-search-wrap">
|
|
920
|
+
<span className="v2-outliner-search-icon" aria-hidden="true">⌕</span>
|
|
921
|
+
<input
|
|
922
|
+
className="v2-outliner-search"
|
|
923
|
+
type="search"
|
|
924
|
+
value={search}
|
|
925
|
+
placeholder={ko("Search", "검색")}
|
|
926
|
+
aria-label={ko("Search Outliner", "아웃라이너 검색")}
|
|
927
|
+
onChange={(event) => setSearch(event.target.value)}
|
|
928
|
+
/>
|
|
929
|
+
</label>
|
|
803
930
|
<div className="hierarchy-tree" role="tree" ref={treeRef} onKeyDown={onTreeKeyDown} onContextMenu={openCreateMenu}>
|
|
804
|
-
{renderNodes(
|
|
931
|
+
{filteredHierarchyNodes.length > 0 ? renderNodes(filteredHierarchyNodes) : (
|
|
932
|
+
<div className="v2-outliner-empty" role="status">{ko("No matches", "일치하는 항목 없음")}</div>
|
|
933
|
+
)}
|
|
805
934
|
</div>
|
|
806
935
|
{sceneHint && (
|
|
807
936
|
<p className="hierarchy-scene-hint" role="status">{sceneHint}</p>
|