pixifact-cli 0.11.2 → 0.13.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/README.md +7 -2
- package/editor/assets/browserAll-DDpbpxbk.js +1 -0
- package/editor/assets/capture--KpnplUn.js +1 -0
- package/editor/assets/captureCompilerScenePreview-CksQqv9T.js +1126 -0
- package/editor/assets/editor-CdMzlphb.js +5 -0
- package/editor/assets/editor-rwswALuA.css +1 -0
- package/editor/assets/init-Cun-jBBV.js +1 -0
- package/editor/assets/webworkerAll-CpwJPQ3Y.js +1 -0
- package/editor/capture.html +11 -0
- package/editor/index.html +3 -2
- package/package.json +3 -2
- package/src/editorServer.ts +25 -6
- package/src/editorSession.ts +26 -6
- package/src/pixifact-cli.ts +25 -0
- package/src/sceneScreenshot.ts +103 -0
- package/editor/assets/browserAll-BsCeISKq.js +0 -1
- package/editor/assets/index-B_YR_yGH.css +0 -1
- package/editor/assets/index-CSHl3qLL.js +0 -1130
- package/editor/assets/init-CKNzy2w2.js +0 -1
- package/editor/assets/webworkerAll-jXukHPxP.js +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{color:#d9dee7;font-synthesis:none;text-rendering:optimizelegibility;--bg:#111318;--panel:#171a20;--panel-raised:#1d2129;--canvas:#0d0f13;--line:#2a2f39;--line-strong:#39404d;--text:#d9dee7;--muted:#818997;--faint:#596171;--focus:#4c8dff;--focus-soft:#213a63;--success:#51b785;--warning:#d4a64f;--danger:#e36f6f;--control:#11141a;background:#111318;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}*{box-sizing:border-box}html,body,#app{width:100%;height:100%;margin:0;overflow:hidden}button,input,select{color:inherit;font:inherit;letter-spacing:0}button{border:0}.editor-shell{background:var(--bg);grid-template-rows:42px 34px minmax(0,1fr);width:100%;height:100%;display:grid}.editor-session-standby{background:var(--bg);place-items:center;width:100%;height:100%;display:grid}.editor-session-standby>section{gap:18px;width:min(420px,100vw - 40px);display:grid}.standby-product{color:var(--faint);font-size:10px;font-weight:750}.editor-session-standby h1{color:var(--text);letter-spacing:0;margin:0;font-size:18px}.standby-context{border-top:1px solid var(--line);border-bottom:1px solid var(--line);grid-template-columns:76px minmax(0,1fr);gap:8px 12px;padding:14px 0;display:grid}.standby-context span{color:var(--muted);font-size:12px}.standby-context strong{min-width:0;color:var(--text);text-overflow:ellipsis;white-space:nowrap;font-size:12px;font-weight:500;overflow:hidden}.takeover-button{background:var(--focus);color:#fff;cursor:pointer;border:1px solid #659cff;border-radius:4px;justify-self:start;min-width:104px;height:32px;padding:0 14px;font-size:12px;font-weight:650}.takeover-button:hover:not(:disabled){background:#5a96ff}.standby-note,.standby-error{margin:-6px 0 0;font-size:11px;line-height:1.6}.standby-note{color:var(--muted)}.standby-error{color:var(--danger)}.topbar{border-bottom:1px solid var(--line);background:#14171c;grid-template-columns:232px minmax(0,1fr) auto 112px;align-items:center;min-width:0;display:grid}.brand{border-right:1px solid var(--line);color:#f1f4f8;align-items:center;height:100%;padding:0 14px;font-size:11px;font-weight:750;display:flex}.scene-identity{align-items:baseline;gap:10px;min-width:0;padding:0 14px;display:flex}.scene-identity strong{flex:none;font-size:13px}.scene-identity span{min-width:0;color:var(--muted);text-overflow:ellipsis;white-space:nowrap;font-size:11px;overflow:hidden}.history-tools{align-items:center;gap:2px;padding-right:10px;display:flex}.history-tools button,.reset-button{width:28px;height:28px;color:var(--muted);cursor:pointer;background:0 0;border-radius:4px;place-items:center;padding:0;display:inline-grid}.history-tools button:hover:not(:disabled),.reset-button:hover:not(:disabled){background:var(--panel-raised);color:var(--text)}.history-tools .save-button{width:auto;color:var(--text);gap:5px;padding:0 8px;font-size:11px;display:inline-flex}button:disabled{opacity:.35;cursor:default}.sync-state{border-left:1px solid var(--line);height:100%;color:var(--muted);align-items:center;gap:7px;padding:0 13px;font-size:11px;display:flex}.sync-state span{background:var(--success);border-radius:50%;width:7px;height:7px}.sync-state[data-state=saving] span,.sync-state[data-state=unsaved] span{background:var(--warning)}.sync-state[data-state=conflict] span,.sync-state[data-state=error] span{background:var(--danger)}.scene-tabs{border-bottom:1px solid var(--line);background:#14171c;min-width:0;display:flex;overflow-x:auto}.scene-tab{border-right:1px solid var(--line);max-width:220px;color:var(--muted);flex:none;align-items:center;display:flex}.scene-tab.active{border-bottom:2px solid var(--focus);background:var(--panel);color:var(--text)}.scene-tab-activate{min-width:0;height:31px;color:inherit;cursor:pointer;background:0 0;align-items:center;gap:7px;padding:0 4px 0 12px;font-size:11px;display:flex}.scene-tab-activate>span:first-child{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.scene-tab-status{color:var(--warning);font-size:10px}.scene-tab-status.error{color:var(--danger);font-weight:800}.scene-tab-close{height:25px;color:inherit;cursor:pointer;background:0 0;border-radius:4px;flex:0 0 25px;place-items:center;margin-right:4px;display:grid}.scene-tab-close:hover:not(:disabled){background:var(--line-strong)}.editor-modal-backdrop{z-index:30;background:#07090bcc;place-items:center;display:grid;position:fixed;inset:0}.editor-modal{border:1px solid var(--line-strong);background:var(--panel);width:min(440px,100vw - 32px);color:var(--text);border-radius:6px;padding:20px}.editor-modal h2{margin:0 0 10px;font-size:15px}.editor-modal p{color:var(--muted);margin:0 0 20px;font-size:12px;line-height:1.5}.editor-modal-actions{justify-content:flex-end;gap:8px;display:flex}.editor-modal-actions button{border:1px solid var(--line-strong);background:var(--panel-raised);color:var(--text);cursor:pointer;border-radius:4px;padding:7px 10px;font-size:11px}.editor-modal-actions button.primary{border-color:var(--focus);background:var(--focus);color:#fff}.conflict-modal{width:min(920px,100vw - 32px)}.conflict-sources{grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;margin-bottom:20px;display:grid}.conflict-sources strong{margin-bottom:7px;font-size:11px;display:block}.conflict-sources pre{border:1px solid var(--line);background:var(--control);height:min(42vh,340px);color:var(--text);margin:0;padding:10px;font-size:11px;overflow:auto}.workspace{grid-template-columns:minmax(190px,232px) minmax(360px,1fr) minmax(270px,296px);min-width:0;min-height:0;display:grid}.left-panel,.right-panel,.canvas-panel{min-width:0;min-height:0}.left-panel,.right-panel{background:var(--panel)}.left-panel{border-right:1px solid var(--line);flex-direction:column;display:flex}.right-panel{border-left:1px solid var(--line);overflow:auto}.left-tabs{flex:1;grid-template-rows:34px minmax(0,1fr);min-height:0;display:grid}.left-panel-footer{border-top:1px solid var(--line);flex:0 0 44px;align-items:center;padding:0 8px;display:flex}.settings-trigger{width:100%;height:32px;color:var(--muted);text-align:left;cursor:pointer;background:0 0;border-radius:4px;align-items:center;gap:9px;padding:0 8px;font-size:12px;display:flex}.settings-trigger:hover,.settings-trigger[data-state=open]{background:var(--panel-raised);color:var(--text)}.editor-settings-popover{z-index:20;border:1px solid var(--line-strong);background:var(--panel-raised);width:248px;color:var(--text);border-radius:7px;padding:12px;box-shadow:0 10px 30px #0007}.settings-heading{border-bottom:1px solid var(--line-strong);padding:0 2px 10px;font-size:12px;font-weight:700}.settings-option{cursor:pointer;justify-content:space-between;align-items:center;gap:14px;padding:12px 2px 2px;display:flex}.settings-option span{gap:4px;display:grid}.settings-option strong{font-size:12px;font-weight:600}.settings-option small{color:var(--muted);font-size:11px}.settings-option input{accent-color:var(--focus);cursor:pointer}.tab-list{border-bottom:1px solid var(--line);grid-template-columns:1fr 1fr;display:grid}.tab-trigger{color:var(--muted);cursor:pointer;background:0 0;font-size:11px;position:relative}.tab-trigger[data-state=active]{color:var(--text)}.tab-trigger[data-state=active]:after{background:var(--focus);content:"";height:2px;position:absolute;bottom:-1px;left:12px;right:12px}.tab-content{outline:none;min-height:0;overflow:auto}.panel-title,.inspector-section-title,.asset-group-label{color:var(--muted);font-size:10px;font-weight:700}.panel-title{border-bottom:1px solid var(--line);align-items:center;height:34px;padding:0 12px;display:flex}.tree-list{margin:0;padding:0;list-style:none}.hierarchy-panel{min-height:100%;padding:0 0 6px}.hierarchy-toolbar{z-index:2;border-bottom:1px solid var(--line);background:var(--panel);grid-template-columns:minmax(0,1fr) 27px 27px 27px;align-items:center;gap:2px;height:35px;padding:4px 6px;display:grid;position:sticky;top:0}.hierarchy-toolbar select{border:1px solid var(--line-strong);background:var(--control);width:100%;min-width:0;height:25px;color:var(--text);border-radius:3px;outline:none;padding:0 5px;font-size:10px}.hierarchy-toolbar select:focus{border-color:var(--focus)}.hierarchy-toolbar button{width:27px;height:27px;color:var(--muted);cursor:pointer;background:0 0;border-radius:3px;place-items:center;padding:0;display:grid}.hierarchy-toolbar button:hover:not(:disabled){background:var(--panel-raised);color:var(--text)}.tree-row{width:100%;height:27px;color:var(--text);text-align:left;cursor:default;background:0 0;grid-template-columns:15px 16px minmax(0,1fr) auto;align-items:center;gap:4px;padding-top:0;padding-bottom:0;padding-right:9px;display:grid;position:relative}.hierarchy-panel.is-dragging .tree-row{cursor:grabbing}.hierarchy-panel.is-asset-dragging .tree-row{cursor:copy}.tree-row.drop-inside{box-shadow:inset 0 0 0 1px var(--focus);background:#26364d}.tree-row.drop-before:before,.tree-row.drop-after:after{z-index:1;background:var(--focus);content:"";pointer-events:none;height:2px;position:absolute;left:5px;right:5px}.tree-row.drop-before:before{top:-1px}.tree-row.drop-after:after{bottom:-1px}.tree-row:hover{background:#20242c}.tree-row.selected{background:var(--focus-soft);color:#eef5ff}.tree-disclosure{width:15px;height:20px;color:var(--muted);place-items:center;display:grid}.tree-disclosure.empty{visibility:hidden}.tree-icon{color:#8da4c7}.tree-label{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-size:12px;overflow:hidden}.tree-row small{color:var(--faint);font-size:9px}.scene-root{cursor:default;padding-left:8px;font-weight:650}.scene-mark{color:#a9c8f8;border:1px solid #637a9d;border-radius:3px;place-items:center;width:14px;height:14px;font-size:8px;display:grid}.asset-list{padding:7px 0 14px}.asset-row{width:100%;height:29px;color:var(--text);text-align:left;background:0 0;grid-template-columns:14px 18px minmax(0,1fr);align-items:center;gap:5px;padding:0 12px;display:grid}button.asset-row{cursor:pointer}.asset-directory{color:var(--muted)}.asset-directory svg:nth-of-type(2){color:#a9c8f8}.asset-row[data-asset-draggable=true]{cursor:grab;-webkit-user-select:none;user-select:none}.asset-row:hover,.asset-row.selected{background:#20242c}.asset-row.selected{color:#a9ccff}.asset-row.focused{box-shadow:inset 2px 0 var(--focus);background:#2a3a52}.asset-row span{text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.asset-row .asset-row-spacer{width:14px}.asset-thumbnail{object-fit:contain;pointer-events:none;border-radius:2px;width:18px;height:18px}.asset-row.asset-draggable{color:var(--muted)}.asset-empty,.panel-empty{color:var(--faint);padding:14px 12px;font-size:12px}.panel-empty.compact{padding-top:18px}.canvas-panel{background:var(--canvas);position:relative}.scene-canvas-host{-webkit-user-select:none;user-select:none;position:absolute;inset:0;overflow:hidden}.scene-canvas-host.is-asset-drop-target:after{z-index:2;border:1px solid var(--focus);content:"";pointer-events:none;position:absolute;inset:3px}.canvas-grid{pointer-events:none;background-image:linear-gradient(#20242b 1px,#0000 1px),linear-gradient(90deg,#20242b 1px,#0000 1px);background-size:24px 24px;position:absolute;inset:0}.scene-canvas{display:block;position:absolute;inset:0}.scene-canvas-host.is-pan-ready .scene-canvas{cursor:grab!important}.scene-canvas-host.is-panning .scene-canvas{cursor:grabbing!important}.canvas-tools{z-index:3;position:absolute;top:10px;right:10px}.canvas-mode-tools{gap:4px;display:flex;left:10px;right:auto}.canvas-tools button{border:1px solid var(--line);background:var(--panel);width:28px;height:28px;color:var(--muted);cursor:pointer;border-radius:4px;place-items:center;padding:0;display:inline-grid}.canvas-tools button:hover:not(:disabled){border-color:var(--line-strong);background:var(--panel-raised);color:var(--text)}.canvas-tools button[aria-pressed=true]{border-color:var(--focus);background:var(--focus-soft);color:var(--text)}.canvas-status{border:1px solid var(--line);max-width:min(520px,100% - 48px);color:var(--muted);background:#15181e;border-radius:4px;padding:7px 10px;font-size:12px;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}.inspector-panel{min-height:0}.inspector-heading{border-bottom:1px solid var(--line);justify-content:space-between;align-items:center;min-height:54px;padding:8px 12px;display:flex}.inspector-heading div{gap:3px;min-width:0;display:grid}.inspector-heading strong{text-overflow:ellipsis;white-space:nowrap;font-size:13px;overflow:hidden}.inspector-heading small{color:var(--muted);font-size:10px}.inspector-section-title{border-bottom:1px solid var(--line);height:28px;padding:9px 12px 0}.property-row{border-bottom:1px solid #21252d;grid-template-columns:minmax(0,1fr);min-height:34px;padding:3px 8px 3px 12px;display:grid}.property-row.is-paired{grid-template-columns:repeat(2,minmax(0,1fr));gap:6px}.property-field{grid-template-columns:minmax(70px,.8fr) minmax(120px,1.4fr);align-items:center;min-width:0;display:grid}.property-row.is-paired .property-field{grid-template-columns:max-content minmax(0,1fr);align-items:center;gap:4px}.property-row.is-paired .property-field>span{font-size:10px}.property-row.is-paired .property-control{grid-template-columns:minmax(0,1fr) 20px;gap:2px}.property-row.is-paired .reset-button{width:20px}.property-field>span{color:var(--text);text-overflow:ellipsis;white-space:nowrap;font-size:11px;overflow:hidden}.property-field>span.inherited{color:var(--muted)}.property-field.is-image-drop-candidate{box-shadow:inset 2px 0 var(--success);cursor:copy;background:#162b24}.property-field.is-image-drop-candidate:hover{background:#1b372d}.property-field.is-image-drop-candidate .property-control input[type=text]{border-color:var(--success)}.property-control{grid-template-columns:minmax(0,1fr) 24px;align-items:start;gap:3px;display:grid}.node-id-control{grid-template-columns:minmax(0,1fr)}.property-value{gap:3px;min-width:0;display:grid}.property-control input[type=text],.property-control input[type=number],.property-control select{border:1px solid var(--line-strong);background:var(--control);width:100%;min-width:0;height:25px;color:var(--text);border-radius:3px;outline:none;padding:0 7px;font-size:11px}.property-control input.resource-reference{cursor:alias}.property-control input:focus,.property-control select:focus{border-color:var(--focus)}.property-control input[type=text].is-invalid,.property-control input[type=text].is-invalid:focus{border-color:var(--danger)}.property-control input:disabled,.property-control select:disabled{color:#9ca6b5;cursor:default;opacity:1;background:#1b1f26;border-color:#303640}.property-control input[type=checkbox]{width:15px;height:15px;accent-color:var(--focus)}.property-control input[type=color]{border:1px solid var(--line-strong);background:var(--control);border-radius:3px;width:34px;height:24px;padding:2px}.reset-button{width:24px;height:24px}.binding-source{color:#8eb8e8;text-overflow:ellipsis;white-space:nowrap;align-items:center;gap:4px;min-width:0;font-size:10px;display:flex;overflow:hidden}.inline-error,.global-error{color:#ffd1d1;margin:0;font-size:11px}.inline-error{background:#2d1b20;border-bottom:1px solid #4f292f;padding:9px 12px}.global-error{background:#321e23;border:1px solid #66343c;border-radius:4px;max-width:min(520px,100vw - 24px);padding:9px 11px;position:fixed;bottom:12px;right:12px}@media (width<=860px){.workspace{grid-template-columns:190px minmax(320px,1fr) 270px}.topbar{grid-template-columns:190px minmax(0,1fr) auto 100px}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{E as e,_ as t,g as n,v as r}from"./captureCompilerScenePreview-CksQqv9T.js";e.add(n,r),e.add(t);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./captureCompilerScenePreview-CksQqv9T.js";import"./init-Cun-jBBV.js";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<title>Pixifact Scene Capture</title>
|
|
6
|
+
<script type="module" crossorigin src="/assets/capture--KpnplUn.js"></script>
|
|
7
|
+
<link rel="modulepreload" crossorigin href="/assets/captureCompilerScenePreview-CksQqv9T.js">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
</body>
|
|
11
|
+
</html>
|
package/editor/index.html
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Pixifact Editor</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/
|
|
8
|
-
<link rel="
|
|
7
|
+
<script type="module" crossorigin src="/assets/editor-CdMzlphb.js"></script>
|
|
8
|
+
<link rel="modulepreload" crossorigin href="/assets/captureCompilerScenePreview-CksQqv9T.js">
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/editor-rwswALuA.css">
|
|
9
10
|
</head>
|
|
10
11
|
<body>
|
|
11
12
|
<div id="app"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pixifact-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Pixifact Scene 自动化 CLI。",
|
|
5
5
|
"packageManager": "bun@1.3.13",
|
|
6
6
|
"type": "module",
|
|
@@ -32,7 +32,8 @@
|
|
|
32
32
|
"start": "bun src/pixifact-cli.ts"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"pixifact": "^0.
|
|
35
|
+
"pixifact": "^0.13.0",
|
|
36
|
+
"playwright": "1.58.2",
|
|
36
37
|
"vite": "^8.0.10"
|
|
37
38
|
}
|
|
38
39
|
}
|
package/src/editorServer.ts
CHANGED
|
@@ -38,6 +38,9 @@ interface EditorProjectFile {
|
|
|
38
38
|
|
|
39
39
|
interface EditorUiState {
|
|
40
40
|
assetTreeExpandedDirectories?: string[];
|
|
41
|
+
autoSave: boolean;
|
|
42
|
+
openScenePaths?: string[];
|
|
43
|
+
activeScenePath?: string;
|
|
41
44
|
}
|
|
42
45
|
|
|
43
46
|
interface BunServer {
|
|
@@ -176,16 +179,32 @@ function parseEditorUiState(value: unknown): EditorUiState {
|
|
|
176
179
|
throw new Error('Editor UI state must be a JSON object.');
|
|
177
180
|
}
|
|
178
181
|
const directories = (value as Record<string, unknown>).assetTreeExpandedDirectories;
|
|
179
|
-
|
|
180
|
-
|
|
182
|
+
const autoSave = (value as Record<string, unknown>).autoSave;
|
|
183
|
+
const openScenePaths = (value as Record<string, unknown>).openScenePaths;
|
|
184
|
+
const activeScenePath = (value as Record<string, unknown>).activeScenePath;
|
|
185
|
+
if (directories !== undefined && (!Array.isArray(directories) || directories.some((directory) => typeof directory !== 'string'))) {
|
|
181
186
|
throw new Error('Editor UI state assetTreeExpandedDirectories must be a string array.');
|
|
182
187
|
}
|
|
183
|
-
|
|
188
|
+
if (autoSave !== undefined && typeof autoSave !== 'boolean') {
|
|
189
|
+
throw new Error('Editor UI state autoSave must be a boolean.');
|
|
190
|
+
}
|
|
191
|
+
if (openScenePaths !== undefined && (!Array.isArray(openScenePaths) || openScenePaths.some((scenePath) => typeof scenePath !== 'string'))) {
|
|
192
|
+
throw new Error('Editor UI state openScenePaths must be a string array.');
|
|
193
|
+
}
|
|
194
|
+
if (activeScenePath !== undefined && typeof activeScenePath !== 'string') {
|
|
195
|
+
throw new Error('Editor UI state activeScenePath must be a string.');
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
...(directories === undefined ? {} : { assetTreeExpandedDirectories: directories }),
|
|
199
|
+
autoSave: autoSave ?? false,
|
|
200
|
+
...(openScenePaths === undefined ? {} : { openScenePaths }),
|
|
201
|
+
...(activeScenePath === undefined ? {} : { activeScenePath }),
|
|
202
|
+
};
|
|
184
203
|
}
|
|
185
204
|
|
|
186
205
|
function readEditorUiState(projectRoot: string): EditorUiState {
|
|
187
206
|
const statePath = editorUiStatePath(projectRoot);
|
|
188
|
-
if (!fs.existsSync(statePath)) return {};
|
|
207
|
+
if (!fs.existsSync(statePath)) return { autoSave: false };
|
|
189
208
|
return parseEditorUiState(JSON.parse(fs.readFileSync(statePath, 'utf8')));
|
|
190
209
|
}
|
|
191
210
|
|
|
@@ -244,8 +263,8 @@ export function createEditorProjectService(options: EditorProjectServiceOptions)
|
|
|
244
263
|
if (url.pathname === '/api/editor-ui-state' && request.method === 'PUT') {
|
|
245
264
|
const body = await readJsonBody(request);
|
|
246
265
|
const state = parseEditorUiState(body);
|
|
247
|
-
if (!state.assetTreeExpandedDirectories) {
|
|
248
|
-
throw new EditorRequestError(400, 'Editor UI state requires assetTreeExpandedDirectories.');
|
|
266
|
+
if (!state.assetTreeExpandedDirectories || typeof body.autoSave !== 'boolean') {
|
|
267
|
+
throw new EditorRequestError(400, 'Editor UI state requires assetTreeExpandedDirectories and autoSave.');
|
|
249
268
|
}
|
|
250
269
|
return jsonResponse(writeEditorUiState(projectRoot, state));
|
|
251
270
|
}
|
package/src/editorSession.ts
CHANGED
|
@@ -4,9 +4,9 @@ import os from 'node:os';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { parseSceneTemplate } from 'pixifact/compiler';
|
|
6
6
|
|
|
7
|
-
export const editorSessionProtocolVersion =
|
|
7
|
+
export const editorSessionProtocolVersion = 4;
|
|
8
8
|
|
|
9
|
-
export type EditorContextSyncState = 'synced' | 'saving' | 'conflict' | 'error';
|
|
9
|
+
export type EditorContextSyncState = 'synced' | 'unsaved' | 'saving' | 'conflict' | 'error';
|
|
10
10
|
export type EditorPreviewState = 'loading' | 'ready' | 'error';
|
|
11
11
|
|
|
12
12
|
export type EditorNodeContext =
|
|
@@ -47,6 +47,7 @@ export interface EditorBrowserContext {
|
|
|
47
47
|
syncState: EditorContextSyncState;
|
|
48
48
|
};
|
|
49
49
|
selection: EditorSelectionContext;
|
|
50
|
+
openScenes: Array<{ path: string; syncState: EditorContextSyncState }>;
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
export interface EditorSessionResumeState {
|
|
@@ -152,18 +153,23 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
152
153
|
}
|
|
153
154
|
|
|
154
155
|
function isEditorBrowserContext(value: unknown): value is EditorBrowserContext {
|
|
155
|
-
if (!isRecord(value) || !isRecord(value.scene) || !isRecord(value.selection)) {
|
|
156
|
+
if (!isRecord(value) || !isRecord(value.scene) || !isRecord(value.selection) || !Array.isArray(value.openScenes)) {
|
|
156
157
|
return false;
|
|
157
158
|
}
|
|
158
159
|
const syncState = value.scene.syncState;
|
|
159
160
|
if (
|
|
160
161
|
typeof value.scene.path !== 'string'
|
|
161
162
|
|| typeof value.scene.revision !== 'string'
|
|
162
|
-
|| !['synced', 'saving', 'conflict', 'error'].includes(String(syncState))
|
|
163
|
+
|| !['synced', 'unsaved', 'saving', 'conflict', 'error'].includes(String(syncState))
|
|
163
164
|
|| !['loading', 'ready', 'error'].includes(String(value.scene.previewState))
|
|
164
165
|
) {
|
|
165
166
|
return false;
|
|
166
167
|
}
|
|
168
|
+
if (value.openScenes.some((openScene) => !isRecord(openScene)
|
|
169
|
+
|| typeof openScene.path !== 'string'
|
|
170
|
+
|| !['synced', 'unsaved', 'saving', 'conflict', 'error'].includes(String(openScene.syncState)))) {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
167
173
|
if (value.selection.kind === 'scene') {
|
|
168
174
|
return true;
|
|
169
175
|
}
|
|
@@ -179,6 +185,7 @@ export function createEditorHostSession(options: EditorHostSessionOptions) {
|
|
|
179
185
|
const browsers = new Set<EditorBrowserSocket>();
|
|
180
186
|
const pendingScreenshots = new Map<string, PendingEditorScreenshot>();
|
|
181
187
|
let activeBrowser: EditorBrowserSocket | undefined;
|
|
188
|
+
let activeHasNoScene = false;
|
|
182
189
|
let context: EditorContext | undefined;
|
|
183
190
|
let resume: EditorSessionResumeState | undefined;
|
|
184
191
|
let screenshotSequence = 0;
|
|
@@ -225,6 +232,7 @@ export function createEditorHostSession(options: EditorHostSessionOptions) {
|
|
|
225
232
|
return false;
|
|
226
233
|
}
|
|
227
234
|
activeBrowser = socket;
|
|
235
|
+
activeHasNoScene = false;
|
|
228
236
|
sendState(socket, 'editorSessionActive');
|
|
229
237
|
return true;
|
|
230
238
|
}
|
|
@@ -234,6 +242,7 @@ export function createEditorHostSession(options: EditorHostSessionOptions) {
|
|
|
234
242
|
if (socket === activeBrowser) {
|
|
235
243
|
rejectBrowserScreenshots(socket, 'Active Editor browser disconnected during screenshot capture.');
|
|
236
244
|
activeBrowser = undefined;
|
|
245
|
+
activeHasNoScene = false;
|
|
237
246
|
context = undefined;
|
|
238
247
|
}
|
|
239
248
|
}
|
|
@@ -245,7 +254,8 @@ export function createEditorHostSession(options: EditorHostSessionOptions) {
|
|
|
245
254
|
}
|
|
246
255
|
if (parsed.type === 'editorSessionTakeoverRequested') {
|
|
247
256
|
if (!browsers.has(socket) || socket === activeBrowser) return;
|
|
248
|
-
if (activeBrowser && context?.scene.syncState !== 'synced'
|
|
257
|
+
if (activeBrowser && !activeHasNoScene && (context?.scene.syncState !== 'synced'
|
|
258
|
+
|| context.openScenes.some((openScene) => openScene.syncState !== 'synced'))) {
|
|
249
259
|
sendState(socket, 'editorSessionStandby', {
|
|
250
260
|
error: '当前 Editor 尚未同步,暂时无法接管。',
|
|
251
261
|
});
|
|
@@ -256,6 +266,7 @@ export function createEditorHostSession(options: EditorHostSessionOptions) {
|
|
|
256
266
|
rejectBrowserScreenshots(previous, 'Active Editor browser changed during screenshot capture.');
|
|
257
267
|
}
|
|
258
268
|
activeBrowser = socket;
|
|
269
|
+
activeHasNoScene = false;
|
|
259
270
|
context = undefined;
|
|
260
271
|
for (const browser of browsers) {
|
|
261
272
|
if (browser === socket) {
|
|
@@ -319,7 +330,15 @@ export function createEditorHostSession(options: EditorHostSessionOptions) {
|
|
|
319
330
|
}));
|
|
320
331
|
return;
|
|
321
332
|
}
|
|
322
|
-
if (socket !== activeBrowser
|
|
333
|
+
if (socket !== activeBrowser) return;
|
|
334
|
+
if (parsed.type === 'editorContextCleared') {
|
|
335
|
+
context = undefined;
|
|
336
|
+
resume = undefined;
|
|
337
|
+
activeHasNoScene = true;
|
|
338
|
+
notifyStandbyBrowsers();
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
if (parsed.type !== 'editorContextChanged') return;
|
|
323
342
|
if (!isEditorBrowserContext(parsed.context)) {
|
|
324
343
|
throw new Error('Editor context message is invalid.');
|
|
325
344
|
}
|
|
@@ -332,6 +351,7 @@ export function createEditorHostSession(options: EditorHostSessionOptions) {
|
|
|
332
351
|
},
|
|
333
352
|
...structuredClone(parsed.context),
|
|
334
353
|
};
|
|
354
|
+
activeHasNoScene = false;
|
|
335
355
|
resume = {
|
|
336
356
|
scenePath: context.scene.path,
|
|
337
357
|
...(context.selection.kind === 'node'
|
package/src/pixifact-cli.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type { SceneValidationDiagnostic } from 'pixifact/compiler';
|
|
|
9
9
|
import { startEditorServer } from './editorServer';
|
|
10
10
|
import { captureEditorScreenshot, queryEditorContext } from './editorSession';
|
|
11
11
|
import { captureRuntimeScreenshot, queryRuntime, queryRuntimeList } from './runtimeSession';
|
|
12
|
+
import { captureSceneScreenshot } from './sceneScreenshot';
|
|
12
13
|
import type { RuntimeLogLevel, RuntimeRequest } from 'pixifact/runtime-dev';
|
|
13
14
|
import {
|
|
14
15
|
buildPixifactTarget,
|
|
@@ -24,6 +25,7 @@ interface CliOptions {
|
|
|
24
25
|
automation?: Automation;
|
|
25
26
|
captureEditorScreenshot?: typeof captureEditorScreenshot;
|
|
26
27
|
captureRuntimeScreenshot?: typeof captureRuntimeScreenshot;
|
|
28
|
+
captureSceneScreenshot?: typeof captureSceneScreenshot;
|
|
27
29
|
onDevEvent?: (event: PixifactDevEvent) => void;
|
|
28
30
|
readEditorContext?: typeof queryEditorContext;
|
|
29
31
|
listRuntimes?: typeof queryRuntimeList;
|
|
@@ -278,6 +280,7 @@ async function executeFileCommand(
|
|
|
278
280
|
listRuntimes: typeof queryRuntimeList = queryRuntimeList,
|
|
279
281
|
sendRuntimeRequest: typeof queryRuntime = queryRuntime,
|
|
280
282
|
captureRuntime: typeof captureRuntimeScreenshot = captureRuntimeScreenshot,
|
|
283
|
+
captureScene: typeof captureSceneScreenshot = captureSceneScreenshot,
|
|
281
284
|
) {
|
|
282
285
|
const [area, action] = positionals;
|
|
283
286
|
const projectRoot = projectRootFlag(flags);
|
|
@@ -527,6 +530,26 @@ async function executeFileCommand(
|
|
|
527
530
|
});
|
|
528
531
|
}
|
|
529
532
|
|
|
533
|
+
if (area === 'scene' && action === 'screenshot') {
|
|
534
|
+
assertAllowedFlags(flags, ['scene', 'output', 'project-root'], 'scene screenshot');
|
|
535
|
+
const scenePath = requireFlag(flags, 'scene');
|
|
536
|
+
const output = path.resolve(requireFlag(flags, 'output'));
|
|
537
|
+
const validation = automation.validateCompilerScene({ projectRoot, scenePath });
|
|
538
|
+
if (!validation.ok) return validation;
|
|
539
|
+
const screenshot = await captureScene({ projectRoot, scenePath: validation.scene });
|
|
540
|
+
fs.mkdirSync(path.dirname(output), { recursive: true });
|
|
541
|
+
fs.writeFileSync(output, screenshot.data);
|
|
542
|
+
return {
|
|
543
|
+
ok: true,
|
|
544
|
+
scenePath: screenshot.scenePath,
|
|
545
|
+
revision: screenshot.revision,
|
|
546
|
+
width: screenshot.width,
|
|
547
|
+
height: screenshot.height,
|
|
548
|
+
bytes: screenshot.data.byteLength,
|
|
549
|
+
output,
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
|
|
530
553
|
if (area === 'node' && action === 'inspect') {
|
|
531
554
|
return automation.inspectNode({
|
|
532
555
|
projectRoot,
|
|
@@ -549,6 +572,7 @@ export async function executePixifactCli(argv: string[], options: CliOptions = {
|
|
|
549
572
|
'summary',
|
|
550
573
|
'scene inspect --scene <scene-path>',
|
|
551
574
|
'scene validate --scene <scene-path>',
|
|
575
|
+
'scene screenshot --scene <scene-path> --output <png-path>',
|
|
552
576
|
'compile-scenes',
|
|
553
577
|
'validate [--mode <vite-mode>]',
|
|
554
578
|
'build [--mode <vite-mode>]',
|
|
@@ -599,6 +623,7 @@ export async function executePixifactCli(argv: string[], options: CliOptions = {
|
|
|
599
623
|
options.listRuntimes,
|
|
600
624
|
options.queryRuntime,
|
|
601
625
|
options.captureRuntimeScreenshot,
|
|
626
|
+
options.captureSceneScreenshot,
|
|
602
627
|
);
|
|
603
628
|
if (isFailedResult(result)) {
|
|
604
629
|
return {
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { createEditorProjectService } from './editorServer';
|
|
5
|
+
|
|
6
|
+
interface SceneScreenshotOptions {
|
|
7
|
+
projectRoot: string;
|
|
8
|
+
scenePath: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface BrowserSceneCapture {
|
|
12
|
+
scenePath: string;
|
|
13
|
+
revision: string;
|
|
14
|
+
width: number;
|
|
15
|
+
height: number;
|
|
16
|
+
dataUrl: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const pngDataUrlPrefix = 'data:image/png;base64,';
|
|
20
|
+
|
|
21
|
+
export async function captureSceneScreenshot(options: SceneScreenshotOptions) {
|
|
22
|
+
const staticRoot = path.resolve(import.meta.dir, '..', 'editor');
|
|
23
|
+
if (!fs.existsSync(path.join(staticRoot, 'capture.html'))) {
|
|
24
|
+
throw new Error('Scene screenshot frontend is not built. Run "bun run editor:frontend:build" first.');
|
|
25
|
+
}
|
|
26
|
+
const service = createEditorProjectService({
|
|
27
|
+
projectRoot: options.projectRoot,
|
|
28
|
+
staticRoot,
|
|
29
|
+
});
|
|
30
|
+
const token = randomBytes(32).toString('hex');
|
|
31
|
+
const server = Bun.serve({
|
|
32
|
+
hostname: '127.0.0.1',
|
|
33
|
+
port: 0,
|
|
34
|
+
fetch(request) {
|
|
35
|
+
if (request.headers.get('x-pixifact-capture-token') !== token) {
|
|
36
|
+
return Response.json({ ok: false, error: 'Scene screenshot request is unauthorized.' }, { status: 403 });
|
|
37
|
+
}
|
|
38
|
+
if (request.method !== 'GET') {
|
|
39
|
+
return Response.json({ ok: false, error: 'Scene screenshot service is read-only.' }, { status: 405 });
|
|
40
|
+
}
|
|
41
|
+
return service.fetch(request);
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
try {
|
|
45
|
+
const { chromium } = await import('playwright');
|
|
46
|
+
const browser = await chromium.launch({ channel: 'chrome', headless: true });
|
|
47
|
+
try {
|
|
48
|
+
const origin = `http://127.0.0.1:${server.port}`;
|
|
49
|
+
const context = await browser.newContext();
|
|
50
|
+
await context.route('**/*', (route) => {
|
|
51
|
+
if (new URL(route.request().url()).origin !== origin) return route.continue();
|
|
52
|
+
return route.continue({
|
|
53
|
+
headers: {
|
|
54
|
+
...route.request().headers(),
|
|
55
|
+
'x-pixifact-capture-token': token,
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
const page = await context.newPage();
|
|
60
|
+
await page.goto(`${origin}/capture.html`);
|
|
61
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
62
|
+
const timeout = new Promise<never>((_, reject) => {
|
|
63
|
+
timer = setTimeout(() => reject(new Error('Scene 截图超过 60 秒仍未完成。')), 60_000);
|
|
64
|
+
});
|
|
65
|
+
let result: BrowserSceneCapture;
|
|
66
|
+
try {
|
|
67
|
+
result = await Promise.race([
|
|
68
|
+
page.evaluate((scenePath) => (
|
|
69
|
+
window as unknown as { capturePixifactScene: (path: string) => Promise<BrowserSceneCapture> }
|
|
70
|
+
).capturePixifactScene(scenePath), options.scenePath),
|
|
71
|
+
timeout,
|
|
72
|
+
]);
|
|
73
|
+
} finally {
|
|
74
|
+
if (timer) clearTimeout(timer);
|
|
75
|
+
}
|
|
76
|
+
if (
|
|
77
|
+
result.scenePath !== options.scenePath
|
|
78
|
+
|| !Number.isInteger(result.width)
|
|
79
|
+
|| result.width <= 0
|
|
80
|
+
|| !Number.isInteger(result.height)
|
|
81
|
+
|| result.height <= 0
|
|
82
|
+
|| !result.dataUrl.startsWith(pngDataUrlPrefix)
|
|
83
|
+
) {
|
|
84
|
+
throw new Error('Scene screenshot renderer returned invalid PNG metadata.');
|
|
85
|
+
}
|
|
86
|
+
const data = Buffer.from(result.dataUrl.slice(pngDataUrlPrefix.length), 'base64');
|
|
87
|
+
if (!data.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))) {
|
|
88
|
+
throw new Error('Scene screenshot renderer did not return PNG data.');
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
scenePath: result.scenePath,
|
|
92
|
+
revision: result.revision,
|
|
93
|
+
width: result.width,
|
|
94
|
+
height: result.height,
|
|
95
|
+
data,
|
|
96
|
+
};
|
|
97
|
+
} finally {
|
|
98
|
+
await browser.close();
|
|
99
|
+
}
|
|
100
|
+
} finally {
|
|
101
|
+
server.stop(true);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{a as e,c as t,i as n,l as r,o as i,s as a,u as o}from"./index-CSHl3qLL.js";import"./init-CKNzy2w2.js";o.add(t),o.mixin(r,a),o.add(i),o.add(e),o.mixin(r,n);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
:root{color:#d9dee7;font-synthesis:none;text-rendering:optimizelegibility;--bg:#111318;--panel:#171a20;--panel-raised:#1d2129;--canvas:#0d0f13;--line:#2a2f39;--line-strong:#39404d;--text:#d9dee7;--muted:#818997;--faint:#596171;--focus:#4c8dff;--focus-soft:#213a63;--success:#51b785;--warning:#d4a64f;--danger:#e36f6f;--control:#11141a;background:#111318;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}*{box-sizing:border-box}html,body,#app{width:100%;height:100%;margin:0;overflow:hidden}button,input,select{color:inherit;font:inherit;letter-spacing:0}button{border:0}.editor-shell{background:var(--bg);grid-template-rows:42px minmax(0,1fr);width:100%;height:100%;display:grid}.editor-session-standby{background:var(--bg);place-items:center;width:100%;height:100%;display:grid}.editor-session-standby>section{gap:18px;width:min(420px,100vw - 40px);display:grid}.standby-product{color:var(--faint);font-size:10px;font-weight:750}.editor-session-standby h1{color:var(--text);letter-spacing:0;margin:0;font-size:18px}.standby-context{border-top:1px solid var(--line);border-bottom:1px solid var(--line);grid-template-columns:76px minmax(0,1fr);gap:8px 12px;padding:14px 0;display:grid}.standby-context span{color:var(--muted);font-size:12px}.standby-context strong{min-width:0;color:var(--text);text-overflow:ellipsis;white-space:nowrap;font-size:12px;font-weight:500;overflow:hidden}.takeover-button{background:var(--focus);color:#fff;cursor:pointer;border:1px solid #659cff;border-radius:4px;justify-self:start;min-width:104px;height:32px;padding:0 14px;font-size:12px;font-weight:650}.takeover-button:hover:not(:disabled){background:#5a96ff}.standby-note,.standby-error{margin:-6px 0 0;font-size:11px;line-height:1.6}.standby-note{color:var(--muted)}.standby-error{color:var(--danger)}.topbar{border-bottom:1px solid var(--line);background:#14171c;grid-template-columns:232px minmax(0,1fr) auto 112px;align-items:center;min-width:0;display:grid}.brand{border-right:1px solid var(--line);color:#f1f4f8;align-items:center;height:100%;padding:0 14px;font-size:11px;font-weight:750;display:flex}.scene-identity{align-items:baseline;gap:10px;min-width:0;padding:0 14px;display:flex}.scene-identity strong{flex:none;font-size:13px}.scene-identity span{min-width:0;color:var(--muted);text-overflow:ellipsis;white-space:nowrap;font-size:11px;overflow:hidden}.history-tools{align-items:center;gap:2px;padding-right:10px;display:flex}.history-tools button,.reset-button{width:28px;height:28px;color:var(--muted);cursor:pointer;background:0 0;border-radius:4px;place-items:center;padding:0;display:inline-grid}.history-tools button:hover:not(:disabled),.reset-button:hover:not(:disabled){background:var(--panel-raised);color:var(--text)}button:disabled{opacity:.35;cursor:default}.sync-state{border-left:1px solid var(--line);height:100%;color:var(--muted);align-items:center;gap:7px;padding:0 13px;font-size:11px;display:flex}.sync-state span{background:var(--success);border-radius:50%;width:7px;height:7px}.sync-state[data-state=saving] span{background:var(--warning)}.sync-state[data-state=conflict] span,.sync-state[data-state=error] span{background:var(--danger)}.workspace{grid-template-columns:minmax(190px,232px) minmax(360px,1fr) minmax(270px,296px);min-width:0;min-height:0;display:grid}.left-panel,.right-panel,.canvas-panel{min-width:0;min-height:0}.left-panel,.right-panel{background:var(--panel)}.left-panel{border-right:1px solid var(--line)}.right-panel{border-left:1px solid var(--line);overflow:auto}.left-tabs{grid-template-rows:34px minmax(0,1fr);height:100%;display:grid}.tab-list{border-bottom:1px solid var(--line);grid-template-columns:1fr 1fr;display:grid}.tab-trigger{color:var(--muted);cursor:pointer;background:0 0;font-size:11px;position:relative}.tab-trigger[data-state=active]{color:var(--text)}.tab-trigger[data-state=active]:after{background:var(--focus);content:"";height:2px;position:absolute;bottom:-1px;left:12px;right:12px}.tab-content{outline:none;min-height:0;overflow:auto}.panel-title,.inspector-section-title,.asset-group-label{color:var(--muted);font-size:10px;font-weight:700}.panel-title{border-bottom:1px solid var(--line);align-items:center;height:34px;padding:0 12px;display:flex}.tree-list{margin:0;padding:0;list-style:none}.hierarchy-panel{min-height:100%;padding:0 0 6px}.hierarchy-toolbar{z-index:2;border-bottom:1px solid var(--line);background:var(--panel);grid-template-columns:minmax(0,1fr) 27px 27px 27px;align-items:center;gap:2px;height:35px;padding:4px 6px;display:grid;position:sticky;top:0}.hierarchy-toolbar select{border:1px solid var(--line-strong);background:var(--control);width:100%;min-width:0;height:25px;color:var(--text);border-radius:3px;outline:none;padding:0 5px;font-size:10px}.hierarchy-toolbar select:focus{border-color:var(--focus)}.hierarchy-toolbar button{width:27px;height:27px;color:var(--muted);cursor:pointer;background:0 0;border-radius:3px;place-items:center;padding:0;display:grid}.hierarchy-toolbar button:hover:not(:disabled){background:var(--panel-raised);color:var(--text)}.tree-row{width:100%;height:27px;color:var(--text);text-align:left;cursor:default;background:0 0;grid-template-columns:15px 16px minmax(0,1fr) auto;align-items:center;gap:4px;padding-top:0;padding-bottom:0;padding-right:9px;display:grid;position:relative}.hierarchy-panel.is-dragging .tree-row{cursor:grabbing}.hierarchy-panel.is-asset-dragging .tree-row{cursor:copy}.tree-row.drop-inside{box-shadow:inset 0 0 0 1px var(--focus);background:#26364d}.tree-row.drop-before:before,.tree-row.drop-after:after{z-index:1;background:var(--focus);content:"";pointer-events:none;height:2px;position:absolute;left:5px;right:5px}.tree-row.drop-before:before{top:-1px}.tree-row.drop-after:after{bottom:-1px}.tree-row:hover{background:#20242c}.tree-row.selected{background:var(--focus-soft);color:#eef5ff}.tree-disclosure{width:15px;height:20px;color:var(--muted);place-items:center;display:grid}.tree-disclosure.empty{visibility:hidden}.tree-icon{color:#8da4c7}.tree-label{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-size:12px;overflow:hidden}.tree-row small{color:var(--faint);font-size:9px}.scene-root{cursor:default;padding-left:8px;font-weight:650}.scene-mark{color:#a9c8f8;border:1px solid #637a9d;border-radius:3px;place-items:center;width:14px;height:14px;font-size:8px;display:grid}.asset-list{padding:7px 0 14px}.asset-row{width:100%;height:29px;color:var(--text);text-align:left;background:0 0;grid-template-columns:14px 18px minmax(0,1fr);align-items:center;gap:5px;padding:0 12px;display:grid}button.asset-row{cursor:pointer}.asset-directory{color:var(--muted)}.asset-directory svg:nth-of-type(2){color:#a9c8f8}.asset-row[data-asset-draggable=true]{cursor:grab;-webkit-user-select:none;user-select:none}.asset-row:hover,.asset-row.selected{background:#20242c}.asset-row.selected{color:#a9ccff}.asset-row.focused{box-shadow:inset 2px 0 var(--focus);background:#2a3a52}.asset-row span{text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.asset-row .asset-row-spacer{width:14px}.asset-thumbnail{object-fit:contain;pointer-events:none;border-radius:2px;width:18px;height:18px}.asset-row.asset-draggable{color:var(--muted)}.asset-empty,.panel-empty{color:var(--faint);padding:14px 12px;font-size:12px}.panel-empty.compact{padding-top:18px}.canvas-panel{background:var(--canvas);position:relative}.scene-canvas-host{-webkit-user-select:none;user-select:none;position:absolute;inset:0;overflow:hidden}.scene-canvas-host.is-asset-drop-target:after{z-index:2;border:1px solid var(--focus);content:"";pointer-events:none;position:absolute;inset:3px}.canvas-grid{pointer-events:none;background-image:linear-gradient(#20242b 1px,#0000 1px),linear-gradient(90deg,#20242b 1px,#0000 1px);background-size:24px 24px;position:absolute;inset:0}.scene-canvas{display:block;position:absolute;inset:0}.scene-canvas-host.is-pan-ready .scene-canvas{cursor:grab!important}.scene-canvas-host.is-panning .scene-canvas{cursor:grabbing!important}.canvas-tools{z-index:3;position:absolute;top:10px;right:10px}.canvas-tools button{border:1px solid var(--line);background:var(--panel);width:28px;height:28px;color:var(--muted);cursor:pointer;border-radius:4px;place-items:center;padding:0;display:inline-grid}.canvas-tools button:hover:not(:disabled){border-color:var(--line-strong);background:var(--panel-raised);color:var(--text)}.canvas-status{border:1px solid var(--line);max-width:min(520px,100% - 48px);color:var(--muted);background:#15181e;border-radius:4px;padding:7px 10px;font-size:12px;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}.inspector-panel{min-height:0}.inspector-heading{border-bottom:1px solid var(--line);justify-content:space-between;align-items:center;min-height:54px;padding:8px 12px;display:flex}.inspector-heading div{gap:3px;min-width:0;display:grid}.inspector-heading strong{text-overflow:ellipsis;white-space:nowrap;font-size:13px;overflow:hidden}.inspector-heading small{color:var(--muted);font-size:10px}.inspector-section-title{border-bottom:1px solid var(--line);height:28px;padding:9px 12px 0}.property-row{border-bottom:1px solid #21252d;grid-template-columns:minmax(0,1fr);min-height:34px;padding:3px 8px 3px 12px;display:grid}.property-row.is-paired{grid-template-columns:repeat(2,minmax(0,1fr));gap:6px}.property-field{grid-template-columns:minmax(70px,.8fr) minmax(120px,1.4fr);align-items:center;min-width:0;display:grid}.property-row.is-paired .property-field{grid-template-columns:max-content minmax(0,1fr);align-items:center;gap:4px}.property-row.is-paired .property-field>span{font-size:10px}.property-row.is-paired .property-control{grid-template-columns:minmax(0,1fr) 20px;gap:2px}.property-row.is-paired .reset-button{width:20px}.property-field>span{color:var(--text);text-overflow:ellipsis;white-space:nowrap;font-size:11px;overflow:hidden}.property-field>span.inherited{color:var(--muted)}.property-field.is-image-drop-candidate{box-shadow:inset 2px 0 var(--success);cursor:copy;background:#162b24}.property-field.is-image-drop-candidate:hover{background:#1b372d}.property-field.is-image-drop-candidate .property-control input[type=text]{border-color:var(--success)}.property-control{grid-template-columns:minmax(0,1fr) 24px;align-items:start;gap:3px;display:grid}.node-id-control{grid-template-columns:minmax(0,1fr)}.property-value{gap:3px;min-width:0;display:grid}.property-control input[type=text],.property-control input[type=number],.property-control select{border:1px solid var(--line-strong);background:var(--control);width:100%;min-width:0;height:25px;color:var(--text);border-radius:3px;outline:none;padding:0 7px;font-size:11px}.property-control input.resource-reference{cursor:alias}.property-control input:focus,.property-control select:focus{border-color:var(--focus)}.property-control input[type=text].is-invalid,.property-control input[type=text].is-invalid:focus{border-color:var(--danger)}.property-control input:disabled,.property-control select:disabled{color:#9ca6b5;cursor:default;opacity:1;background:#1b1f26;border-color:#303640}.property-control input[type=checkbox]{width:15px;height:15px;accent-color:var(--focus)}.property-control input[type=color]{border:1px solid var(--line-strong);background:var(--control);border-radius:3px;width:34px;height:24px;padding:2px}.reset-button{width:24px;height:24px}.binding-source{color:#8eb8e8;text-overflow:ellipsis;white-space:nowrap;align-items:center;gap:4px;min-width:0;font-size:10px;display:flex;overflow:hidden}.inline-error,.global-error{color:#ffd1d1;margin:0;font-size:11px}.inline-error{background:#2d1b20;border-bottom:1px solid #4f292f;padding:9px 12px}.global-error{background:#321e23;border:1px solid #66343c;border-radius:4px;max-width:min(520px,100vw - 24px);padding:9px 11px;position:fixed;bottom:12px;right:12px}@media (width<=860px){.workspace{grid-template-columns:190px minmax(320px,1fr) 270px}.topbar{grid-template-columns:190px minmax(0,1fr) auto 100px}}
|