feat(editor): highlight edges and show preview import loader

- Highlight all edges connected to selected scene

- Show overlay spinner while uploading/optimizing preview image

- macOS: keep control window independent from presentation

Made-with: Cursor
This commit is contained in:
Ivan Fontosh
2026-04-23 22:01:07 +08:00
parent 8f8eef53c9
commit a24e87035a
5 changed files with 94 additions and 7 deletions
+24 -1
View File
@@ -64,6 +64,7 @@ export function EditorApp() {
const [settingsMenuOpen, setSettingsMenuOpen] = useState(false);
const [renameOpen, setRenameOpen] = useState(false);
const [exportModalOpen, setExportModalOpen] = useState(false);
const [previewBusy, setPreviewBusy] = useState(false);
const [licenseSnap, setLicenseSnap] = useState<LicenseSnapshot | null>(null);
const [licenseKeyModalOpen, setLicenseKeyModalOpen] = useState(false);
const [eulaModalOpen, setEulaModalOpen] = useState(false);
@@ -480,6 +481,7 @@ export function EditorApp() {
previewAssetType={sc?.previewAssetType ?? null}
previewVideoAutostart={sc?.previewVideoAutostart ?? false}
previewRotationDeg={sc?.previewRotationDeg ?? 0}
previewBusy={previewBusy}
mediaAssets={sceneMediaAssets}
audioRefs={sceneAudioRefs}
onAudioRefsChange={(next) =>
@@ -492,7 +494,18 @@ export function EditorApp() {
onDescriptionChange={(description) =>
void actions.updateScene(sid, { description })
}
onImportPreview={() => void actions.importScenePreview(sid)}
onImportPreview={() => {
setPreviewBusy(true);
void (async () => {
try {
await actions.importScenePreview(sid);
} catch (e) {
window.alert(e instanceof Error ? e.message : String(e));
} finally {
setPreviewBusy(false);
}
})();
}}
onClearPreview={() => void actions.clearScenePreview(sid)}
onRotatePreview={(previewRotationDeg) =>
void actions.updateScene(sid, { previewRotationDeg })
@@ -1040,6 +1053,7 @@ type SceneInspectorProps = {
previewAssetType: 'image' | 'video' | null;
previewVideoAutostart: boolean;
previewRotationDeg: 0 | 90 | 180 | 270;
previewBusy: boolean;
mediaAssets: MediaAsset[];
audioRefs: SceneAudioRef[];
onAudioRefsChange: (next: SceneAudioRef[]) => void;
@@ -1145,6 +1159,7 @@ function SceneInspector({
previewAssetType,
previewVideoAutostart,
previewRotationDeg,
previewBusy,
mediaAssets,
audioRefs,
onAudioRefsChange,
@@ -1188,6 +1203,14 @@ function SceneInspector({
) : (
<div className={styles.previewEmpty}>Превью не задано</div>
)}
{previewBusy ? (
<div className={styles.previewBusyOverlay} aria-live="polite">
<div className={styles.previewBusyModal}>
<div className={styles.previewSpinner} aria-hidden />
<div className={styles.previewBusyText}>Загрузка и оптимизация изображения</div>
</div>
</div>
) : null}
</div>
<div className={styles.actionsRow}>
<Button variant="primary" onClick={onImportPreview}>