Overrides-Fenster aufgeräumt + Rule-Templates
UX-Cleanup:
- Globaler AN/AUS-Toggle entfernt — den gibt's bereits in der
Oberleiste, doppelt war redundant.
- Reload/Refresh-Button entfernt — Backend re-applied automatisch
bei jeder Regel-Aenderung, manuelles Reload nicht noetig.
- + (Neue Regel) wurde aus dem Header in eine neue Sektion
UNTER der Kombinationen-Card verschoben.
Neues Feature: Rule-Templates (einzelne wiederverwendbare Regeln)
- Storage: ~/Library/.../override_rule_templates.json (cross-doc,
parallel zu den Kombinationen-Presets)
- API in overrides.py: list/save/load/delete_rule_template
- Bridge-Messages: SAVE_RULE_TEMPLATE, DELETE_RULE_TEMPLATE,
ADD_FROM_TEMPLATE
- State enthaelt jetzt ruleTemplates: [{name, rule}]
UI:
- Neuer Bereich "Neue Regel" unter Kombinationen: [+ leer] +
[+ Aus Vorlage ▼ dropdown]
- Vorlage waehlen → insert auf hoechste Prio (gleich wie addRule)
- Im Dropdown unten: "🗑 <name> loeschen" zum Entfernen einer Vorlage
- Im Rule-Kontextmenue: neuer Eintrag "Als Vorlage speichern…"
fragt nach Name, speichert die Regel cross-doc
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -54,6 +54,9 @@ _STORE_KEY = "dossier_overrides"
|
||||
# Globale Presets (cross-doc) — Datei im User-Home
|
||||
_PRESETS_DIR = os.path.expanduser("~/Library/Application Support/RhinoPanel")
|
||||
_PRESETS_PATH = os.path.join(_PRESETS_DIR, "override_presets.json")
|
||||
# Rule-Templates: einzelne wiederverwendbare Regeln (cross-doc). Andere
|
||||
# Datei damit User Combo-Presets und Einzel-Templates separat verwalten kann.
|
||||
_RULE_TPL_PATH = os.path.join(_PRESETS_DIR, "override_rule_templates.json")
|
||||
|
||||
# UserString-Keys fuer Original-Backups (pro Objekt)
|
||||
_ORIG_COLOR_SRC = "dossier_or_csrc"
|
||||
@@ -183,6 +186,72 @@ def delete_preset(name):
|
||||
return _write_presets_file(new)
|
||||
|
||||
|
||||
# --- Rule-Templates: einzelne wiederverwendbare Regeln (cross-doc) ----------
|
||||
|
||||
def _read_rule_templates():
|
||||
if not os.path.isfile(_RULE_TPL_PATH): return []
|
||||
try:
|
||||
with open(_RULE_TPL_PATH, "rb") as f:
|
||||
data = json.loads(f.read().decode("utf-8"))
|
||||
if isinstance(data, list): return data
|
||||
if isinstance(data, dict) and "templates" in data:
|
||||
return data.get("templates") or []
|
||||
except Exception as ex:
|
||||
print("[OVERRIDES] read_rule_templates:", ex)
|
||||
return []
|
||||
|
||||
|
||||
def _write_rule_templates(templates):
|
||||
try:
|
||||
if not os.path.isdir(_PRESETS_DIR):
|
||||
os.makedirs(_PRESETS_DIR)
|
||||
with open(_RULE_TPL_PATH, "wb") as f:
|
||||
f.write(json.dumps(templates or [], ensure_ascii=False, indent=2).encode("utf-8"))
|
||||
return True
|
||||
except Exception as ex:
|
||||
print("[OVERRIDES] write_rule_templates:", ex)
|
||||
return False
|
||||
|
||||
|
||||
def list_rule_templates():
|
||||
"""Liefert Liste von {name, rule} fuer alle gespeicherten Templates."""
|
||||
out = []
|
||||
for t in _read_rule_templates():
|
||||
if not isinstance(t, dict): continue
|
||||
out.append({"name": t.get("name", "(ohne Name)"),
|
||||
"rule": t.get("rule") or {}})
|
||||
return out
|
||||
|
||||
|
||||
def save_rule_template(name, rule):
|
||||
"""Speichert/ueberschreibt eine Regel als Template unter name."""
|
||||
if not name or not isinstance(name, str): return False
|
||||
name = name.strip()
|
||||
if not name or not isinstance(rule, dict): return False
|
||||
templates = _read_rule_templates()
|
||||
for i, t in enumerate(templates):
|
||||
if isinstance(t, dict) and t.get("name") == name:
|
||||
templates[i] = {"name": name, "rule": rule}
|
||||
return _write_rule_templates(templates)
|
||||
templates.append({"name": name, "rule": rule})
|
||||
return _write_rule_templates(templates)
|
||||
|
||||
|
||||
def load_rule_template(name):
|
||||
"""Liefert die Rule eines Templates oder None."""
|
||||
for t in _read_rule_templates():
|
||||
if isinstance(t, dict) and t.get("name") == name:
|
||||
return json.loads(json.dumps(t.get("rule") or {}))
|
||||
return None
|
||||
|
||||
|
||||
def delete_rule_template(name):
|
||||
templates = _read_rule_templates()
|
||||
new = [t for t in templates if not (isinstance(t, dict) and t.get("name") == name)]
|
||||
if len(new) == len(templates): return False
|
||||
return _write_rule_templates(new)
|
||||
|
||||
|
||||
def set_active_preset(doc, name):
|
||||
"""Aktiviert ein gespeichertes Preset: kopiert dessen Rules ins Doc-Config
|
||||
und markiert es als activePreset. Wenn name leer/None: aktives Preset
|
||||
|
||||
@@ -67,13 +67,14 @@ def _list_hatch_patterns(doc):
|
||||
def _payload(doc):
|
||||
cfg = overrides.load_config(doc)
|
||||
return {
|
||||
"enabled": bool(cfg.get("enabled")),
|
||||
"rules": cfg.get("rules") or [],
|
||||
"layers": _list_layer_names(doc),
|
||||
"linetypes": _list_linetypes(doc),
|
||||
"hatchPatterns": _list_hatch_patterns(doc),
|
||||
"presets": overrides.list_presets(),
|
||||
"activePreset": cfg.get("activePreset"),
|
||||
"enabled": bool(cfg.get("enabled")),
|
||||
"rules": cfg.get("rules") or [],
|
||||
"layers": _list_layer_names(doc),
|
||||
"linetypes": _list_linetypes(doc),
|
||||
"hatchPatterns": _list_hatch_patterns(doc),
|
||||
"presets": overrides.list_presets(),
|
||||
"activePreset": cfg.get("activePreset"),
|
||||
"ruleTemplates": overrides.list_rule_templates(),
|
||||
}
|
||||
|
||||
|
||||
@@ -210,6 +211,36 @@ class OverridesBridge(panel_base.BaseBridge):
|
||||
overrides.delete_preset(name)
|
||||
self._send_state()
|
||||
|
||||
# --- Rule-Templates (cross-doc, einzelne Regeln) ----------------
|
||||
elif t == "SAVE_RULE_TEMPLATE":
|
||||
name = (p.get("name") or "").strip()
|
||||
rule = p.get("rule") or {}
|
||||
if name and isinstance(rule, dict):
|
||||
# ID/Name aus dem Template-Rule herausschneiden (template hat
|
||||
# eigenen Namen, ID wird beim Insert neu generiert)
|
||||
clean = {k: v for k, v in rule.items() if k not in ("id",)}
|
||||
overrides.save_rule_template(name, clean)
|
||||
self._send_state()
|
||||
elif t == "DELETE_RULE_TEMPLATE":
|
||||
name = (p.get("name") or "").strip()
|
||||
if name:
|
||||
overrides.delete_rule_template(name)
|
||||
self._send_state()
|
||||
elif t == "ADD_FROM_TEMPLATE":
|
||||
name = (p.get("name") or "").strip()
|
||||
if not name: return
|
||||
tpl = overrides.load_rule_template(name)
|
||||
if not tpl: return
|
||||
cfg = overrides.load_config(doc)
|
||||
new_rule = dict(tpl)
|
||||
new_rule["id"] = "rule_" + uuid.uuid4().hex[:8]
|
||||
new_rule.setdefault("enabled", True)
|
||||
new_rule.setdefault("name", name)
|
||||
rules = cfg.get("rules") or []
|
||||
rules.insert(0, new_rule)
|
||||
overrides.update_rules(doc, rules, cfg.get("enabled"))
|
||||
self._send_state()
|
||||
|
||||
|
||||
def _ensure_listeners_once():
|
||||
"""Overrides-Listener nur EINMAL global installieren (statt bei jedem
|
||||
|
||||
Reference in New Issue
Block a user