Tutorial Patch 2: Pfeile, Cursor-Z-Index, Modal-Scroll, Karteninteraktion, Layout-Demo, Theme-Toggle
- Schritt 3: Bubble repositioniert sich auf Modal nach Oeffnung - Schritt 5: Cursor-Z-Index ueber Dropdown (999999) - Schritt 7ff: Modal scrollt automatisch zu Feldern (async scrollModalTo) - Schritt 20: Goldener Rahmen um gesamte Faktencheck-Kachel - Schritt 21: Timeline-Kachel wird temporaer vergroessert - Schritt 23: Alle Karteninteraktionen deaktiviert (kein Zoom/Click) - Schritt 25: Drag nach rechts + zurueck, dann Resize vom Original - Schritt 26: Theme-Toggle-Simulation (hell/dunkel/zurueck) - Schritt 27: Button bleibt sichtbar nach Quellenverwaltung-Oeffnung - Spotlight ausgeblendet waehrend Layout-Demo
Dieser Commit ist enthalten in:
@@ -554,18 +554,36 @@ const Tutorial = {
|
||||
id: 'new-incident-btn',
|
||||
target: '#new-incident-btn',
|
||||
title: 'Neue Lage anlegen',
|
||||
text: 'Mit diesem Button öffnen Sie das Formular zur Erstellung einer neuen Lage. '
|
||||
text: 'Mit diesem Button \u00f6ffnen Sie das Formular zur Erstellung einer neuen Lage. '
|
||||
+ 'Wir gehen jetzt gemeinsam alle Felder durch.',
|
||||
position: 'right',
|
||||
onEnter: function() {
|
||||
Tutorial._stepTimeout(function() {
|
||||
var overlay = document.getElementById('modal-new');
|
||||
if (overlay && !overlay.classList.contains('active')) overlay.classList.add('active');
|
||||
// Bubble auf Modal umpositionieren nach Oeffnung
|
||||
Tutorial._stepTimeout(function() {
|
||||
var modal = document.querySelector('#modal-new .modal');
|
||||
if (modal) {
|
||||
var step = Tutorial._steps[Tutorial._currentStep];
|
||||
step._origTarget = step.target;
|
||||
step.target = '#modal-new .modal';
|
||||
step.position = 'left';
|
||||
Tutorial._positionBubble(step);
|
||||
}
|
||||
}, 400);
|
||||
}, 1500);
|
||||
},
|
||||
onExit: function() {
|
||||
var overlay = document.getElementById('modal-new');
|
||||
if (overlay) overlay.classList.remove('active');
|
||||
// Target zuruecksetzen
|
||||
var step = Tutorial._steps[2];
|
||||
if (step && step._origTarget) {
|
||||
step.target = step._origTarget;
|
||||
step.position = 'right';
|
||||
delete step._origTarget;
|
||||
}
|
||||
},
|
||||
},
|
||||
// 3 - Titel und Beschreibung (Cursor-Demo)
|
||||
@@ -883,7 +901,7 @@ const Tutorial = {
|
||||
// Faktencheck: Status-Demo
|
||||
{
|
||||
id: 'faktencheck-detail',
|
||||
target: '.factcheck-item[data-fc-status="confirmed"]',
|
||||
target: '[gs-id="faktencheck"]',
|
||||
title: 'Faktencheck-Eintrag',
|
||||
text: 'Jeder Faktencheck-Eintrag besteht aus:<br><br>'
|
||||
+ '<strong>Status-Symbol</strong> - Farbcodiert f\u00fcr schnelle Einordnung<br>'
|
||||
@@ -913,9 +931,26 @@ const Tutorial = {
|
||||
position: 'top',
|
||||
disableNav: true,
|
||||
onEnter: function() {
|
||||
// Timeline-Kachel vergroessern damit Detail-Panel sichtbar
|
||||
var tile = document.querySelector('[gs-id="timeline"]');
|
||||
if (tile && typeof LayoutManager !== 'undefined' && LayoutManager._grid) {
|
||||
var node = LayoutManager._grid.engine.nodes.find(function(n) { return n.el === tile; });
|
||||
if (node) {
|
||||
Tutorial._savedTimelineH = node.h;
|
||||
LayoutManager._grid.update(tile, { h: Math.max(node.h, 4) });
|
||||
}
|
||||
}
|
||||
Tutorial._runDemo(Tutorial._simulateTimeline);
|
||||
},
|
||||
onExit: function() {
|
||||
// Kachel-Hoehe wiederherstellen
|
||||
if (Tutorial._savedTimelineH) {
|
||||
var tile = document.querySelector('[gs-id="timeline"]');
|
||||
if (tile && typeof LayoutManager !== 'undefined' && LayoutManager._grid) {
|
||||
LayoutManager._grid.update(tile, { h: Tutorial._savedTimelineH });
|
||||
}
|
||||
delete Tutorial._savedTimelineH;
|
||||
}
|
||||
Tutorial._clearSubHighlights();
|
||||
Tutorial._hideCursor();
|
||||
},
|
||||
@@ -1004,6 +1039,8 @@ const Tutorial = {
|
||||
position: 'right',
|
||||
disableNav: true,
|
||||
onEnter: function() {
|
||||
// Spotlight ausblenden waehrend Drag-Demo
|
||||
Tutorial._els.spotlight.style.opacity = '0';
|
||||
Tutorial._runDemo(Tutorial._simulateLayoutDemo);
|
||||
},
|
||||
onExit: function() {
|
||||
@@ -1013,14 +1050,26 @@ const Tutorial = {
|
||||
Tutorial._hideCursor();
|
||||
},
|
||||
},
|
||||
// 20 - Theme
|
||||
// Design umschalten
|
||||
{
|
||||
id: 'theme',
|
||||
target: '#theme-toggle',
|
||||
title: 'Design umschalten',
|
||||
text: 'Wechseln Sie zwischen dem dunklen und hellen Design. '
|
||||
+ 'Ihre Auswahl wird automatisch gespeichert und beim nächsten Besuch beibehalten.',
|
||||
+ 'Ihre Auswahl wird automatisch gespeichert und beim n\u00e4chsten Besuch beibehalten.',
|
||||
position: 'bottom',
|
||||
disableNav: true,
|
||||
onEnter: function() {
|
||||
Tutorial._runDemo(Tutorial._simulateThemeToggle);
|
||||
},
|
||||
onExit: function() {
|
||||
Tutorial._hideCursor();
|
||||
// Sicherstellen dass Dark-Theme aktiv ist
|
||||
if (document.documentElement.getAttribute('data-theme') === 'light') {
|
||||
var btn = document.getElementById('theme-toggle');
|
||||
if (btn) btn.click();
|
||||
}
|
||||
},
|
||||
},
|
||||
// Quellen verwalten
|
||||
{
|
||||
@@ -1034,6 +1083,9 @@ const Tutorial = {
|
||||
+ '<strong>Quellen deaktivieren</strong> - Tempor\u00e4r oder dauerhaft ausschlie\u00dfen',
|
||||
position: 'top',
|
||||
onEnter: function() {
|
||||
// Button sichtbar halten per z-index
|
||||
var srcLink = document.querySelector('.sidebar-sources-link');
|
||||
if (srcLink) srcLink.style.zIndex = '9003';
|
||||
Tutorial._stepTimeout(function() {
|
||||
var overlay = document.getElementById('modal-sources');
|
||||
if (overlay && !overlay.classList.contains('active')) {
|
||||
@@ -1044,6 +1096,8 @@ const Tutorial = {
|
||||
}, 1500);
|
||||
},
|
||||
onExit: function() {
|
||||
var srcLink = document.querySelector('.sidebar-sources-link');
|
||||
if (srcLink) srcLink.style.zIndex = '';
|
||||
var overlay = document.getElementById('modal-sources');
|
||||
if (overlay) {
|
||||
overlay.classList.remove('active');
|
||||
@@ -1607,7 +1661,7 @@ const Tutorial = {
|
||||
|
||||
// Start weit herausgezoomt (Europa)
|
||||
this._demoMap = L.map(mapDiv, {
|
||||
zoomControl: true,
|
||||
zoomControl: false, dragging: false, touchZoom: false, doubleClickZoom: false, scrollWheelZoom: false, boxZoom: false, keyboard: false,
|
||||
attributionControl: true,
|
||||
}).setView([51.0, 10.0], 5);
|
||||
|
||||
@@ -1742,12 +1796,20 @@ const Tutorial = {
|
||||
_scrollModalTo(selector) {
|
||||
var el = document.querySelector(selector);
|
||||
var modalBody = document.querySelector('#modal-new .modal-body');
|
||||
if (!el || !modalBody) return;
|
||||
if (!el || !modalBody) return Promise.resolve();
|
||||
// Element-Position relativ zum sichtbaren Modal-Bereich
|
||||
var elRect = el.getBoundingClientRect();
|
||||
var bodyRect = modalBody.getBoundingClientRect();
|
||||
// Pruefen ob Element bereits sichtbar
|
||||
if (elRect.top >= bodyRect.top && elRect.bottom <= bodyRect.bottom) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
// Scroll-Ziel: Element mit Abstand oben im sichtbaren Bereich
|
||||
var offset = elRect.top - bodyRect.top + modalBody.scrollTop;
|
||||
var targetScroll = offset - Math.min(40, bodyRect.height * 0.2);
|
||||
var targetScroll = offset - 30;
|
||||
modalBody.scrollTo({ top: Math.max(0, targetScroll), behavior: 'smooth' });
|
||||
// Warten bis Scroll abgeschlossen
|
||||
return new Promise(function(resolve) { setTimeout(resolve, 500); });
|
||||
},
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
@@ -1795,6 +1857,7 @@ const Tutorial = {
|
||||
if (!sel) { this._demoRunning = false; this._enableNavAfterDemo(); return; }
|
||||
|
||||
var pos = await this._cursorToElement('#inc-type');
|
||||
this._els.cursor.style.zIndex = '999999';
|
||||
await this._wait(300);
|
||||
|
||||
// Simuliertes Dropdown
|
||||
@@ -1859,6 +1922,7 @@ const Tutorial = {
|
||||
await this._wait(250);
|
||||
if (dropdown.parentNode) dropdown.remove();
|
||||
|
||||
this._els.cursor.style.zIndex = '';
|
||||
this._hideCursor();
|
||||
this._demoRunning = false;
|
||||
this._enableNavAfterDemo();
|
||||
@@ -1869,7 +1933,7 @@ const Tutorial = {
|
||||
// -----------------------------------------------------------------------
|
||||
async _simulateFormSources() {
|
||||
this._demoRunning = true;
|
||||
this._scrollModalTo('#inc-international');
|
||||
await this._scrollModalTo('#inc-international');
|
||||
await this._wait(400);
|
||||
|
||||
// International-Toggle highlighten
|
||||
@@ -1903,7 +1967,7 @@ const Tutorial = {
|
||||
// -----------------------------------------------------------------------
|
||||
async _simulateFormVisibility() {
|
||||
this._demoRunning = true;
|
||||
this._scrollModalTo('#inc-visibility');
|
||||
await this._scrollModalTo('#inc-visibility');
|
||||
await this._wait(400);
|
||||
|
||||
var checkbox = document.getElementById('inc-visibility');
|
||||
@@ -1935,7 +1999,7 @@ const Tutorial = {
|
||||
// -----------------------------------------------------------------------
|
||||
async _simulateFormRefresh() {
|
||||
this._demoRunning = true;
|
||||
this._scrollModalTo('#inc-refresh-mode');
|
||||
await this._scrollModalTo('#inc-refresh-mode');
|
||||
await this._wait(400);
|
||||
|
||||
var refreshSelect = document.getElementById('inc-refresh-mode');
|
||||
@@ -1971,7 +2035,7 @@ const Tutorial = {
|
||||
// -----------------------------------------------------------------------
|
||||
async _simulateFormRetention() {
|
||||
this._demoRunning = true;
|
||||
this._scrollModalTo('#inc-retention');
|
||||
await this._scrollModalTo('#inc-retention');
|
||||
await this._wait(400);
|
||||
|
||||
var retentionInput = document.getElementById('inc-retention');
|
||||
@@ -1992,7 +2056,7 @@ const Tutorial = {
|
||||
// -----------------------------------------------------------------------
|
||||
async _simulateFormNotifications() {
|
||||
this._demoRunning = true;
|
||||
this._scrollModalTo('#inc-notify-summary');
|
||||
await this._scrollModalTo('#inc-notify-summary');
|
||||
await this._wait(400);
|
||||
|
||||
var checks = ['#inc-notify-summary', '#inc-notify-new-articles', '#inc-notify-status-change'];
|
||||
@@ -2423,6 +2487,8 @@ const Tutorial = {
|
||||
this._enableNavAfterDemo();
|
||||
},
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Layout Demo (Drag + Resize kombiniert)
|
||||
// -----------------------------------------------------------------------
|
||||
// Layout Demo (Drag + Resize kombiniert)
|
||||
// -----------------------------------------------------------------------
|
||||
@@ -2434,7 +2500,7 @@ const Tutorial = {
|
||||
var grid = typeof LayoutManager !== 'undefined' ? LayoutManager._grid : null;
|
||||
var self = this;
|
||||
|
||||
// Phase 1: Drag
|
||||
// Phase 1: Drag nach rechts (Lagebild <-> Faktencheck tauschen)
|
||||
var lbH = lbTile.querySelector('.card-header');
|
||||
if (lbH) {
|
||||
var lbR = lbH.getBoundingClientRect();
|
||||
@@ -2469,6 +2535,7 @@ const Tutorial = {
|
||||
});
|
||||
await this._wait(400);
|
||||
|
||||
// GridStack-Swap
|
||||
lbTile.style.transform = '';
|
||||
lbTile.style.transition = '';
|
||||
lbTile.style.zIndex = '';
|
||||
@@ -2476,18 +2543,71 @@ const Tutorial = {
|
||||
var ln = grid.engine.nodes.find(function(n){return n.el===lbTile;});
|
||||
var fn = grid.engine.nodes.find(function(n){return n.el===fcTile;});
|
||||
if (ln && fn) {
|
||||
var tmpX=ln.x, tmpY=ln.y;
|
||||
var tmpX=ln.x, tmpY=ln.y, tmpW=ln.w, tmpH=ln.h;
|
||||
grid.update(lbTile,{x:fn.x,y:fn.y});
|
||||
grid.update(fcTile,{x:tmpX,y:tmpY});
|
||||
grid.compact();
|
||||
}
|
||||
}
|
||||
await this._wait(600);
|
||||
|
||||
// Phase 1b: Zurueck tauschen
|
||||
lbTile = document.querySelector('[gs-id="lagebild"]');
|
||||
fcTile = document.querySelector('[gs-id="faktencheck"]');
|
||||
if (lbTile && fcTile) {
|
||||
lbH = lbTile.querySelector('.card-header');
|
||||
if (lbH) {
|
||||
lbR = lbH.getBoundingClientRect();
|
||||
fcR = fcTile.getBoundingClientRect();
|
||||
sx = lbR.left + lbR.width/2; sy = lbR.top + lbR.height/2;
|
||||
ex = fcR.left + fcR.width/2; ey = fcR.top + lbR.height/2;
|
||||
dx = ex - sx; dy = ey - sy;
|
||||
|
||||
await this._animateCursor(
|
||||
parseFloat(this._els.cursor.style.left),
|
||||
parseFloat(this._els.cursor.style.top), sx, sy, 400);
|
||||
await this._wait(200);
|
||||
|
||||
lbTile.style.transition = 'none';
|
||||
lbTile.style.zIndex = '9002';
|
||||
ms = null;
|
||||
await new Promise(function(resolve) {
|
||||
function f(ts) {
|
||||
if (!self._isActive) { resolve(); return; }
|
||||
if (!ms) ms = ts;
|
||||
var p = Math.min((ts-ms)/1000,1);
|
||||
var e2 = p<0.5?4*p*p*p:1-Math.pow(-2*p+2,3)/2;
|
||||
lbTile.style.transform = 'translate('+(dx*e2)+'px,'+(dy*e2)+'px)';
|
||||
self._els.cursor.style.left = (sx+dx*e2)+'px';
|
||||
self._els.cursor.style.top = (sy+dy*e2)+'px';
|
||||
if (p<1) requestAnimationFrame(f); else resolve();
|
||||
}
|
||||
requestAnimationFrame(f);
|
||||
});
|
||||
await this._wait(300);
|
||||
|
||||
lbTile.style.transform = '';
|
||||
lbTile.style.transition = '';
|
||||
lbTile.style.zIndex = '';
|
||||
if (grid) {
|
||||
var ln2 = grid.engine.nodes.find(function(n){return n.el===lbTile;});
|
||||
var fn2 = grid.engine.nodes.find(function(n){return n.el===fcTile;});
|
||||
if (ln2 && fn2) {
|
||||
var tx2=ln2.x, ty2=ln2.y;
|
||||
grid.update(lbTile,{x:fn2.x,y:fn2.y});
|
||||
grid.update(fcTile,{x:tx2,y:ty2});
|
||||
grid.compact();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._els.cursor.classList.remove('tutorial-cursor-grabbing');
|
||||
this._els.cursor.classList.add('tutorial-cursor-default');
|
||||
await this._wait(800);
|
||||
}
|
||||
|
||||
// Phase 2: Resize
|
||||
// Phase 2: Resize Lagebild stark vergroessern
|
||||
lbTile = document.querySelector('[gs-id="lagebild"]');
|
||||
if (lbTile) {
|
||||
var tr = lbTile.getBoundingClientRect();
|
||||
@@ -2521,13 +2641,14 @@ const Tutorial = {
|
||||
});
|
||||
await this._wait(600);
|
||||
|
||||
// GridStack-Resize
|
||||
lbTile.style.transform = '';
|
||||
lbTile.style.transformOrigin = '';
|
||||
lbTile.style.transition = '';
|
||||
lbTile.style.zIndex = '';
|
||||
if (grid) {
|
||||
var ln2 = grid.engine.nodes.find(function(n){return n.el===lbTile;});
|
||||
if (ln2) { grid.update(lbTile,{w:12,h:Math.max(ln2.h+3,6)}); grid.compact(); }
|
||||
var ln3 = grid.engine.nodes.find(function(n){return n.el===lbTile;});
|
||||
if (ln3) { grid.update(lbTile,{w:12,h:Math.max(ln3.h+3,6)}); grid.compact(); }
|
||||
}
|
||||
await this._wait(600);
|
||||
}
|
||||
@@ -2538,6 +2659,45 @@ const Tutorial = {
|
||||
},
|
||||
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Theme-Toggle Demo
|
||||
// -----------------------------------------------------------------------
|
||||
async _simulateThemeToggle() {
|
||||
this._demoRunning = true;
|
||||
await this._wait(400);
|
||||
|
||||
var btn = document.getElementById('theme-toggle');
|
||||
if (!btn) { this._demoRunning = false; this._enableNavAfterDemo(); return; }
|
||||
|
||||
var rect = btn.getBoundingClientRect();
|
||||
var tx = rect.left + rect.width / 2;
|
||||
var ty = rect.top + rect.height / 2;
|
||||
this._showCursor(tx - 40, ty - 30, 'default');
|
||||
await this._wait(300);
|
||||
await this._animateCursor(tx - 40, ty - 30, tx, ty, 400);
|
||||
await this._wait(300);
|
||||
|
||||
// Klick - zum hellen Design wechseln
|
||||
btn.click();
|
||||
await this._wait(2500);
|
||||
|
||||
// Zurueck zum dunklen Design
|
||||
rect = btn.getBoundingClientRect();
|
||||
tx = rect.left + rect.width / 2;
|
||||
ty = rect.top + rect.height / 2;
|
||||
await this._animateCursor(
|
||||
parseFloat(this._els.cursor.style.left),
|
||||
parseFloat(this._els.cursor.style.top), tx, ty, 300);
|
||||
await this._wait(300);
|
||||
btn.click();
|
||||
await this._wait(800);
|
||||
|
||||
this._hideCursor();
|
||||
this._demoRunning = false;
|
||||
this._enableNavAfterDemo();
|
||||
},
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Navigation nach Demo-Ende freigeben
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren