feat: dirty-Markierung + Standard-Hinweis + Bloom 0.01-Schritte im Panel

- Werte abweichend vom Standard werden amber markiert
- klickbarer Hinweis '↺ <Standard>' zeigt Originalwert (Bugfix: _defaultEl wurde nie zugewiesen)
- Bloom-Schwelle jetzt in 0.01-Schritten einstellbar

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
minecraft-cloneandClaude committed 2026-06-21 18:24:35 +02:00
1 parent 6e8e207afa
commit 951cb26f3f
2 files changed
+98 -19

No files matched your search

+84 -18
View File
@@ -14,7 +14,7 @@
// ============================================================
import * as THREE from 'three';
import * as CFG from './config.js';
import { loadSettings, saveSettings, clearSettings } from './config.js';
import { loadSettings, saveSettings, clearSettings, DEFAULTS } from './config.js';
// ---------- Apply-Registry (von main.js gefüllt) ----------
const applyFns = Object.create(null);
@@ -141,7 +141,7 @@ const SCHEMA = [
{ path: 'BLOOM_ENABLED', label: 'Bloom an', type: 'checkbox', live: true },
{ path: 'BLOOM.strength', label: 'Bloom-Stärke', type: 'range', min: 0, max: 2, step: 0.05, live: true },
{ path: 'BLOOM.radius', label: 'Bloom-Radius', type: 'range', min: 0, max: 2, step: 0.05, live: true },
{ path: 'BLOOM.threshold', label: 'Bloom-Schwelle', type: 'range', min: 0, max: 2, step: 0.05, live: true },
{ path: 'BLOOM.threshold', label: 'Bloom-Schwelle', type: 'range', min: 0, max: 2, step: 0.01, live: true },
],
},
{
@@ -202,6 +202,7 @@ function onControlChange(item, value, skipDisplay) {
markReloadNeeded();
}
if (!skipDisplay && item._valueEl) item._valueEl.textContent = fmt(item, value);
updateDirty(item);
}
function markReloadNeeded() {
@@ -225,9 +226,57 @@ function fmt(item, value) {
return String(value);
}
// ---------- Wert eines Controls aus config lesen ----------
function currentValue(item) {
return getPath(CFG, item.path);
// ---------- Effektiver Wert (Override aus settings, sonst config = Default) ----------
// Für Reload-Werte, die erst beim Neustart greifen, steht die letzte
// Änderung nur in `settings` (localStorage), nicht im config-Export.
// Daher für Anzeige + Dirty-Check immer `settings` zuerst fragen.
function effectiveValue(item) {
const v = getPath(settings, item.path);
return v !== undefined ? v : getPath(CFG, item.path);
}
// Code-Default (unabhängig von Overrides), aus dem DEFAULTS-Snapshot.
function defaultFor(item) {
return getPath(DEFAULTS, item.path);
}
// Gleichheit mit numerischer Toleranz (Slider-Rundungsfehler abfangen,
// sonst bliebe ein per Slider auf Default zurückbewegter Wert "dirty").
function valueEquals(a, b) {
if (typeof a === 'number' && typeof b === 'number') {
if (a === b) return true;
return Math.abs(a - b) < 1e-6 * Math.max(1, Math.abs(a), Math.abs(b));
}
return a === b;
}
// ---------- Dirty-Markierung pro Control ----------
// Zeigt an, dass ein Wert vom Standard abweicht: Label + Wert werden
// amber, und der klickbare "↺ Standard: X"-Hinweis erscheint.
function updateDirty(item) {
if (!item._row) return;
const dirty = !valueEquals(effectiveValue(item), defaultFor(item));
item._row.classList.toggle('dirty', dirty);
if (item._defaultEl) {
item._defaultEl.hidden = !dirty;
item._defaultEl.textContent = '↺ ' + fmt(item, defaultFor(item));
}
}
// Auf Default zurücksetzen (Klick auf den Standard-Hinweis).
function resetItem(item) {
const d = defaultFor(item);
const inp = item._input;
if (item.type === 'checkbox') {
if (inp) inp.checked = !!d;
onControlChange(item, !!d);
} else if (item.type === 'select') {
if (inp) inp.value = String(d);
onControlChange(item, Number(d));
} else if (item.type === 'color') {
if (inp) inp.value = toHex(d);
onControlChange(item, d);
} else {
const v = Number(d);
if (inp) inp.value = v;
onControlChange(item, v);
}
}
// ---------- DOM-Aufbau ----------
@@ -240,28 +289,41 @@ function el(tag, cls, html) {
function buildControl(item) {
const row = el('div', 'ctrl-row');
item._row = row;
const head = el('div', 'ctrl-head');
const label = el('span', 'ctrl-label', item.label);
const badge = el('span', 'badge ' + (item.live === false || item.groupReload ? 'reload' : 'live'),
item.live === false || item.groupReload ? '⟳' : '●');
badge.title = item.live === false || item.groupReload ? 'Braucht Neustart' : 'Greift sofort';
head.append(label, badge);
// Klickbarer Standard-Hinweis (nur sichtbar, wenn der Wert abweicht).
const defSpan = el('span', 'ctrl-default');
defSpan.title = 'Auf Standard zurücksetzen';
defSpan.addEventListener('click', (e) => { e.stopPropagation(); resetItem(item); });
item._defaultEl = defSpan;
const isReload = item.live === false || item.groupReload;
const badge = el('span', 'badge ' + (isReload ? 'reload' : 'live'), isReload ? '⟳' : '●');
badge.title = isReload ? 'Braucht Neustart' : 'Greift sofort';
const right = el('span', 'ctrl-head-right');
right.append(defSpan, badge);
head.append(label, right);
row.append(head);
const val = el('span', 'ctrl-value');
item._valueEl = val;
const numSpan = el('span', 'cv-num');
val.append(numSpan);
item._valueEl = numSpan;
let input;
if (item.type === 'checkbox') {
input = el('label', 'switch');
const cb = document.createElement('input');
cb.type = 'checkbox';
cb.checked = !!currentValue(item);
cb.checked = !!effectiveValue(item);
cb.addEventListener('change', () => onControlChange(item, cb.checked));
const slider = el('span', 'switch-track');
input.append(cb, slider);
val.textContent = cb.checked ? 'an' : 'aus';
item._input = cb;
numSpan.textContent = cb.checked ? 'an' : 'aus';
row.append(input, val);
updateDirty(item);
return row;
}
@@ -273,34 +335,38 @@ function buildControl(item) {
opt.textContent = o.label;
input.append(opt);
});
input.value = String(currentValue(item));
input.value = String(effectiveValue(item));
input.addEventListener('change', () => onControlChange(item, Number(input.value)));
val.textContent = fmt(item, Number(input.value));
item._input = input;
numSpan.textContent = fmt(item, Number(input.value));
row.append(input, val);
updateDirty(item);
return row;
}
input = document.createElement('input');
input.type = item.type;
const cv = currentValue(item);
const cv = effectiveValue(item);
if (item.type === 'color') {
input.value = toHex(cv);
input.addEventListener('input', () => onControlChange(item, fromHex(input.value)));
val.textContent = toHex(cv);
numSpan.textContent = toHex(cv);
} else if (item.type === 'number') {
if (item.min != null) input.min = item.min;
if (item.max != null) input.max = item.max;
if (item.step != null) input.step = item.step;
input.value = cv;
input.addEventListener('change', () => onControlChange(item, Number(input.value)));
val.textContent = fmt(item, Number(cv));
numSpan.textContent = fmt(item, Number(cv));
} else { // range
input.min = item.min; input.max = item.max; input.step = item.step;
input.value = cv;
input.addEventListener('input', () => onControlChange(item, Number(input.value)));
val.textContent = fmt(item, Number(cv));
numSpan.textContent = fmt(item, Number(cv));
}
item._input = input;
row.append(input, val);
updateDirty(item);
return row;
}