Panels poliert: Ebenenkombi in Oberleiste, Satelliten-Dialoge, Caps weg, Perf

- Ebenenkombination raus aus Ebenen-Panel, in Oberleiste-Topbar +
  Editor-Satellite (AusschnittLayerDialog embedded). doc.Strings
  haelt active_comb_name, auto-clear bei manueller Eye/Lock-Aenderung.
- EbenenSettingsDialog jetzt Satellite mit Ebene-Picker-Dropdown
  (auto-save on switch via SAVE_KEEP).
- Per-Ausschnitt Einstellungen-Satellite (Massstab, Display, Overrides,
  Ebenenkombi). Alte 'Sichtbarkeit bearbeiten'-Option entfernt.
- Layouts/Ausschnitte: Top-Header weg, Sticky-Footer mit Anzahl +
  Aktionen. LayoutDialog ist jetzt Satellite mit Format-Live-Preview.
- Panel-Captions + Default-Ebenen-Namen auf Mixed-Case (Ausschnitte,
  Ebenen, Waende ...). Nur DOSSIER bleibt caps.
- DimensionenApp: Card-Optik raus, REF-Wuerfel mit Kreisen statt
  Quadraten + Hover-Scale.
- GeschossManager angeglichen an EbenenManager: Rechtsklick-Menue,
  Lock-Button, Delete-X, Duplizieren. layer_builder honoriert z.locked.
- Active Sublayer folgt jetzt dem Geschoss-Wechsel (gleicher Code
  unter neuem Parent).

Performance Geschoss-Wechsel:
- elemente._send_state() ersetzt durch _notify_active_geschoss()
  (Partial-Push statt 200+ Elements re-enumerieren).
- _apply_visibility dedupe via sticky last-applied-signature
  (STATE_SYNC-Echo loopt nicht mehr durch alle Layer).
- _update_clipping nur wenn alt oder neu hasClipping=True.
- Redundante doc.Views.Redraw() im CPlane-Pfad entfernt — die folgende
  apply_visibility-Roundtrip redrawt 30ms spaeter ohnehin.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-19 03:58:28 +02:00
parent e3918cb155
commit 95031ee2c0
29 changed files with 1708 additions and 713 deletions
+25 -58
View File
@@ -1,7 +1,6 @@
import { useState, useEffect, useMemo } from 'react'
import Icon from './components/Icon'
import ContextMenu from './components/ContextMenu'
import AusschnittLayerDialog from './components/AusschnittLayerDialog'
import {
onMessage, notifyReady,
listAusschnitte, saveAusschnitt, updateAusschnitt,
@@ -9,8 +8,7 @@ import {
renameAusschnitt, deleteAusschnitt,
setAusschnittFolder, setAusschnittScale,
duplicateAusschnitt, addAusschnittFolder, removeAusschnittFolder,
getAusschnittLayers, updateAusschnittLayers,
saveLayerPreset, deleteLayerPreset,
openAusschnittSettings,
} from './lib/rhinoBridge'
function EditableInline({ value, onCommit, autoEdit, style, fontSize }) {
@@ -247,22 +245,16 @@ function RootDropZone({ children, onDragOver, onDragLeave, onDrop, dragOver, emp
export default function AusschnitteApp() {
const [snaps, setSnaps] = useState([])
const [extraFolders, setExtraFolders] = useState([])
const [presets, setPresets] = useState([])
const [newName, setNewName] = useState('')
const [ctxMenu, setCtxMenu] = useState(null)
const [collapsed, setCollapsed] = useState({})
const [draggingId, setDraggingId] = useState(null)
const [dragTarget, setDragTarget] = useState(null)
const [layerDialog, setLayerDialog] = useState(null)
useEffect(() => {
onMessage('LIST', ({ snapshots, folders, presets }) => {
onMessage('LIST', ({ snapshots, folders }) => {
setSnaps(snapshots || [])
setExtraFolders(folders || [])
setPresets(presets || [])
})
onMessage('LAYERS_DATA', ({ id, name, layers, presets }) => {
setLayerDialog({ id, name, layers: layers || [], presets: presets || [] })
})
notifyReady()
const blockContext = (ev) => ev.preventDefault()
@@ -301,7 +293,7 @@ export default function AusschnitteApp() {
{ label: 'Wiederherstellen', icon: 'restore', onClick: () => restoreAusschnitt(id) },
{ label: 'Auf Detail anwenden', icon: 'crop_landscape', onClick: () => applyAusschnittToDetail(id) },
{ divider: true },
{ label: 'Sichtbarkeit bearbeiten…', icon: 'layers', onClick: () => getAusschnittLayers(id) },
{ label: 'Ausschnittseinstellungen…', icon: 'tune', onClick: () => openAusschnittSettings(id) },
{ divider: true },
{ label: 'Duplizieren', icon: 'content_copy', onClick: () => duplicateAusschnitt(id) },
{ label: 'Aktualisieren', icon: 'sync', onClick: () => updateAusschnitt(id) },
@@ -361,17 +353,6 @@ export default function AusschnitteApp() {
/>
)
const actions = (
<div style={{ display: 'flex', gap: 4 }}>
<button className="btn-icon-tonal" onClick={handleAddFolder} title="Neuer Ordner">
<Icon name="create_new_folder" size={14} />
</button>
<button className="btn-icon-tonal" onClick={() => listAusschnitte()} title="Aktualisieren">
<Icon name="refresh" size={14} />
</button>
</div>
)
const rootItems = groups[''] || []
const isEmpty = snaps.length === 0 && allFolders.length === 0
@@ -382,21 +363,6 @@ export default function AusschnitteApp() {
background: 'var(--bg-base)',
position: 'relative',
}}>
{/* Fixed Header — wie Layouts/Overrides Pattern */}
<div style={{
display: 'flex', alignItems: 'center', gap: 6,
padding: '8px 10px',
borderBottom: '1px solid var(--border)',
flexShrink: 0,
}}>
<span style={{ flex: 1, fontWeight: 600, letterSpacing: '0.08em',
color: 'var(--text-primary)' }}>
AUSSCHNITTE
</span>
<span className="chip" style={{ fontSize: 8 }}>{snaps.length}</span>
{actions}
</div>
<div style={{ flex: 1, overflowY: 'auto', padding: 8 }}>
{/* Save-Bar als Card */}
<div style={{
@@ -481,6 +447,28 @@ export default function AusschnitteApp() {
</div>
</div>
{/* Sticky Footer: Anzahl + Ordner erstellen + Reload */}
<div style={{
display: 'flex', alignItems: 'center', gap: 8,
padding: '8px 10px',
borderTop: '1px solid var(--border)',
background: 'var(--bg-panel)',
flexShrink: 0,
}}>
<span className="chip" style={{
fontSize: 9, minWidth: 22, justifyContent: 'center',
}}>{snaps.length}</span>
<span style={{ fontSize: 10, color: 'var(--text-muted)', flex: 1 }}>
Ausschnitte
</span>
<button className="btn-icon-tonal" onClick={handleAddFolder} title="Neuer Ordner">
<Icon name="create_new_folder" size={14} />
</button>
<button className="btn-icon-tonal" onClick={() => listAusschnitte()} title="Aktualisieren">
<Icon name="refresh" size={14} />
</button>
</div>
{ctxMenu && (
<ContextMenu
x={ctxMenu.x} y={ctxMenu.y}
@@ -489,27 +477,6 @@ export default function AusschnitteApp() {
/>
)}
{layerDialog && (
<AusschnittLayerDialog
snapName={layerDialog.name}
layers={layerDialog.layers}
presets={layerDialog.presets}
onSave={(layers) => {
updateAusschnittLayers(layerDialog.id,
layers.map(l => ({ id: l.id, visible: l.visible, locked: l.locked })))
setLayerDialog(null)
}}
onClose={() => setLayerDialog(null)}
onSavePreset={(name, layers) => {
saveLayerPreset(name, layers)
setLayerDialog(d => d ? { ...d, presets: [...d.presets.filter(p => p.name !== name), { name, layers }] } : d)
}}
onDeletePreset={(name) => {
deleteLayerPreset(name)
setLayerDialog(d => d ? { ...d, presets: d.presets.filter(p => p.name !== name) } : d)
}}
/>
)}
</div>
)
}