LS bekommt einen festen Platz unter Gaming
Das Modul stand nur unter "Meine Funktionen" — also solange, wie du es angeheftet laesst. Loest man die Nadel, ist es nur noch uebers Modul-Raster erreichbar. Fuer die groesste Seite im Panel zu wenig. Jetzt kann ein Modul `nav: true` setzen und bekommt damit einen Eintrag in der Seitenleiste, unter seiner eigenen Gruppe. Kein Sonderfall fuer LS: jedes Modul, das eine ganze Seite fuellt, kann sich so einsortieren. Dabei aufgefallen und mitgenommen: eine halbe Antwort von /api/modules/:id hat die ganze Config weiss gemacht. Der Ladepfad ist zwar sauber abgesichert — `fehler` und `!detail` fangen ab —, aber ein Objekt OHNE die Listen kommt durch beide Wachen und laesst dann `detail.werte.some(...)` werfen, was React mit dem gesamten Baum quittiert. Vier Listen mit `?? []` kosten nichts und machen aus dem Weissbild schlimmstenfalls eine leere Sektion. Zwei Werkzeug-Fallen nebenbei: `npx vite build` zieht mal das lokale Vite 6, mal ein gecachtes Vite 8 aus dem npx-Ordner — ab jetzt nur noch `npm --prefix frontend run build`. Und der laufende Preview-Server haelt dist/assets fest, was den Build mit EPERM abbricht; vorher stoppen. Geprueft: Seitenleiste zeigt Gaming mit Game-Server und LS, Klick oeffnet die Modulseite und markiert den Eintrag, kein Fehler in der Konsole. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -80,6 +80,13 @@ export default function ModulePage(props) {
|
||||
|
||||
if (fehler) return <p className="notice">Diese Funktion konnte nicht geladen werden.</p>;
|
||||
if (!detail) return <p className="notice">Lade …</p>;
|
||||
// Listen notfalls auffüllen. Eine halbe Antwort — abgeschnitten, veraltet,
|
||||
// von einem Proxy verstümmelt — hat sonst die ganze Config weiß gemacht:
|
||||
// `werte.some(...)` wirft, und React reisst den Baum mit.
|
||||
const felder = detail.felder ?? [];
|
||||
const texte = detail.texte ?? [];
|
||||
const werte = detail.werte ?? [];
|
||||
const missing = detail.missing ?? [];
|
||||
|
||||
const Icon = moduleIcon(detail.id);
|
||||
|
||||
@@ -97,7 +104,7 @@ export default function ModulePage(props) {
|
||||
async function werteSpeichern() {
|
||||
const geaendert = Object.fromEntries(
|
||||
Object.entries(werteEntwurf).filter(([wid, v]) => {
|
||||
const jetzt = detail.werte.find((w) => w.id === wid);
|
||||
const jetzt = werte.find((w) => w.id === wid);
|
||||
return jetzt && String(v) !== String(jetzt.value);
|
||||
})
|
||||
);
|
||||
@@ -125,7 +132,7 @@ export default function ModulePage(props) {
|
||||
}
|
||||
}
|
||||
|
||||
const werteOffen = detail.werte.some(
|
||||
const werteOffen = werte.some(
|
||||
(w) => werteEntwurf[w.id] !== undefined && String(werteEntwurf[w.id]) !== String(w.value)
|
||||
);
|
||||
|
||||
@@ -159,22 +166,22 @@ export default function ModulePage(props) {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{detail.enabled && detail.missing.length > 0 && (
|
||||
{detail.enabled && missing.length > 0 && (
|
||||
<p className="mod-missing with-icon" style={{ marginBottom: '1.2rem' }}>
|
||||
<IconWarning size={13} /> Fehlt noch: {detail.missing.map((m) => m.label).join(', ')}
|
||||
<IconWarning size={13} /> Fehlt noch: {missing.map((m) => m.label).join(', ')}
|
||||
</p>
|
||||
)}
|
||||
{detail.enabled && detail.missing.length === 0 && (
|
||||
{detail.enabled && missing.length === 0 && (
|
||||
<p className="mod-ready with-icon" style={{ marginBottom: '1.2rem' }}>
|
||||
<IconCheck size={13} /> Einsatzbereit
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Kanäle, Rollen und alles Weitere */}
|
||||
{detail.felder.length > 0 && (
|
||||
{felder.length > 0 && (
|
||||
<div className="modpage-block">
|
||||
<h3 className="settings-title">Einstellungen</h3>
|
||||
{detail.felder.filter((f) => f.art !== 'farbe').map((f) => (
|
||||
{felder.filter((f) => f.art !== 'farbe').map((f) => (
|
||||
<div className="field" key={f.setting}>
|
||||
{f.art !== 'schalter' && (
|
||||
<label htmlFor={`feld-${f.setting}`}>
|
||||
@@ -192,8 +199,8 @@ export default function ModulePage(props) {
|
||||
roleItems={roleItems}
|
||||
/>
|
||||
{/* Fehlt eine Voraussetzung, kann der Bot sie anlegen */}
|
||||
{f.pflicht && detail.missing.some((m) => m.setting === f.setting)
|
||||
&& detail.missing.find((m) => m.setting === f.setting)?.create && (
|
||||
{f.pflicht && missing.some((m) => m.setting === f.setting)
|
||||
&& missing.find((m) => m.setting === f.setting)?.create && (
|
||||
<button className="btn-mini btn-create" onClick={() => anlegen(f.setting)}>
|
||||
{f.art === 'rolle' ? 'Rolle anlegen' : 'Kanal anlegen'}
|
||||
</button>
|
||||
@@ -202,9 +209,9 @@ export default function ModulePage(props) {
|
||||
</div>
|
||||
))}
|
||||
|
||||
{detail.felder.some((f) => f.art === 'farbe') && (
|
||||
{felder.some((f) => f.art === 'farbe') && (
|
||||
<div className="wc-colors">
|
||||
{detail.felder.filter((f) => f.art === 'farbe').map((f) => (
|
||||
{felder.filter((f) => f.art === 'farbe').map((f) => (
|
||||
<Feld
|
||||
key={f.setting}
|
||||
feld={f}
|
||||
@@ -229,11 +236,11 @@ export default function ModulePage(props) {
|
||||
)}
|
||||
|
||||
{/* Texte dieser Funktion */}
|
||||
{detail.texte.length > 0 && (
|
||||
{texte.length > 0 && (
|
||||
<div className="modpage-block">
|
||||
<h3 className="settings-title">Texte</h3>
|
||||
<div className="tpl-list">
|
||||
{detail.texte.map((t) => {
|
||||
{texte.map((t) => {
|
||||
const entwurf = textEntwurf[t.id];
|
||||
const wert = entwurf ?? t.text;
|
||||
const geaendert = entwurf != null && entwurf !== t.text;
|
||||
@@ -294,11 +301,11 @@ export default function ModulePage(props) {
|
||||
)}
|
||||
|
||||
{/* Werte dieser Funktion */}
|
||||
{detail.werte.length > 0 && (
|
||||
{werte.length > 0 && (
|
||||
<div className="modpage-block">
|
||||
<h3 className="settings-title">Werte</h3>
|
||||
<div className="tune-list">
|
||||
{detail.werte.map((w) => {
|
||||
{werte.map((w) => {
|
||||
const wert = werteEntwurf[w.id] ?? w.value;
|
||||
const geaendert = werteEntwurf[w.id] !== undefined && String(werteEntwurf[w.id]) !== String(w.value);
|
||||
return (
|
||||
|
||||
@@ -4103,7 +4103,12 @@ export default function Settings({ me }) {
|
||||
|
||||
{TAB_SECTIONS.map((section) => {
|
||||
const items = foundTabs.filter((t) => t.section === section.id);
|
||||
if (items.length === 0) return null;
|
||||
// Module mit `nav` bekommen hier einen festen Platz —
|
||||
// sonst sind sie nur übers Raster erreichbar oder
|
||||
// solange man sie angeheftet lässt.
|
||||
const eigene = modules.filter((m) => m.nav && m.group === section.id
|
||||
&& (!query || m.name.toLowerCase().includes(query.toLowerCase())));
|
||||
if (items.length === 0 && eigene.length === 0) return null;
|
||||
return (
|
||||
<div className="settings-nav-group" key={section.id}>
|
||||
<span className="settings-nav-label">{section.label}</span>
|
||||
@@ -4117,6 +4122,19 @@ export default function Settings({ me }) {
|
||||
{t.label}
|
||||
</button>
|
||||
))}
|
||||
{eigene.map((m) => {
|
||||
const Icon = moduleIcon(m.id);
|
||||
return (
|
||||
<button
|
||||
key={m.id}
|
||||
className={`settings-nav-item ${openModule === m.id ? 'active' : ''}`}
|
||||
onClick={() => { setTabState('module'); setOpenModule(m.id); }}
|
||||
>
|
||||
<span className="settings-nav-icon"><Icon /></span>
|
||||
{m.name}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user