From 0c9ee1c144958d6bac1a4c0d7ce3aaa7c7025465 Mon Sep 17 00:00:00 2001 From: Claude Dev Date: Mon, 16 Mar 2026 17:22:30 +0100 Subject: [PATCH] Tutorial: Alle Demos mit _runDemo() absichern gegen haengenbleibende Navigation Neuer Helfer _runDemo(fn): Fuehrt async Demo-Methoden aus und faengt alle Fehler ab. Bei Fehler wird _demoRunning zurueckgesetzt und _enableNavAfterDemo aufgerufen, sodass der Weiter-Button immer erscheint. Alle 12 Demo-Aufrufe (FormTitleDesc, TypeSwitch, FormSources, FormVisibility, FormRefresh, FormRetention, FormNotifications, MapDemo, Drag, Resize, SourcesInfoIcon, SourcesActions) verwenden jetzt _runDemo statt direktem Aufruf. Zusaetzlich: - _cursorToElement gibt sichere Fallback-Koordinaten zurueck wenn Element nicht sichtbar (getBoundingClientRect width/height = 0) - _simulateFormTitleDesc wartet 600ms auf Modal-Rendering Co-Authored-By: Claude Opus 4.6 (1M context) --- src/static/dashboard.html | 2 +- src/static/js/tutorial.js | 40 ++++++++++++++++++++++++++------------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/src/static/dashboard.html b/src/static/dashboard.html index e5995ce..67d4a46 100644 --- a/src/static/dashboard.html +++ b/src/static/dashboard.html @@ -764,7 +764,7 @@ - + diff --git a/src/static/js/tutorial.js b/src/static/js/tutorial.js index 1dc5e38..5ff7103 100644 --- a/src/static/js/tutorial.js +++ b/src/static/js/tutorial.js @@ -493,6 +493,17 @@ const Tutorial = { this._stepTimers = []; }, + // Sichere Demo-Ausfuehrung: Faengt Fehler ab und stellt Navigation sicher + _runDemo(fn) { + var self = this; + fn.call(this).catch(function(e) { + self._hideCursor(); + self._clearSubHighlights(); + self._demoRunning = false; + self._enableNavAfterDemo(); + }); + }, + // ----------------------------------------------------------------------- // Scroll-Helfer: Element in den sichtbaren Bereich scrollen // ----------------------------------------------------------------------- @@ -575,7 +586,7 @@ const Tutorial = { if (modalBody) modalBody.scrollTop = 0; var t = document.getElementById('inc-title'); if (t) t.value = ''; var d = document.getElementById('inc-description'); if (d) d.value = ''; - Tutorial._simulateFormTitleDesc(); + Tutorial._runDemo(Tutorial._simulateFormTitleDesc); }, onExit: function() { Tutorial._clearSubHighlights(); @@ -600,7 +611,7 @@ const Tutorial = { if (modalBody) modalBody.scrollTo({ top: 0, behavior: 'smooth' }); Tutorial._stepTimeout(function() { Tutorial._highlightSub('#inc-type'); - Tutorial._simulateTypeSwitch(); + Tutorial._runDemo(Tutorial._simulateTypeSwitch); }, 500); }, onExit: function() { @@ -624,7 +635,7 @@ const Tutorial = { var overlay = document.getElementById('modal-new'); if (overlay && !overlay.classList.contains('active')) overlay.classList.add('active'); if (overlay) overlay.style.zIndex = '9002'; - Tutorial._simulateFormSources(); + Tutorial._runDemo(Tutorial._simulateFormSources); }, onExit: function() { Tutorial._clearSubHighlights(); @@ -645,7 +656,7 @@ const Tutorial = { var overlay = document.getElementById('modal-new'); if (overlay && !overlay.classList.contains('active')) overlay.classList.add('active'); if (overlay) overlay.style.zIndex = '9002'; - Tutorial._simulateFormVisibility(); + Tutorial._runDemo(Tutorial._simulateFormVisibility); }, onExit: function() { Tutorial._clearSubHighlights(); @@ -666,7 +677,7 @@ const Tutorial = { var overlay = document.getElementById('modal-new'); if (overlay && !overlay.classList.contains('active')) overlay.classList.add('active'); if (overlay) overlay.style.zIndex = '9002'; - Tutorial._simulateFormRefresh(); + Tutorial._runDemo(Tutorial._simulateFormRefresh); }, onExit: function() { Tutorial._clearSubHighlights(); @@ -687,7 +698,7 @@ const Tutorial = { var overlay = document.getElementById('modal-new'); if (overlay && !overlay.classList.contains('active')) overlay.classList.add('active'); if (overlay) overlay.style.zIndex = '9002'; - Tutorial._simulateFormRetention(); + Tutorial._runDemo(Tutorial._simulateFormRetention); }, onExit: function() { Tutorial._clearSubHighlights(); @@ -709,7 +720,7 @@ const Tutorial = { var overlay = document.getElementById('modal-new'); if (overlay && !overlay.classList.contains('active')) overlay.classList.add('active'); if (overlay) overlay.style.zIndex = '9002'; - Tutorial._simulateFormNotifications(); + Tutorial._runDemo(Tutorial._simulateFormNotifications); }, onExit: function() { var overlay = document.getElementById('modal-new'); @@ -970,7 +981,7 @@ const Tutorial = { position: 'right', disableNav: true, onEnter: function() { - Tutorial._simulateDrag(); + Tutorial._runDemo(Tutorial._simulateDrag); }, }, // 19 - Resize Demo @@ -984,7 +995,7 @@ const Tutorial = { position: 'left', disableNav: true, onEnter: function() { - Tutorial._simulateResize(); + Tutorial._runDemo(Tutorial._simulateResize); }, }, // 20 - Theme @@ -1044,7 +1055,7 @@ const Tutorial = { } if (overlay) overlay.style.zIndex = '9002'; Tutorial._stepTimeout(function() { - Tutorial._simulateSourcesInfoIcon(); + Tutorial._runDemo(Tutorial._simulateSourcesInfoIcon); }, 1500); }, onExit: function() { @@ -1075,7 +1086,7 @@ const Tutorial = { } } if (overlay) overlay.style.zIndex = '9002'; - Tutorial._simulateSourcesActions(); + Tutorial._runDemo(Tutorial._simulateSourcesActions); }, onExit: function() { var overlay = document.getElementById('modal-sources'); @@ -1594,7 +1605,7 @@ const Tutorial = { } // Nach Zoom: Demo starten setTimeout(function() { - self._simulateMapDemo(); + self._runDemo(self._simulateMapDemo); }, 3200); }, 1200); }, @@ -1680,8 +1691,9 @@ const Tutorial = { // Helfer: Cursor zu einem Element bewegen async _cursorToElement(selector, fromX, fromY) { var el = document.querySelector(selector); - if (!el) return { x: fromX, y: fromY }; + if (!el) return { x: fromX || 400, y: fromY || 300 }; var rect = el.getBoundingClientRect(); + if (!rect.width && !rect.height) return { x: fromX || 400, y: fromY || 300 }; var tx = rect.left + Math.min(rect.width / 2, 60); var ty = rect.top + rect.height / 2; if (fromX !== undefined && fromY !== undefined) { @@ -1707,6 +1719,8 @@ const Tutorial = { // ----------------------------------------------------------------------- async _simulateFormTitleDesc() { this._demoRunning = true; + // Warten bis Modal vollstaendig gerendert + await this._wait(600); var titleInput = document.getElementById('inc-title'); var descInput = document.getElementById('inc-description'); if (!titleInput) { this._demoRunning = false; this._enableNavAfterDemo(); return; }