From a24e87035af248d04861b60332ad02aed3028b32 Mon Sep 17 00:00:00 2001 From: Ivan Fontosh Date: Thu, 23 Apr 2026 22:01:07 +0800 Subject: [PATCH] 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 --- app/main/windows/createWindows.ts | 4 +- app/renderer/editor/EditorApp.module.css | 40 +++++++++++++++++++ app/renderer/editor/EditorApp.tsx | 25 +++++++++++- .../editor/graph/SceneGraph.module.css | 6 ++- app/renderer/editor/graph/SceneGraph.tsx | 26 ++++++++++-- 5 files changed, 94 insertions(+), 7 deletions(-) diff --git a/app/main/windows/createWindows.ts b/app/main/windows/createWindows.ts index 19b3de8..a2861ef 100644 --- a/app/main/windows/createWindows.ts +++ b/app/main/windows/createWindows.ts @@ -279,7 +279,9 @@ export function openMultiWindow() { presentation.maximize(); } if (!windows.has('control')) { - createWindow('control', { parent: presentation }); + // macOS: parent-child window binding moves child with the parent (unlike Windows behavior we want). + // Keep control window independent on darwin. + createWindow('control', process.platform === 'darwin' ? undefined : { parent: presentation }); } } diff --git a/app/renderer/editor/EditorApp.module.css b/app/renderer/editor/EditorApp.module.css index 11d580f..6a721b8 100644 --- a/app/renderer/editor/EditorApp.module.css +++ b/app/renderer/editor/EditorApp.module.css @@ -462,6 +462,46 @@ display: flex; align-items: center; justify-content: center; + position: relative; +} + +.previewBusyOverlay { + position: absolute; + inset: 0; + background: rgba(0, 0, 0, 0.55); + display: flex; + align-items: center; + justify-content: center; + padding: 12px; +} + +.previewBusyModal { + display: grid; + justify-items: center; + gap: 10px; + color: var(--text1); + text-align: center; +} + +.previewBusyText { + font-size: var(--text-xs); + font-weight: 700; + color: rgba(255, 255, 255, 0.92); +} + +.previewSpinner { + width: 26px; + height: 26px; + border-radius: 999px; + border: 3px solid rgba(255, 255, 255, 0.25); + border-top-color: rgba(255, 255, 255, 0.9); + animation: previewSpin 0.9s linear infinite; +} + +@keyframes previewSpin { + to { + transform: rotate(360deg); + } } .videoCover { diff --git a/app/renderer/editor/EditorApp.tsx b/app/renderer/editor/EditorApp.tsx index 8efe47e..661d65b 100644 --- a/app/renderer/editor/EditorApp.tsx +++ b/app/renderer/editor/EditorApp.tsx @@ -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(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({ ) : (
Превью не задано
)} + {previewBusy ? ( +
+
+
+
Загрузка и оптимизация изображения…
+
+
+ ) : null}