Snapshot: Wand/Öffnung Multi-Surface-Select + Z-Drag + Brüstungs-Mitnahme
Stable working state after a long iteration session. The plugin now supports:
- Multi-Surface-Select für alle Element-Typen (Türen/Fenster/Treppen/Tragwerk)
- Wand-Z-Drag → unbound mode (UK/OK-Override, Wand vom Geschoss entkoppelt)
- Wand-Z-Drag nimmt verknüpfte Öffnungen mit (Brüstung += delta_z via Idle-Pfad)
- Öffnungs-XY-Drag snapt direktional auf Wand-Tangente
- Öffnungs-Z-Drag passt Brüstung an (Fenster sofort sync, Tür deferred)
- Wand-Delete kaskadiert Öffnungen (deferred via Idle, robust gegen _Rotate/_Move)
- Source-Cascade beim Öffnungs-Delete (deferred analog Wand-Kaskade)
- Listener-Cleanup robust gegen _reset_panels.py Reload (Refs in
_dossier_runtime_event_refs gespeichert, vor Re-Install deregistriert)
- _count_same_id_type filtert IsDeleted (verhindert Source-Duplikat-Bug bei Move)
- Frontend: Brüstungs-Slider für Tür ("Schwelle"), Flügel-Block nur bei Fenster
Plus aus früherer Phase dieser Session:
- Dossier-Launcher Auto-Load via Rhinos StartupCommands-XML
- Default-Pfad zeigt auf gebundeltes startup.py (out-of-the-box für neue User)
- Splash-Window beim Plugin-Load mit native macOS rounded corners
- Diverse Launcher-Verbesserungen (Brüstungs-Default, tauri.conf, capabilities)
Known issue: bei Multi-Select-Move mit vielen Sub-Volumen kann sporadisch
"Unable to transform" auftreten (Rhinos Move-Operation kollidiert mit Wand-
Regen). Tür-spezifischer Defer-Pfad mildert das, Fenster läuft sync.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
// Material-Symbols-Outlined-style Icons als Inline-SVG. Keine Font-Loads,
|
||||
// kein Codepoint-Mapping — sauber zu themen via currentColor + stroke-width.
|
||||
|
||||
const PATHS = {
|
||||
folder: (
|
||||
<path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7Z" />
|
||||
),
|
||||
edit: (
|
||||
<>
|
||||
<path d="M4 20h4l10.5-10.5a2.121 2.121 0 0 0-3-3L5 17v3Z" />
|
||||
<path d="m13.5 6.5 3 3" />
|
||||
</>
|
||||
),
|
||||
plus: (
|
||||
<>
|
||||
<path d="M12 5v14" />
|
||||
<path d="M5 12h14" />
|
||||
</>
|
||||
),
|
||||
search: (
|
||||
<>
|
||||
<circle cx="11" cy="11" r="7" />
|
||||
<path d="m20 20-3.5-3.5" />
|
||||
</>
|
||||
),
|
||||
close: (
|
||||
<>
|
||||
<path d="M18 6 6 18" />
|
||||
<path d="M6 6l12 12" />
|
||||
</>
|
||||
),
|
||||
settings: (
|
||||
<>
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9c.36.13.73.31 1.05.55" />
|
||||
</>
|
||||
),
|
||||
layers: (
|
||||
<>
|
||||
<path d="m12 2 9 5-9 5-9-5 9-5Z" />
|
||||
<path d="m3 12 9 5 9-5" />
|
||||
<path d="m3 17 9 5 9-5" />
|
||||
</>
|
||||
),
|
||||
trash: (
|
||||
<>
|
||||
<path d="M3 6h18" />
|
||||
<path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
|
||||
<path d="m19 6-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6" />
|
||||
</>
|
||||
),
|
||||
refresh: (
|
||||
<>
|
||||
<path d="M21 12a9 9 0 1 1-3.5-7.1" />
|
||||
<path d="M21 4v5h-5" />
|
||||
</>
|
||||
),
|
||||
pin: (
|
||||
<>
|
||||
<path d="M9 4h6l-1 5 3 3v2H7v-2l3-3-1-5Z" />
|
||||
<path d="M12 14v6" />
|
||||
</>
|
||||
),
|
||||
pin_filled: (
|
||||
<path d="M9 4h6l-1 5 3 3v2h-4v6h-2v-6H7v-2l3-3-1-5Z" fill="currentColor" stroke="none" />
|
||||
),
|
||||
snapshot: (
|
||||
<>
|
||||
<path d="M3 9a2 2 0 0 1 2-2h2.5l1.7-2h5.6l1.7 2H19a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V9Z" />
|
||||
<circle cx="12" cy="13" r="3.5" />
|
||||
</>
|
||||
),
|
||||
tag: (
|
||||
<>
|
||||
<path d="M3 12V4h8l10 10-8 8L3 12Z" />
|
||||
<circle cx="8" cy="8" r="1.4" fill="currentColor" stroke="none" />
|
||||
</>
|
||||
),
|
||||
chevron_down: <path d="m6 9 6 6 6-6" />,
|
||||
chevron_up: <path d="m6 15 6-6 6 6" />,
|
||||
};
|
||||
|
||||
export default function Icon({ name, size = 16, strokeWidth = 1.6, style }) {
|
||||
const path = PATHS[name];
|
||||
if (!path) return null;
|
||||
return (
|
||||
<svg
|
||||
width={size} height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={strokeWidth}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
style={{ flexShrink: 0, ...style }}
|
||||
>
|
||||
{path}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
import { useEffect, useState, useCallback } from "react";
|
||||
import { checkForAppUpdate, installAppUpdate, skipUpdateVersion, isTauri } from "../utils/updater.js";
|
||||
|
||||
export default function UpdateNotifier() {
|
||||
const [update, setUpdate] = useState(null);
|
||||
const [state, setState] = useState("idle");
|
||||
const [downloaded, setDownloaded] = useState(0);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
const runCheck = useCallback(async ({ silent }) => {
|
||||
if (!isTauri()) return;
|
||||
try {
|
||||
setState("checking");
|
||||
setError(null);
|
||||
const res = await checkForAppUpdate({ respectSkip: silent });
|
||||
if (!res.available) {
|
||||
setUpdate(null);
|
||||
setState("idle");
|
||||
return;
|
||||
}
|
||||
setUpdate(res.update);
|
||||
setState("available");
|
||||
} catch (e) {
|
||||
console.error("Update-Check fehlgeschlagen:", e);
|
||||
setError(String(e?.message || e));
|
||||
setState("idle");
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const t = setTimeout(() => runCheck({ silent: true }), 1500);
|
||||
return () => clearTimeout(t);
|
||||
}, [runCheck]);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = () => runCheck({ silent: false });
|
||||
window.addEventListener("dossier:check-update", handler);
|
||||
return () => window.removeEventListener("dossier:check-update", handler);
|
||||
}, [runCheck]);
|
||||
|
||||
const install = async () => {
|
||||
if (!update) return;
|
||||
try {
|
||||
setState("downloading");
|
||||
setDownloaded(0);
|
||||
setTotal(0);
|
||||
await installAppUpdate(update, (event) => {
|
||||
if (event.event === "Started") {
|
||||
setTotal(event.data.contentLength || 0);
|
||||
} else if (event.event === "Progress") {
|
||||
setDownloaded((d) => d + (event.data.chunkLength || 0));
|
||||
} else if (event.event === "Finished") {
|
||||
setState("installing");
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("Update-Installation fehlgeschlagen:", e);
|
||||
setError(String(e?.message || e));
|
||||
setState("available");
|
||||
}
|
||||
};
|
||||
|
||||
const skipVersion = () => {
|
||||
skipUpdateVersion(update?.version);
|
||||
setUpdate(null);
|
||||
setState("idle");
|
||||
};
|
||||
|
||||
const later = () => {
|
||||
setUpdate(null);
|
||||
setState("idle");
|
||||
};
|
||||
|
||||
if (!isTauri() || !update || state === "idle" || state === "checking") return null;
|
||||
|
||||
const isBusy = state === "downloading" || state === "installing";
|
||||
const pct = total > 0 ? Math.min(100, Math.round((downloaded / total) * 100)) : null;
|
||||
|
||||
return (
|
||||
<div className="dialog-bg" style={{ zIndex: 300 }}>
|
||||
<div className="dialog" style={{ width: 460, maxWidth: "92vw" }}>
|
||||
<header style={{ background: "var(--bg-panel)" }}>
|
||||
<div style={{ fontSize: 10, letterSpacing: "0.18em", color: "var(--accent)", marginBottom: 4, fontWeight: 600 }}>
|
||||
UPDATE VERFÜGBAR
|
||||
</div>
|
||||
<div style={{ display: "flex", alignItems: "baseline", gap: 10 }}>
|
||||
<div style={{ fontSize: 18, color: "var(--text)", fontWeight: 500 }}>
|
||||
Dossier {update.version}
|
||||
</div>
|
||||
{update.currentVersion && (
|
||||
<div style={{ fontSize: 11, color: "var(--text-muted)" }}>von {update.currentVersion}</div>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="body">
|
||||
{update.body && (
|
||||
<div style={{ display: "flex", gap: 12 }}>
|
||||
<div style={{ width: 3, flexShrink: 0, background: "var(--accent)", borderRadius: 2 }} />
|
||||
<div style={{ fontSize: 12, color: "var(--text-muted)", lineHeight: 1.55, whiteSpace: "pre-wrap" }}>{update.body}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div style={{ padding: "10px 12px", background: "rgba(200, 112, 80, 0.12)", border: "1px solid var(--danger)", borderRadius: 6, fontSize: 12, color: "var(--danger)" }}>
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isBusy && (
|
||||
<div>
|
||||
<div style={{ fontSize: 11, color: "var(--text-muted)", marginBottom: 6, letterSpacing: "0.04em" }}>
|
||||
{state === "downloading"
|
||||
? (pct !== null ? `Wird heruntergeladen … ${pct}%` : "Wird heruntergeladen …")
|
||||
: "Wird installiert …"}
|
||||
</div>
|
||||
<div style={{ height: 4, background: "var(--bg-elev)", borderRadius: 2, overflow: "hidden" }}>
|
||||
<div style={{
|
||||
height: "100%",
|
||||
width: pct !== null ? `${pct}%` : "100%",
|
||||
background: "var(--accent)",
|
||||
transition: "width 0.2s",
|
||||
animation: pct === null ? "dossier-update-pulse 1.2s ease-in-out infinite" : undefined,
|
||||
}} />
|
||||
</div>
|
||||
<style>{`@keyframes dossier-update-pulse { 0%,100% { opacity: 0.5; } 50% { opacity: 1; } }`}</style>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<footer style={{ flexDirection: "column", gap: 8, alignItems: "stretch" }}>
|
||||
<button className="primary pill" style={{ width: "100%" }} onClick={install} disabled={isBusy}>
|
||||
{isBusy ? "Bitte warten …" : "Jetzt installieren"}
|
||||
</button>
|
||||
<div style={{ display: "flex", gap: 8 }}>
|
||||
<button style={{ flex: 1 }} onClick={later} disabled={isBusy}>Später</button>
|
||||
<button style={{ flex: 1 }} onClick={skipVersion} disabled={isBusy}>Diese Version überspringen</button>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user