diff --git a/rhino/panel_base.py b/rhino/panel_base.py index 932d897..c1c8a50 100644 --- a/rhino/panel_base.py +++ b/rhino/panel_base.py @@ -267,10 +267,12 @@ def _build_inline_template(): html = f.read().decode("utf-8") placeholder_script = '' + _no_select = '' + _no_ctx = '' if "" in html: - html = html.replace("", placeholder_script + "") + html = html.replace("", placeholder_script + _no_select + _no_ctx + "") else: - html = placeholder_script + html + html = placeholder_script + _no_select + _no_ctx + html def inline_css(m): p = os.path.join(dist_dir, m.group(1).lstrip("./").replace("/", os.sep)) @@ -353,6 +355,15 @@ def attach_webview(panel, bridge, mode): wv.ExecuteScript("window.RHINO_MODE=true;") except Exception: pass + try: + wv.ExecuteScript( + "var _ds=document.createElement('style');" + "_ds.textContent='*{-webkit-user-select:none!important;user-select:none!important;}';" + "document.head.appendChild(_ds);" + "document.addEventListener('contextmenu',function(e){e.preventDefault();},true);" + ) + except Exception: + pass def on_idle(s, e): Rhino.RhinoApp.Idle -= on_idle @@ -440,6 +451,14 @@ def open_satellite_window(mode, params=None, title=None, size=(420, 560), def on_loaded(s, e): try: wv.ExecuteScript("window.RHINO_MODE=true;") except Exception: pass + try: + wv.ExecuteScript( + "var _ds=document.createElement('style');" + "_ds.textContent='*{-webkit-user-select:none!important;user-select:none!important;}';" + "document.head.appendChild(_ds);" + "document.addEventListener('contextmenu',function(e){e.preventDefault();},true);" + ) + except Exception: pass wv.DocumentTitleChanged += on_title_ wv.DocumentLoaded += on_loaded diff --git a/rhino/startup.py b/rhino/startup.py index d83619b..8698772 100644 --- a/rhino/startup.py +++ b/rhino/startup.py @@ -23,11 +23,13 @@ if _HERE not in sys.path: # Nutzer waehrend Python-Imports + Panel-Registrierung nicht in eine schwarze # Rhino-Oberflaeche schaut. Skipt automatisch wenn Launcher seinen eigenen # Splash zeigt (Owner-Marker-Check). -try: - import _startup_splash as _splash_first - _splash_first.show() -except Exception as _ex_splash: - print("[STARTUP] splash early:", _ex_splash) +# Skipt auch wenn Plugin bereits in dieser Session geladen ist (z.B. Cmd+N). +if not sc.sticky.get("_dossier_startup_scheduled"): + try: + import _startup_splash as _splash_first + _splash_first.show() + except Exception as _ex_splash: + print("[STARTUP] splash early:", _ex_splash) # DIAGNOSE — welcher Python-Engine laeuft hier wirklich? Einmalig beim Start. print("[STARTUP] Python: {}".format(sys.version)) @@ -299,6 +301,10 @@ def _load_all(sender, e): Rhino.RhinoDoc.EndOpenDocument += _on_doc_opened except Exception as ex: print("[STARTUP] EndOpenDocument-Hook:", ex) + try: + Rhino.RhinoDoc.NewDocument += _on_doc_opened + except Exception as ex: + print("[STARTUP] NewDocument-Hook:", ex) # Projekt-Config bestimmt, welche Module geladen werden. Ohne Config # (kein Launcher benutzt, oder Datei nicht da) laedt der Host alles. config = _read_project_config() diff --git a/src/components/ContextMenu.css b/src/components/ContextMenu.css new file mode 100644 index 0000000..33f967a --- /dev/null +++ b/src/components/ContextMenu.css @@ -0,0 +1,122 @@ +.ctx-menu { + position: fixed; + padding: 5px; + min-width: 200px; + background: var(--bg-dialog); + border: 1px solid var(--border); + border-radius: 13px; + box-shadow: + 0 2px 6px rgba(0,0,0,0.10), + 0 8px 24px rgba(0,0,0,0.18), + 0 0 0 0.5px var(--border); + z-index: 300; + animation: ctx-in 100ms cubic-bezier(0.2, 0, 0.13, 1) both; +} + +@media (prefers-color-scheme: dark) { + .ctx-menu { + box-shadow: + 0 2px 8px rgba(0,0,0,0.4), + 0 12px 32px rgba(0,0,0,0.55), + 0 0 0 0.5px var(--border); + } +} + +@keyframes ctx-in { + from { opacity: 0; transform: scale(0.94) translateY(-5px); } + to { opacity: 1; transform: scale(1) translateY(0); } +} + +.ctx-title { + padding: 6px 12px 4px; + font-size: 10px; + font-weight: 600; + letter-spacing: 0.06em; + text-transform: uppercase; + color: var(--text-muted); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.ctx-divider { + height: 1px; + background: var(--border-light); + margin: 4px 2px; +} + +.ctx-item { + display: flex; + align-items: center; + gap: 10px; + width: 100%; + padding: 5px 12px; + font-size: 11.5px; + letter-spacing: 0.01em; + font-family: var(--font); + font-weight: 400; + color: var(--text-primary); + background: transparent; + border: none; + border-radius: 999px; + text-align: left; + cursor: pointer; + transition: background 100ms ease, color 100ms ease; +} + +.ctx-item:hover:not(:disabled) { + background: var(--accent-dim); + color: var(--accent); +} + +.ctx-item:hover:not(:disabled) .ctx-item__icon { + color: var(--accent); +} + +.ctx-item:active:not(:disabled) { + background: var(--active-dim); +} + +.ctx-item:disabled { + opacity: 0.38; + cursor: default; +} + +.ctx-item--danger { + color: var(--danger); +} + +.ctx-item--danger .ctx-item__icon { + color: var(--danger) !important; +} + +.ctx-item--danger:hover:not(:disabled) { + color: var(--danger); + background: color-mix(in srgb, var(--danger) 10%, transparent); +} + +.ctx-item__icon { + color: var(--text-secondary); + flex-shrink: 0; + transition: color 100ms ease; +} + +.ctx-item__icon-gap { + width: 14px; + flex-shrink: 0; +} + +.ctx-item__label { + flex: 1; +} + +.ctx-item__shortcut { + font-size: 9px; + font-family: var(--font-mono); + color: var(--text-muted); + background: var(--bg-item); + border: 1px solid var(--border-light); + border-radius: 999px; + padding: 1px 6px; + letter-spacing: 0.04em; +} diff --git a/src/components/ContextMenu.jsx b/src/components/ContextMenu.jsx index 6c23efb..a270b39 100644 --- a/src/components/ContextMenu.jsx +++ b/src/components/ContextMenu.jsx @@ -2,12 +2,12 @@ // Copyright (C) 2026 Karim Gabriele Varano import { useEffect, useRef, useState } from 'react' import Icon from './Icon' +import './ContextMenu.css' -export default function ContextMenu({ x, y, items, onClose }) { +export default function ContextMenu({ x, y, items, onClose, title }) { const ref = useRef(null) const [pos, setPos] = useState({ left: x, top: y }) - // Falls Menue rechts/unten ueberlaufen wuerde, links/oben verschieben useEffect(() => { if (!ref.current) return const rect = ref.current.getBoundingClientRect() @@ -41,47 +41,26 @@ export default function ContextMenu({ x, y, items, onClose }) { }, [onClose]) return ( -