9a61f55cb1
- Design-System (assets/css/design-system.css) mit 21 Komponenten, iPad-Responsive-Breakpoints, Touch-Ziele 36px, Music-Player, Glossar-Tooltips - Templates (sims/template.html + student/teacher-Dashboard) - Docs: module-interface.md (inkl. 4a Sprachregel, 4b Leichte Sprache, 4c iPad), content-architecture.md, crash-recovery.md, music-registry.md - Admin-Infrastruktur: admin-modules.html + api/admin-modules.php (Titel, Emoji, Bild, Status, Dauer, Alter pro Modul) - Inbox-System: _inbox/README.md + _status.md fuer Atlas + Briefings an Klima, Glossar, Lehrplan, Fluss - Zentrale SFX-Pipeline (scripts/generate-sounds.py) - DALL-E-Bilder: 8 Badges + 5 Glossar-Repraesentationsbilder (Querformat) - Logo + Inter-Font lokal - PHP-APIs: admin, glossar, levels, licenses, progress, waypoints, assignments, profile, tickets - Spielsprache entfernt (admin-modules, admin-levels, schueler) - Landing-Page-Bearbeitungen (Boote sichtbarer, Button-Hintergrund) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
359 lines
21 KiB
HTML
359 lines
21 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>GeoGraSim — Administration</title>
|
||
<link rel="icon" type="image/png" href="favicon-96x96.png" sizes="96x96">
|
||
<link rel="icon" type="image/svg+xml" href="favicon.svg">
|
||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
|
||
<link rel="stylesheet" href="assets/fonts/inter.css">
|
||
<style>
|
||
*{margin:0;padding:0;box-sizing:border-box}
|
||
body{font-family:'Inter',sans-serif;background:#f7f6f3;min-height:100vh}
|
||
|
||
.topbar{background:#fff;padding:.5rem 1rem;display:flex;align-items:center;gap:.6rem;border-bottom:1px solid rgba(0,0,0,.06);flex-wrap:wrap;position:sticky;top:0;z-index:100}
|
||
.topbar h1{font-size:.9rem;font-weight:800;color:#1f4e5a;display:flex;align-items:center;gap:.5rem}
|
||
.topbar h1 img{height:28px}
|
||
.topbar .spacer{flex:1}
|
||
.nav-btn{padding:.35rem .7rem;border:1.5px solid rgba(0,0,0,.08);border-radius:6px;background:#fff;font-size:.7rem;font-weight:600;cursor:pointer;color:#4a4a4a;text-decoration:none}
|
||
.nav-btn:hover{background:#dae8ec;color:#4a7c8a}
|
||
.nav-btn.on{background:#1f4e5a;color:#fff;border-color:#1f4e5a}
|
||
|
||
.wrap{max-width:1100px;margin:0 auto;padding:1rem}
|
||
.card{background:#fff;border-radius:10px;padding:1rem;box-shadow:0 1px 4px rgba(0,0,0,.03);border:1px solid rgba(0,0,0,.04);margin-bottom:1rem}
|
||
.card-h{font-size:.62rem;font-weight:700;color:#4a7c8a;text-transform:uppercase;letter-spacing:.05em;margin-bottom:.5rem}
|
||
.btn{padding:.4rem .8rem;border:none;border-radius:6px;background:#4a7c8a;color:#fff;font-size:.72rem;font-weight:700;cursor:pointer}
|
||
.btn:hover{background:#3a6470}
|
||
.btn-outline{background:transparent;color:#4a7c8a;border:1.5px solid #4a7c8a}
|
||
select,input[type=text],input[type=number]{padding:.35rem .5rem;border:1.5px solid rgba(0,0,0,.1);border-radius:6px;font-size:.75rem;font-family:inherit}
|
||
|
||
/* Sim Cards Grid */
|
||
.sim-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:.8rem;margin-bottom:1.5rem}
|
||
.sim-card{background:#fff;border-radius:12px;overflow:hidden;cursor:pointer;border:2px solid rgba(0,0,0,.05);transition:all .2s}
|
||
.sim-card:hover{border-color:#4a7c8a;transform:translateY(-2px);box-shadow:0 6px 20px rgba(74,124,138,.1)}
|
||
.sim-card.active{border-color:#1f4e5a;box-shadow:0 0 0 3px rgba(31,78,90,.15)}
|
||
.sim-card img{width:100%;height:100px;object-fit:cover}
|
||
.sim-card-body{padding:.6rem}
|
||
.sim-card-body h3{font-size:.78rem;font-weight:700;color:#1f4e5a}
|
||
.sim-card-body p{font-size:.6rem;color:#8a8a8a}
|
||
|
||
/* Slider rows */
|
||
.slider-row{display:grid;grid-template-columns:140px 1fr 40px;gap:8px;align-items:center;margin-bottom:.4rem;font-size:.75rem}
|
||
.slider-row label{color:#4a4a4a}
|
||
.slider-row input[type=range]{width:100%;accent-color:#4a7c8a}
|
||
.slider-row .sval{text-align:right;font-weight:700;color:#1f4e5a;font-variant-numeric:tabular-nums}
|
||
|
||
/* Waypoint rows */
|
||
.wp-row{display:grid;grid-template-columns:100px 1fr 80px 80px 70px 60px 110px 50px;gap:5px;align-items:center;padding:.4rem .2rem;border-bottom:1px solid rgba(0,0,0,.03);font-size:.7rem}
|
||
.wp-row:hover{background:#f0f8fa}
|
||
.wp-header{font-weight:700;font-size:.58rem;color:#8a8a8a;text-transform:uppercase;border-bottom:2px solid #dae8ec}
|
||
.wp-type{font-size:.58rem;padding:1px 5px;border-radius:3px;font-weight:600}
|
||
.wp-type-base{background:#f2eacc;color:#8a7020}
|
||
.wp-type-city{background:#dae8ec;color:#4a7c8a}
|
||
.wp-type-village{background:#f0eeea;color:#666}
|
||
.wp-type-peak{background:#dceadd;color:#3a6b3e}
|
||
.wp-type-target{background:#f2ddd8;color:#8a3a2a}
|
||
.wp-map-mini{width:110px;height:65px;border-radius:5px;border:1px solid rgba(0,0,0,.06)}
|
||
|
||
/* Level cards */
|
||
.level-card{border:1.5px solid rgba(0,0,0,.06);border-radius:8px;padding:.7rem;margin-bottom:.4rem}
|
||
.level-card h4{font-size:.78rem;font-weight:700;color:#1f4e5a}
|
||
.level-card .meta{font-size:.62rem;color:#8a8a8a}
|
||
|
||
/* Modal */
|
||
.modal{position:fixed;inset:0;background:rgba(0,0,0,.4);display:flex;align-items:center;justify-content:center;z-index:1000}
|
||
.modal-card{background:#fff;border-radius:14px;padding:1.2rem;max-width:600px;width:95%;max-height:90vh;overflow-y:auto}
|
||
.modal-card h3{font-size:.95rem;font-weight:800;color:#1f4e5a;margin-bottom:.6rem}
|
||
.modal-card label{display:block;font-size:.65rem;font-weight:600;color:#4a4a4a;margin-top:.5rem}
|
||
.modal-card input,.modal-card select{width:100%}
|
||
#editMap{height:400px;border-radius:8px;margin-top:.5rem}
|
||
.hidden{display:none}
|
||
|
||
/* Tab content */
|
||
.tab-content{display:none}
|
||
.tab-content.active{display:block}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<div class="topbar">
|
||
<h1><img src="assets/img/geograsim_t.svg" alt="">Administration</h1>
|
||
<span class="spacer"></span>
|
||
<a href="#" class="nav-btn on" onclick="showTab('sims')">Simulationen</a>
|
||
<a href="#" class="nav-btn" onclick="showTab('waypoints')">📍 Waypoints</a>
|
||
<a href="heli" class="nav-btn" target="_blank">🗺️ Gesamtkarte</a>
|
||
<a href="admin-modules.html" class="nav-btn">🧩 Module</a>
|
||
<a href="admin-licenses.html" class="nav-btn">🔑 Lizenzen</a>
|
||
<a href="admin.html" class="nav-btn">Logout</a>
|
||
</div>
|
||
|
||
<div class="wrap">
|
||
|
||
<!-- TAB: Simulationen -->
|
||
<div class="tab-content active" id="tab-sims">
|
||
<div class="card">
|
||
<div class="card-h">Simulationen — Wähle ein Modul zur Konfiguration</div>
|
||
<div class="sim-grid" id="simGrid"></div>
|
||
</div>
|
||
<div id="simDetail"></div>
|
||
</div>
|
||
|
||
<!-- TAB: Waypoints -->
|
||
<div class="tab-content" id="tab-waypoints">
|
||
<div class="card">
|
||
<div class="card-h">📍 Waypoints — Koordinaten (<span id="wp-count">0</span>)</div>
|
||
<div style="display:flex;gap:.4rem;margin-bottom:.5rem;flex-wrap:wrap">
|
||
<select id="wp-filter-region" onchange="loadWaypoints()"><option value="">Alle</option><option value="vorarlberg">Vorarlberg</option><option value="tirol">Tirol</option></select>
|
||
<select id="wp-filter-type" onchange="loadWaypoints()"><option value="">Alle Typen</option><option value="base">Basis</option><option value="city">Stadt</option><option value="village">Dorf</option><option value="peak">Gipfel</option><option value="target">Einsatzort</option></select>
|
||
<button class="btn" onclick="openAdd()">+ Waypoint</button>
|
||
</div>
|
||
<div class="wp-row wp-header"><div>Key</div><div>Name</div><div>Lat</div><div>Lon</div><div>Region</div><div>Typ</div><div>Karte</div><div></div></div>
|
||
<div id="wp-list"></div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!-- Edit Modal -->
|
||
<div class="modal hidden" id="editModal">
|
||
<div class="modal-card">
|
||
<h3 id="editTitle">Waypoint bearbeiten</h3>
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:.5rem">
|
||
<div><label>Key</label><input type="text" id="e-key" placeholder="bregenz"></div>
|
||
<div><label>Name</label><input type="text" id="e-name" placeholder="Bregenz"></div>
|
||
</div>
|
||
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:.5rem">
|
||
<div><label>Lat</label><input type="number" id="e-lat" step="0.00001"></div>
|
||
<div><label>Lon</label><input type="number" id="e-lon" step="0.00001"></div>
|
||
<div><label>Region</label><select id="e-region"><option value="vorarlberg">Vorarlberg</option><option value="tirol">Tirol</option></select></div>
|
||
</div>
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:.5rem">
|
||
<div><label>Typ</label><select id="e-type"><option value="village">Dorf</option><option value="city">Stadt</option><option value="base">Basis</option><option value="peak">Gipfel</option><option value="target">Einsatzort</option><option value="pass">Pass</option></select></div>
|
||
<div><label>Info</label><input type="text" id="e-info" placeholder="Beschreibung"></div>
|
||
</div>
|
||
<p style="font-size:.68rem;color:#8a8a8a;margin-top:.4rem">Klicke auf die Karte um die Position zu setzen:</p>
|
||
<div id="editMap"></div>
|
||
<div style="display:flex;gap:.4rem;margin-top:.6rem">
|
||
<button class="btn" onclick="saveEdit()">Speichern</button>
|
||
<button class="btn btn-outline" onclick="closeEdit()">Abbrechen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||
<script>
|
||
var BASE = (window.__GGS__?.baseUrl) || '';
|
||
if (!BASE) { var p = location.pathname, i = p.indexOf('/geograsim'); if (i !== -1) { var rest = p.substring(i + '/geograsim'.length); BASE = rest.indexOf('/App/') === 0 ? p.substring(0, i) + '/geograsim/App' : p.substring(0, i) + '/geograsim'; } }
|
||
|
||
async function api(endpoint, data) {
|
||
var opts = data ? {method:'POST',headers:{'Content-Type':'application/json'},credentials:'same-origin',body:JSON.stringify(data)} : {credentials:'same-origin'};
|
||
return (await fetch(BASE + '/api/' + endpoint, opts)).json();
|
||
}
|
||
|
||
// ==========================================
|
||
// TABS
|
||
// ==========================================
|
||
function showTab(tab) {
|
||
document.querySelectorAll('.tab-content').forEach(function(el) { el.classList.toggle('active', el.id === 'tab-' + tab); });
|
||
document.querySelectorAll('.nav-btn').forEach(function(b) { b.classList.remove('on'); });
|
||
event.target.classList.add('on');
|
||
if (tab === 'waypoints') loadWaypoints();
|
||
}
|
||
|
||
// ==========================================
|
||
// SIMULATIONEN
|
||
// ==========================================
|
||
var SIMS = [
|
||
{id:'klima', name:'Klimawächter V2', icon:'🌍', img:'assets/img/card-climate.png',
|
||
params:{
|
||
start_budget:{min:100,max:2000,def:600,label:'Start-Budget (Mio €)'},
|
||
start_population:{min:1000,max:20000,def:6000,label:'Start-Bevölkerung'},
|
||
income_per_10k:{min:50,max:500,def:220,label:'Einnahmen pro 10.000 Bürger'},
|
||
budget_loss:{min:-2000,max:0,def:-500,label:'Pleite-Grenze (Mio €, negativ)'},
|
||
event_frequency:{min:0,max:1,def:0.3,step:0.1,label:'Ereignis-Häufigkeit (0–1)'},
|
||
time_limit:{min:30,max:150,def:75,label:'Simulationsdauer (Runden)'}
|
||
}},
|
||
{id:'sim-05', name:'Klimawächter 2D (V1)', icon:'🌡️', img:'assets/img/card-climate.png',
|
||
params:{start_co2:{min:300,max:600,def:425,label:'Start-CO₂ (ppm)'},start_temp:{min:14,max:18,def:15.2,label:'Start-Temperatur (°C)'},budget:{min:200,max:2000,def:950,label:'Budget (Mrd)'},income:{min:100,max:500,def:290,label:'Einkommen/Jahr'},damage_mult:{min:0.5,max:3,def:1,label:'Schadensmultiplikator'}}},
|
||
{id:'sim-05-3d', name:'Klimawächter 3D (V1)', icon:'🌊', img:'assets/img/card-climate.png',
|
||
params:{start_co2:{min:300,max:600,def:425,label:'Start-CO₂ (ppm)'},start_temp:{min:14,max:18,def:15.2,label:'Start-Temperatur (°C)'},budget:{min:200,max:2000,def:950,label:'Budget (Mrd)'},income:{min:100,max:500,def:290,label:'Einkommen/Jahr'}}},
|
||
{id:'sim-07', name:'Erdbeben', icon:'🌋', img:'assets/img/card-tectonic.png',
|
||
params:{quake_freq:{min:1,max:10,def:3,label:'Erdbeben-Häufigkeit'},build_cost:{min:50,max:500,def:200,label:'Baukosten'},population:{min:1000,max:50000,def:10000,label:'Bevölkerung'}}},
|
||
{id:'sim-09', name:'Energiemix', icon:'⚡', img:'assets/img/card-energy.png',
|
||
params:{demand_growth:{min:0,max:5,def:2,label:'Nachfrage-Wachstum (%/Jahr)'},co2_target:{min:20,max:80,def:50,label:'CO₂-Reduktionsziel (%)'},budget:{min:500,max:5000,def:2000,label:'Budget (Mio)'}}},
|
||
{id:'sim-10', name:'Lieferketten', icon:'👕', img:'assets/img/card-globe.png',
|
||
params:{price_weight:{min:0,max:100,def:40,label:'Preis-Gewichtung (%)'},fair_weight:{min:0,max:100,def:30,label:'Fairness-Gewichtung (%)'},co2_weight:{min:0,max:100,def:30,label:'CO₂-Gewichtung (%)'}}},
|
||
{id:'sim-11', name:'Regenwald', icon:'🌴', img:'assets/img/card-climate.png', params:{}},
|
||
{id:'sim-12', name:'Flussmanagement', icon:'🏞️', img:'assets/img/card-fluss.png',
|
||
params:{flood_freq:{min:1,max:10,def:3,label:'Hochwasser-Häufigkeit'},budget:{min:500,max:5000,def:2000,label:'Budget'},bio_weight:{min:0,max:100,def:40,label:'Biodiversitäts-Gewichtung (%)'}}},
|
||
{id:'sim-13', name:'Stadt & Raumplanung', icon:'🏗️', img:'assets/img/card-city.png',
|
||
params:{rounds:{min:5,max:20,def:10,label:'Runden'},budget_per_round:{min:100,max:1000,def:300,label:'Budget/Runde'},grid_size:{min:6,max:12,def:8,label:'Raster-Größe'}}},
|
||
{id:'sim-14', name:'Helikopter-Navigation', icon:'🚁', img:'assets/img/card-heli.png',
|
||
params:{houses:{min:0,max:14,def:8,label:'Häuser'},trees_tall:{min:0,max:14,def:6,label:'Hohe Bäume'},flocks:{min:0,max:8,def:2,label:'Vogelschwärme'},airliners:{min:0,max:3,def:1,label:'Verkehrsflugzeuge'},sportplanes:{min:0,max:4,def:2,label:'Sportflieger'},storms:{min:0,max:4,def:1,label:'Gewitterzellen'}}},
|
||
];
|
||
|
||
var selectedSim = null;
|
||
|
||
function renderSimGrid() {
|
||
var html = '';
|
||
SIMS.forEach(function(s) {
|
||
var cls = selectedSim === s.id ? ' active' : '';
|
||
html += '<div class="sim-card'+cls+'" onclick="selectSim(\''+s.id+'\')">';
|
||
html += '<img src="'+s.img+'" alt="">';
|
||
html += '<div class="sim-card-body"><h3>'+s.icon+' '+s.name+'</h3><p>'+Object.keys(s.params).length+' Parameter</p></div>';
|
||
html += '</div>';
|
||
});
|
||
document.getElementById('simGrid').innerHTML = html;
|
||
}
|
||
|
||
async function selectSim(id) {
|
||
selectedSim = id;
|
||
renderSimGrid();
|
||
var sim = SIMS.find(function(s){return s.id===id;});
|
||
if (!sim) return;
|
||
|
||
// Bestehende Levels laden
|
||
var levels = await api('levels?game_id=' + id);
|
||
if (levels.error) levels = [];
|
||
|
||
var html = '<div class="card"><div class="card-h">'+sim.icon+' '+sim.name+' — Level-Konfiguration</div>';
|
||
var params = sim.params || {};
|
||
var paramKeys = Object.keys(params);
|
||
|
||
// Für jedes der 3 Standard-Levels (Leicht/Mittel/Schwer) alle Schieberegler anzeigen
|
||
var levelNames = ['Leicht','Mittel','Schwer'];
|
||
var levelColors = ['#5a8a5e','#c4a35a','#c85c4a'];
|
||
|
||
levelNames.forEach(function(lName, li) {
|
||
var existingLevel = levels.find(function(l){return l.level_name===lName;});
|
||
var existingParams = existingLevel ? existingLevel.params : {};
|
||
var levelId = existingLevel ? existingLevel.id : 0;
|
||
|
||
html += '<div style="border-left:4px solid '+levelColors[li]+';padding:.6rem .8rem;margin-bottom:.6rem;background:rgba(0,0,0,.01);border-radius:0 8px 8px 0">';
|
||
html += '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:.4rem">';
|
||
html += '<strong style="font-size:.85rem;color:'+levelColors[li]+'">'+lName+'</strong>';
|
||
html += '<button class="btn" style="font-size:.62rem;padding:.2rem .6rem" onclick="saveLevel(\''+id+'\',\''+lName+'\','+li+','+levelId+')">Speichern</button>';
|
||
html += '</div>';
|
||
|
||
paramKeys.forEach(function(key) {
|
||
var p = params[key];
|
||
var val = existingParams[key] !== undefined ? existingParams[key] : p.def;
|
||
var sliderId = 'sl-'+li+'-'+key;
|
||
var valId = 'sv-'+li+'-'+key;
|
||
html += '<div class="slider-row">';
|
||
html += '<label>'+p.label+'</label>';
|
||
html += '<input type="range" min="'+p.min+'" max="'+p.max+'" value="'+val+'" step="'+(p.max-p.min > 10 ? 1 : 0.1)+'" id="'+sliderId+'" oninput="document.getElementById(\''+valId+'\').textContent=this.value">';
|
||
html += '<span class="sval" id="'+valId+'">'+val+'</span>';
|
||
html += '</div>';
|
||
});
|
||
html += '</div>';
|
||
});
|
||
|
||
html += '</div>';
|
||
|
||
document.getElementById('simDetail').innerHTML = html;
|
||
}
|
||
|
||
async function saveLevel(gameId, levelName, levelIndex, existingId) {
|
||
var sim = SIMS.find(function(s){return s.id===gameId;});
|
||
var params = {};
|
||
Object.keys(sim.params||{}).forEach(function(key) {
|
||
var el = document.getElementById('sl-'+levelIndex+'-'+key);
|
||
if (el) params[key] = parseFloat(el.value);
|
||
});
|
||
|
||
var r = await api('levels', {action:'save', gameId:gameId, levelId:existingId||undefined, levelName:levelName, scenario:'standard', params:params, sortOrder:levelIndex+1});
|
||
if (r.error) { alert(r.error); return; }
|
||
alert(levelName + ' gespeichert!');
|
||
selectSim(gameId);
|
||
}
|
||
|
||
async function deleteLevel(id, gameId) {
|
||
if (!confirm('Level löschen?')) return;
|
||
await api('levels', {action:'delete', levelId:id});
|
||
selectSim(gameId);
|
||
}
|
||
|
||
// ==========================================
|
||
// WAYPOINTS
|
||
// ==========================================
|
||
var allWaypoints = [];
|
||
async function loadWaypoints() {
|
||
var region = document.getElementById('wp-filter-region').value;
|
||
var type = document.getElementById('wp-filter-type').value;
|
||
var url = 'waypoints?';
|
||
if (region) url += 'region=' + region + '&';
|
||
if (type) url += 'type=' + type;
|
||
var wps = await api(url);
|
||
if (wps.error) wps = [];
|
||
allWaypoints = wps;
|
||
document.getElementById('wp-count').textContent = wps.length;
|
||
|
||
var html = '';
|
||
wps.forEach(function(w) {
|
||
var typeClass = 'wp-type-' + w.wp_type;
|
||
var typeLabel = {base:'Basis',city:'Stadt',village:'Dorf',peak:'Gipfel',target:'Ziel',pass:'Pass'}[w.wp_type] || w.wp_type;
|
||
var lat = parseFloat(w.lat).toFixed(5), lon = parseFloat(w.lon).toFixed(5);
|
||
html += '<div class="wp-row">';
|
||
html += '<div style="font-family:monospace;font-size:.65rem;color:#4a7c8a;font-weight:600">'+w.wp_key+'</div>';
|
||
html += '<div><strong>'+w.name+'</strong>'+(w.info?'<br><span style="font-size:.58rem;color:#8a8a8a">'+w.info+'</span>':'')+'</div>';
|
||
html += '<div style="font-variant-numeric:tabular-nums;font-size:.65rem">'+lat+'</div>';
|
||
html += '<div style="font-variant-numeric:tabular-nums;font-size:.65rem">'+lon+'</div>';
|
||
html += '<div style="font-size:.58rem;color:#8a8a8a">'+w.region+'</div>';
|
||
html += '<div><span class="wp-type '+typeClass+'">'+typeLabel+'</span></div>';
|
||
html += '<div><img class="wp-map-mini" src="https://staticmap.openstreetmap.de/staticmap.php?center='+lat+','+lon+'&zoom=13&size=110x65&markers='+lat+','+lon+',ol-marker" loading="lazy"></div>';
|
||
html += '<div><button style="padding:.2rem .4rem;font-size:.6rem;border:1px solid rgba(0,0,0,.08);border-radius:4px;background:#fff;cursor:pointer" onclick="openEdit(\''+w.wp_key+'\')">✏️</button></div>';
|
||
html += '</div>';
|
||
});
|
||
document.getElementById('wp-list').innerHTML = html;
|
||
}
|
||
|
||
var editMap = null, editMarker = null;
|
||
function openEdit(key) {
|
||
var w = allWaypoints.find(function(x){return x.wp_key===key;});
|
||
if (!w) return;
|
||
document.getElementById('editTitle').textContent = 'Bearbeiten: ' + w.name;
|
||
document.getElementById('e-key').value = w.wp_key;
|
||
document.getElementById('e-name').value = w.name;
|
||
document.getElementById('e-lat').value = parseFloat(w.lat);
|
||
document.getElementById('e-lon').value = parseFloat(w.lon);
|
||
document.getElementById('e-region').value = w.region;
|
||
document.getElementById('e-type').value = w.wp_type;
|
||
document.getElementById('e-info').value = w.info || '';
|
||
document.getElementById('editModal').classList.remove('hidden');
|
||
setTimeout(function() {
|
||
if (!editMap) {
|
||
editMap = L.map('editMap').setView([w.lat, w.lon], 14);
|
||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{attribution:'© OSM',maxZoom:18}).addTo(editMap);
|
||
editMap.on('click',function(e){document.getElementById('e-lat').value=e.latlng.lat.toFixed(5);document.getElementById('e-lon').value=e.latlng.lng.toFixed(5);if(editMarker)editMap.removeLayer(editMarker);editMarker=L.marker(e.latlng).addTo(editMap);});
|
||
} else { editMap.setView([w.lat,w.lon],14); editMap.invalidateSize(); }
|
||
if(editMarker)editMap.removeLayer(editMarker);
|
||
editMarker=L.marker([w.lat,w.lon]).addTo(editMap);
|
||
}, 200);
|
||
}
|
||
function openAdd() {
|
||
document.getElementById('editTitle').textContent = 'Neuer Waypoint';
|
||
['e-key','e-name','e-lat','e-lon','e-info'].forEach(function(id){document.getElementById(id).value='';});
|
||
document.getElementById('editModal').classList.remove('hidden');
|
||
setTimeout(function(){if(!editMap){editMap=L.map('editMap').setView([47.3,10.5],9);L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{attribution:'© OSM',maxZoom:18}).addTo(editMap);editMap.on('click',function(e){document.getElementById('e-lat').value=e.latlng.lat.toFixed(5);document.getElementById('e-lon').value=e.latlng.lng.toFixed(5);if(editMarker)editMap.removeLayer(editMarker);editMarker=L.marker(e.latlng).addTo(editMap);});}else{editMap.setView([47.3,10.5],9);editMap.invalidateSize();}if(editMarker){editMap.removeLayer(editMarker);editMarker=null;}},200);
|
||
}
|
||
function closeEdit(){document.getElementById('editModal').classList.add('hidden');}
|
||
async function saveEdit() {
|
||
var r = await api('waypoints',{action:'save',wpKey:document.getElementById('e-key').value.trim().toLowerCase().replace(/[^a-z0-9_]/g,''),name:document.getElementById('e-name').value.trim(),lat:parseFloat(document.getElementById('e-lat').value),lon:parseFloat(document.getElementById('e-lon').value),region:document.getElementById('e-region').value,wpType:document.getElementById('e-type').value,info:document.getElementById('e-info').value.trim()});
|
||
if(r.error){alert(r.error);return;}
|
||
closeEdit(); loadWaypoints();
|
||
}
|
||
|
||
// ==========================================
|
||
// INIT
|
||
// ==========================================
|
||
fetch(BASE+'/api/admin',{method:'POST',headers:{'Content-Type':'application/json'},credentials:'same-origin',body:JSON.stringify({action:'status'})})
|
||
.then(function(r){return r.json()}).then(function(r){
|
||
if(!r.authenticated){location.href='admin.html';return;}
|
||
renderSimGrid();
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|