From b1b2090b3ee7033685f275407c8a05dcf5b44f6a Mon Sep 17 00:00:00 2001 From: karim Date: Tue, 19 May 2026 01:30:28 +0200 Subject: [PATCH] Satelliten-Dialoge: embedded-Mode + GeschossDialog auch als echtes Fenster MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Zwei Dinge: 1. embedded-Mode in den Dialog-Komponenten — wenn TRUE, kein Backdrop + keine MaxWidth-Constraint, das Dialog fuellt das ganze WebView statt wie ein kleines zentriertes Fenster IN dem WebView gerendert zu werden (= "Fenster im Fenster"-Effekt). Betroffen: - GeschossSettingsDialog - EbenenSettingsDialog - GeschossDialog Satelliten-Apps (GeschossSettingsApp, EbenenSettingsApp, GeschossDialogApp) passen jetzt `embedded` durch. 2. GeschossDialog (= der grosse Mehrfach-Editor hinter dem Pencil-Button) laeuft jetzt auch als Satelliten-Fenster — selbe Architektur wie die Settings-Dialoge. Backend hat neuen Handler _open_geschoss_dialog und neuen Message OPEN_GESCHOSS_DIALOG. Auf Save: ganze z-Liste replace + _apply(save_z=True). GeschossManager braucht den inline-Dialog-State nicht mehr; Pencil-Button ruft openGeschossDialog(zeichnungsebenen). Co-Authored-By: Claude Opus 4.7 --- rhino/rhinopanel.py | 23 +++++++++++ src/EbenenSettingsApp.jsx | 19 ++++----- src/GeschossDialogApp.jsx | 49 +++++++++++++++++++++++ src/GeschossSettingsApp.jsx | 24 ++++------- src/components/EbenenSettingsDialog.jsx | 46 +++++++++++++-------- src/components/GeschossDialog.jsx | 45 +++++++++++++-------- src/components/GeschossManager.jsx | 18 ++------- src/components/GeschossSettingsDialog.jsx | 49 +++++++++++++++-------- src/lib/rhinoBridge.js | 3 ++ src/main.jsx | 2 + 10 files changed, 184 insertions(+), 94 deletions(-) create mode 100644 src/GeschossDialogApp.jsx diff --git a/rhino/rhinopanel.py b/rhino/rhinopanel.py index 8a8a2c2..feff71b 100644 --- a/rhino/rhinopanel.py +++ b/rhino/rhinopanel.py @@ -221,6 +221,8 @@ class EbenenBridge(panel_base.BaseBridge): elif t == "OPEN_EBENEN_SETTINGS": self._open_ebenen_settings(p.get("ebene") or {}, p.get("hatchPatterns") or []) + elif t == "OPEN_GESCHOSS_DIALOG": + self._open_geschoss_dialog(p.get("zeichnungsebenen") or []) # ---- Helpers ---- @@ -299,6 +301,27 @@ class EbenenBridge(panel_base.BaseBridge): size=(420, 600), on_save=on_save) + def _open_geschoss_dialog(self, zeichnungsebenen): + """Oeffnet den vollen GeschossDialog (Mehrfach-Editor) als + Satelliten-Fenster. Save schreibt die ganze z-Liste neu.""" + if not isinstance(zeichnungsebenen, list): + print("[EBENEN] open_geschoss_dialog: keine Liste"); return + def on_save(payload): + doc = Rhino.RhinoDoc.ActiveDoc + if doc is None: return + new_z = payload.get("zeichnungsebenen") or [] + if not new_z: return + e_raw = doc.Strings.GetValue("dossier_ebenen") + try: e_list = json.loads(e_raw) if e_raw else [] + except Exception: e_list = [] + self._apply(new_z, e_list, save_z=True, save_e=False) + panel_base.open_satellite_window( + "geschoss_dialog", + params={"zeichnungsebenen": zeichnungsebenen}, + title="Zeichnungsebenen bearbeiten", + size=(560, 620), + on_save=on_save) + def _apply(self, zeichnungsebenen, ebenen, save_z=True, save_e=True): print("[EBENEN] _apply START z={} e={} (save_z={} save_e={})".format( len(zeichnungsebenen) if zeichnungsebenen else 0, diff --git a/src/EbenenSettingsApp.jsx b/src/EbenenSettingsApp.jsx index 21a8bf1..fed1307 100644 --- a/src/EbenenSettingsApp.jsx +++ b/src/EbenenSettingsApp.jsx @@ -26,17 +26,12 @@ export default function EbenenSettingsApp() { } return ( -
- bridgeSend('SAVE', updated)} - onClose={() => bridgeSend('CANCEL', {})} - /> -
+ bridgeSend('SAVE', updated)} + onClose={() => bridgeSend('CANCEL', {})} + /> ) } diff --git a/src/GeschossDialogApp.jsx b/src/GeschossDialogApp.jsx new file mode 100644 index 0000000..2374ad5 --- /dev/null +++ b/src/GeschossDialogApp.jsx @@ -0,0 +1,49 @@ +import { useEffect } from 'react' +import GeschossDialog from './components/GeschossDialog' +import { notifyReady } from './lib/rhinoBridge' + +function bridgeSend(type, payload = {}) { + if (!window.RHINO_MODE) { console.log('[Bridge] →', type, payload); return } + const json = JSON.stringify({ type, payload }) + document.title = 'RHINOMSG::' + json +} + +// recalcOkff direkt hier — gleiche Logik wie in ZeichnungsebenenApp.jsx, +// damit der Dialog die OKFF-Werte beim Editieren live updaten kann. +function recalcOkff(list) { + let acc = 0 + return list.map(z => { + if (z.isGeschoss) { + const next = { ...z, okff: parseFloat(acc.toFixed(3)) } + acc += (z.hoehe ?? 3.0) + return next + } + return { ...z, okff: undefined } + }) +} + +export default function GeschossDialogApp() { + const initial = (typeof window !== 'undefined' && window.PANEL_PARAMS) || {} + + useEffect(() => { + notifyReady() + const blockContext = (ev) => ev.preventDefault() + document.addEventListener('contextmenu', blockContext) + return () => document.removeEventListener('contextmenu', blockContext) + }, []) + + const z = initial.zeichnungsebenen || initial + if (!Array.isArray(z) || z.length === 0) { + return
Keine Daten
+ } + + return ( + bridgeSend('SAVE', { zeichnungsebenen: updated })} + onClose={() => bridgeSend('CANCEL', {})} + /> + ) +} diff --git a/src/GeschossSettingsApp.jsx b/src/GeschossSettingsApp.jsx index f9088a5..11d29e2 100644 --- a/src/GeschossSettingsApp.jsx +++ b/src/GeschossSettingsApp.jsx @@ -1,9 +1,7 @@ import { useEffect } from 'react' import GeschossSettingsDialog from './components/GeschossSettingsDialog' -import { onMessage, notifyReady } from './lib/rhinoBridge' +import { notifyReady } from './lib/rhinoBridge' -// Inline send fuer SAVE/CANCEL — schickt direkt an Python-Bridge des -// Satelliten-Fensters. function bridgeSend(type, payload = {}) { if (!window.RHINO_MODE) { console.log('[Bridge] →', type, payload); return } const json = JSON.stringify({ type, payload }) @@ -11,33 +9,25 @@ function bridgeSend(type, payload = {}) { } export default function GeschossSettingsApp() { - // PANEL_PARAMS wurden von Python beim Window-Open injiziert. const initial = (typeof window !== 'undefined' && window.PANEL_PARAMS) || {} useEffect(() => { notifyReady() - // Native Browser-Context-Menu unterdruecken const blockContext = (ev) => ev.preventDefault() document.addEventListener('contextmenu', blockContext) return () => document.removeEventListener('contextmenu', blockContext) }, []) - // Wenn keine Daten da sind: leeres Frame if (!initial || typeof initial !== 'object') { return
Keine Daten
} return ( -
- bridgeSend('SAVE', updated)} - onClose={() => bridgeSend('CANCEL', {})} - /> -
+ bridgeSend('SAVE', updated)} + onClose={() => bridgeSend('CANCEL', {})} + /> ) } diff --git a/src/components/EbenenSettingsDialog.jsx b/src/components/EbenenSettingsDialog.jsx index c21f338..4941f05 100644 --- a/src/components/EbenenSettingsDialog.jsx +++ b/src/components/EbenenSettingsDialog.jsx @@ -27,7 +27,7 @@ function SectionLabel({ children }) { ) } -export default function EbenenSettingsDialog({ ebene, hatchPatterns = ['Solid'], onSave, onClose }) { +export default function EbenenSettingsDialog({ ebene, hatchPatterns = ['Solid'], onSave, onClose, embedded = false }) { const [draft, setDraft] = useState({ ...ebene, fill: { @@ -78,23 +78,35 @@ export default function EbenenSettingsDialog({ ebene, hatchPatterns = ['Solid'], ...hatchPatterns.filter(p => p !== 'Solid' && p !== 'None'), ] + const wrapperStyle = embedded ? { + width: '100%', height: '100%', + background: 'var(--bg-dialog)', + display: 'flex', flexDirection: 'column', + overflow: 'hidden', + } : { + position: 'absolute', inset: 0, zIndex: 150, + background: 'var(--bg-overlay)', + display: 'flex', alignItems: 'flex-start', justifyContent: 'center', + paddingTop: 30, + } + const innerStyle = embedded ? { + width: '100%', height: '100%', + display: 'flex', flexDirection: 'column', + overflow: 'hidden', + } : { + background: 'var(--bg-dialog)', + border: '1px solid var(--border)', + borderRadius: 'var(--r-lg)', + boxShadow: 'var(--shadow-3)', + width: 'calc(100% - 16px)', maxWidth: 300, + display: 'flex', flexDirection: 'column', + overflow: 'hidden', + maxHeight: 'calc(100vh - 60px)', + } + return ( -
-
+
+
{/* Header */}
({ ...z }))) const update = (i, field, val) => { @@ -56,23 +56,34 @@ export default function GeschossDialog({ zeichnungsebenen, recalcOkff, onSave, o del: { width: 22, flexShrink: 0 }, } + const wrapperStyle = embedded ? { + width: '100%', height: '100%', + background: 'var(--bg-dialog)', + display: 'flex', flexDirection: 'column', + overflow: 'hidden', + } : { + position: 'absolute', inset: 0, zIndex: 100, + background: 'var(--bg-overlay)', + display: 'flex', alignItems: 'flex-start', justifyContent: 'center', + paddingTop: 40, + } + const innerStyle = embedded ? { + width: '100%', height: '100%', + display: 'flex', flexDirection: 'column', + overflow: 'hidden', + } : { + background: 'var(--bg-dialog)', + border: '1px solid var(--border)', + borderRadius: 'var(--r-lg)', + width: 'calc(100% - 24px)', + boxShadow: 'var(--shadow)', + display: 'flex', flexDirection: 'column', + maxHeight: 'calc(100vh - 80px)', + overflow: 'hidden', + } return ( -
-
+
+
{name} @@ -85,7 +83,8 @@ export default function GeschossManager({ zeichnungsebenen, activeId, onActiveChange, onChange, recalcOkff, mode, onModeChange, }) { - const [dialogOpen, setDialogOpen] = useState(false) + // dialogOpen-State entfaellt — Bearbeiten-Dialog laeuft jetzt als + // Satelliten-Fenster via openGeschossDialog(). const sorted = [...zeichnungsebenen].reverse() const gesamthoehe = zeichnungsebenen @@ -134,7 +133,7 @@ export default function GeschossManager({ -
@@ -166,15 +165,6 @@ export default function GeschossManager({ ))}
- {dialogOpen && ( - { onChange(updated); setDialogOpen(false) }} - onClose={() => setDialogOpen(false)} - /> - )} - ) } diff --git a/src/components/GeschossSettingsDialog.jsx b/src/components/GeschossSettingsDialog.jsx index 9569ffc..f2d3dfd 100644 --- a/src/components/GeschossSettingsDialog.jsx +++ b/src/components/GeschossSettingsDialog.jsx @@ -35,7 +35,7 @@ function Toggle({ label, checked, onChange, hint }) { ) } -export default function GeschossSettingsDialog({ geschoss, onSave, onClose }) { +export default function GeschossSettingsDialog({ geschoss, onSave, onClose, embedded = false }) { const [draft, setDraft] = useState({ ...geschoss }) const set = (patch) => setDraft({ ...draft, ...patch }) @@ -46,22 +46,37 @@ export default function GeschossSettingsDialog({ geschoss, onSave, onClose }) { const okff = draft.okff ?? 0 const clipZ = (okff + schnitt).toFixed(2) + // embedded=true: in einem Satelliten-Fenster gerendert — kein Backdrop, + // keine Width-Constraint, fuellt das ganze WebView. + const Wrapper = embedded ? 'div' : 'div' + const wrapperStyle = embedded ? { + width: '100%', height: '100%', + background: 'var(--bg-dialog)', + display: 'flex', flexDirection: 'column', + overflow: 'hidden', + } : { + position: 'absolute', inset: 0, zIndex: 150, + background: 'var(--bg-overlay)', + display: 'flex', alignItems: 'flex-start', justifyContent: 'center', + paddingTop: 30, + } + const innerStyle = embedded ? { + width: '100%', height: '100%', + display: 'flex', flexDirection: 'column', + overflow: 'hidden', + } : { + background: 'var(--bg-dialog)', + border: '1px solid var(--border)', + borderRadius: 'var(--r-lg)', + boxShadow: 'var(--shadow-3)', + width: 'calc(100% - 16px)', maxWidth: 280, + display: 'flex', flexDirection: 'column', + overflow: 'hidden', + } + return ( -
-
+ +
{/* Header */}
Übernehmen
-
+ ) } diff --git a/src/lib/rhinoBridge.js b/src/lib/rhinoBridge.js index 3674f0e..52f1401 100644 --- a/src/lib/rhinoBridge.js +++ b/src/lib/rhinoBridge.js @@ -285,6 +285,9 @@ export function openGeschossSettings(geschoss) { export function openEbenenSettings(ebene, hatchPatterns) { send('OPEN_EBENEN_SETTINGS', { ebene, hatchPatterns }) } +export function openGeschossDialog(zeichnungsebenen) { + send('OPEN_GESCHOSS_DIALOG', { zeichnungsebenen }) +} export function applyVisibility(activeZ, zeichnungsebenen, activeCode, ebenen, zMode, eMode) { // Split-Panels koennen mit null/[] fuer fremde Slice aufrufen — Backend diff --git a/src/main.jsx b/src/main.jsx index 0263c60..89d2f54 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -5,6 +5,7 @@ import App from './App.jsx' import ZeichnungsebenenApp from './ZeichnungsebenenApp.jsx' import GeschossSettingsApp from './GeschossSettingsApp.jsx' import EbenenSettingsApp from './EbenenSettingsApp.jsx' +import GeschossDialogApp from './GeschossDialogApp.jsx' import GestaltungApp from './GestaltungApp.jsx' import AusschnitteApp from './AusschnitteApp.jsx' import MassstabApp from './MassstabApp.jsx' @@ -28,6 +29,7 @@ const RootApp = mode === 'gestaltung' ? GestaltungApp : mode === 'zeichnungsebenen' ? ZeichnungsebenenApp : mode === 'geschoss_settings' ? GeschossSettingsApp : mode === 'ebenen_settings' ? EbenenSettingsApp + : mode === 'geschoss_dialog' ? GeschossDialogApp : App window.onerror = function (msg, src, line, col, err) {