diff --git a/asteroid.lisp b/asteroid.lisp index d0254e4..33d1327 100644 --- a/asteroid.lisp +++ b/asteroid.lisp @@ -607,6 +607,13 @@ :top-artist-3 "" :top-artist-3-plays "")) +;; Status content frame (for frameset mode) +(define-page status-content #@"/status-content" () + "Status page content (displayed in content frame)" + (clip:process-to-string + (load-template "status-content") + :title "📡 Asteroid Radio - Status")) + ;; Configure static file serving for other files ;; BUT exclude ParenScript-compiled JS files (define-page static #@"/static/(.*)" (:uri-groups (path)) diff --git a/parenscript/spectrum-analyzer.lisp b/parenscript/spectrum-analyzer.lisp index e520b76..53200ed 100644 --- a/parenscript/spectrum-analyzer.lisp +++ b/parenscript/spectrum-analyzer.lisp @@ -43,24 +43,22 @@ (let ((audio-element nil) (canvas-element (ps:chain document (get-element-by-id "spectrum-canvas")))) - ;; Try to find audio element in current frame first + ;; Try current document first (setf audio-element (or (ps:chain document (get-element-by-id "live-audio")) (ps:chain document (get-element-by-id "persistent-audio")))) - ;; If not found and we're in a frame, try to access from parent frameset + ;; If not found and we're in a frame, try parent frame (frameset mode) (when (and (not audio-element) (ps:@ window parent) (not (eq (ps:@ window parent) window))) - (ps:chain console (log "Trying to access audio from parent frame...")) (ps:try - (progn - ;; Try accessing via parent.frames - (let ((player-frame (ps:getprop (ps:@ window parent) "player-frame"))) - (when player-frame - (setf audio-element (ps:chain player-frame document (get-element-by-id "persistent-audio"))) - (ps:chain console (log "Found audio in player-frame:" audio-element))))) + (let ((player-frame (ps:getprop (ps:@ window parent frames) "player-frame"))) + (when player-frame + (setf audio-element (ps:chain player-frame document (get-element-by-id "persistent-audio"))) + (when audio-element + (ps:chain console (log "Found persistent-audio in player-frame"))))) (:catch (e) - (ps:chain console (log "Cross-frame access error:" e))))) + (ps:chain console (log "Could not access parent frame:" e))))) (when (and audio-element canvas-element) ;; Store current audio element @@ -218,49 +216,72 @@ "Return array of available visualization styles" (array "bars" "wave" "dots")) - ;; Initialize when audio starts playing - (ps:chain document (add-event-listener "DOMContentLoaded" - (lambda () - ;; Load saved theme and style preferences - (let ((saved-theme (ps:chain local-storage (get-item "spectrum-theme"))) - (saved-style (ps:chain local-storage (get-item "spectrum-style")))) - (when (and saved-theme (ps:getprop *themes* saved-theme)) - (setf *current-theme* saved-theme)) - (when (and saved-style (or (= saved-style "bars") (= saved-style "wave") (= saved-style "dots"))) - (setf *current-style* saved-style)) - - ;; Update UI selectors, canvas border, and dropdown colors - (let ((theme-selector (ps:chain document (get-element-by-id "spectrum-theme-selector"))) - (style-selector (ps:chain document (get-element-by-id "spectrum-style-selector"))) - (canvas (ps:chain document (get-element-by-id "spectrum-canvas"))) - (theme (ps:getprop *themes* *current-theme*))) - (when theme-selector - (setf (ps:@ theme-selector value) *current-theme*) - (setf (ps:@ theme-selector style color) (ps:@ theme top)) - (setf (ps:@ theme-selector style border-color) (ps:@ theme top))) - (when style-selector - (setf (ps:@ style-selector value) *current-style*) - (setf (ps:@ style-selector style color) (ps:@ theme top)) - (setf (ps:@ style-selector style border-color) (ps:@ theme top))) - - ;; Set initial canvas border color - (when canvas - (setf (ps:@ canvas style border-color) (ps:@ theme top))))) + (defun initialize-spectrum-analyzer () + "Initialize or re-initialize the spectrum analyzer (can be called after AJAX navigation)" + ;; Stop existing analyzer if running + (when *animation-id* + (ps:chain window (cancel-animation-frame *animation-id*)) + (setf *animation-id* nil)) + + ;; Load saved theme and style preferences + (let ((saved-theme (ps:chain local-storage (get-item "spectrum-theme"))) + (saved-style (ps:chain local-storage (get-item "spectrum-style")))) + (when (and saved-theme (ps:getprop *themes* saved-theme)) + (setf *current-theme* saved-theme)) + (when (and saved-style (or (= saved-style "bars") (= saved-style "wave") (= saved-style "dots"))) + (setf *current-style* saved-style)) - (let ((audio-element (or (ps:chain document (get-element-by-id "live-audio")) - (ps:chain document (get-element-by-id "persistent-audio"))))) + ;; Update UI selectors, canvas border, and dropdown colors + (let ((theme-selector (ps:chain document (get-element-by-id "spectrum-theme-selector"))) + (style-selector (ps:chain document (get-element-by-id "spectrum-style-selector"))) + (canvas (ps:chain document (get-element-by-id "spectrum-canvas"))) + (theme (ps:getprop *themes* *current-theme*))) + (when theme-selector + (setf (ps:@ theme-selector value) *current-theme*) + (setf (ps:@ theme-selector style color) (ps:@ theme top)) + (setf (ps:@ theme-selector style border-color) (ps:@ theme top))) + (when style-selector + (setf (ps:@ style-selector value) *current-style*) + (setf (ps:@ style-selector style color) (ps:@ theme top)) + (setf (ps:@ style-selector style border-color) (ps:@ theme top))) - ;; If not found and we're in a frame, try parent - (when (and (not audio-element) - (ps:@ window parent) - (not (eq (ps:@ window parent) window))) - (ps:try - (let ((player-frame (ps:getprop (ps:@ window parent) "player-frame"))) - (when player-frame - (setf audio-element (ps:chain player-frame document (get-element-by-id "persistent-audio"))))) - (:catch (e) - (ps:chain console (log "Event listener cross-frame error:" e))))) + ;; Set initial canvas border color + (when canvas + (setf (ps:@ canvas style border-color) (ps:@ theme top))))) + + (let ((audio-element nil)) + + ;; Try current document first + (setf audio-element (or (ps:chain document (get-element-by-id "live-audio")) + (ps:chain document (get-element-by-id "persistent-audio")))) + + ;; If not found and we're in a frame, try parent frame (frameset mode) + (when (and (not audio-element) + (ps:@ window parent) + (not (eq (ps:@ window parent) window))) + (ps:try + (let ((player-frame (ps:getprop (ps:@ window parent frames) "player-frame"))) + (when player-frame + (setf audio-element (ps:chain player-frame document (get-element-by-id "persistent-audio"))) + (when audio-element + (ps:chain console (log "Found persistent-audio in player-frame"))))) + (:catch (e) + (ps:chain console (log "Could not access parent frame:" e))))) + + (when audio-element + ;; Store reference for muted detection + (setf *current-audio-element* audio-element) + (ps:chain audio-element (add-event-listener "play" init-spectrum-analyzer)) + (ps:chain audio-element (add-event-listener "pause" stop-spectrum-analyzer)) - (when audio-element - (ps:chain audio-element (add-event-listener "play" init-spectrum-analyzer)) - (ps:chain audio-element (add-event-listener "pause" stop-spectrum-analyzer))))))))) + ;; If audio is already playing, restart the analyzer with new reference + (when (and (not (ps:@ audio-element paused)) + (ps:chain document (get-element-by-id "spectrum-canvas"))) + (ps:chain console (log "Audio already playing, restarting spectrum analyzer")) + (init-spectrum-analyzer))))) + + ;; Make initialization function globally accessible + (setf (ps:@ window |initializeSpectrumAnalyzer|) initialize-spectrum-analyzer) + + ;; Initialize when audio starts playing + (ps:chain document (add-event-listener "DOMContentLoaded" initialize-spectrum-analyzer)))) diff --git a/template/admin-content.ctml b/template/admin-content.ctml index 59864f6..43a7e8c 100644 --- a/template/admin-content.ctml +++ b/template/admin-content.ctml @@ -34,6 +34,13 @@ const url = link.href; console.log('Loading via AJAX:', url); + // Clear all intervals to prevent old page scripts from running + const highestId = window.setTimeout(() => {}, 0); + for (let i = 0; i < highestId; i++) { + window.clearInterval(i); + window.clearTimeout(i); + } + fetch(url) .then(response => response.text()) .then(html => { @@ -41,6 +48,23 @@ document.write(html); document.close(); + // Execute scripts in the new content + const scripts = document.querySelectorAll('script'); + scripts.forEach(oldScript => { + const newScript = document.createElement('script'); + if (oldScript.src) { + newScript.src = oldScript.src; + } else { + newScript.textContent = oldScript.textContent; + } + oldScript.parentNode.replaceChild(newScript, oldScript); + }); + + // Re-initialize spectrum analyzer after navigation + if (window.initializeSpectrumAnalyzer) { + setTimeout(() => window.initializeSpectrumAnalyzer(), 100); + } + if (window.history && window.history.pushState) { window.history.pushState({}, '', url); } diff --git a/template/front-page-content.ctml b/template/front-page-content.ctml index 57832a2..b3ac5c9 100644 --- a/template/front-page-content.ctml +++ b/template/front-page-content.ctml @@ -39,6 +39,13 @@ const url = link.href; console.log('Loading via AJAX:', url); + // Clear all intervals to prevent old page scripts from running + const highestId = window.setTimeout(() => {}, 0); + for (let i = 0; i < highestId; i++) { + window.clearInterval(i); + window.clearTimeout(i); + } + fetch(url) .then(response => response.text()) .then(html => { @@ -47,6 +54,23 @@ document.write(html); document.close(); + // Execute scripts in the new content + const scripts = document.querySelectorAll('script'); + scripts.forEach(oldScript => { + const newScript = document.createElement('script'); + if (oldScript.src) { + newScript.src = oldScript.src; + } else { + newScript.textContent = oldScript.textContent; + } + oldScript.parentNode.replaceChild(newScript, oldScript); + }); + + // Re-initialize spectrum analyzer after navigation + if (window.initializeSpectrumAnalyzer) { + setTimeout(() => window.initializeSpectrumAnalyzer(), 100); + } + // Update browser history if (window.history && window.history.pushState) { window.history.pushState({}, '', url); @@ -101,13 +125,13 @@ diff --git a/template/login-content.ctml b/template/login-content.ctml index 77e7cdc..ffa4c60 100644 --- a/template/login-content.ctml +++ b/template/login-content.ctml @@ -14,6 +14,13 @@ const url = link.href; console.log('Loading via AJAX:', url); + // Clear all intervals to prevent old page scripts from running + const highestId = window.setTimeout(() => {}, 0); + for (let i = 0; i < highestId; i++) { + window.clearInterval(i); + window.clearTimeout(i); + } + fetch(url) .then(response => response.text()) .then(html => { @@ -21,6 +28,23 @@ document.write(html); document.close(); + // Execute scripts in the new content + const scripts = document.querySelectorAll('script'); + scripts.forEach(oldScript => { + const newScript = document.createElement('script'); + if (oldScript.src) { + newScript.src = oldScript.src; + } else { + newScript.textContent = oldScript.textContent; + } + oldScript.parentNode.replaceChild(newScript, oldScript); + }); + + // Re-initialize spectrum analyzer after navigation + if (window.initializeSpectrumAnalyzer) { + setTimeout(() => window.initializeSpectrumAnalyzer(), 100); + } + if (window.history && window.history.pushState) { window.history.pushState({}, '', url); } @@ -74,9 +98,9 @@ ASTEROID RADIO - LOGIN diff --git a/template/player-content.ctml b/template/player-content.ctml index 17912ab..7a00bc0 100644 --- a/template/player-content.ctml +++ b/template/player-content.ctml @@ -35,6 +35,13 @@ const url = link.href; console.log('Loading via AJAX:', url); + // Clear all intervals to prevent old page scripts from running + const highestId = window.setTimeout(() => {}, 0); + for (let i = 0; i < highestId; i++) { + window.clearInterval(i); + window.clearTimeout(i); + } + fetch(url) .then(response => response.text()) .then(html => { @@ -43,6 +50,23 @@ document.write(html); document.close(); + // Execute scripts in the new content + const scripts = document.querySelectorAll('script'); + scripts.forEach(oldScript => { + const newScript = document.createElement('script'); + if (oldScript.src) { + newScript.src = oldScript.src; + } else { + newScript.textContent = oldScript.textContent; + } + oldScript.parentNode.replaceChild(newScript, oldScript); + }); + + // Re-initialize spectrum analyzer after navigation + if (window.initializeSpectrumAnalyzer) { + setTimeout(() => window.initializeSpectrumAnalyzer(), 100); + } + // Update browser history if (window.history && window.history.pushState) { window.history.pushState({}, '', url); @@ -95,11 +119,11 @@
diff --git a/template/profile-content.ctml b/template/profile-content.ctml index 7674ba4..1361d20 100644 --- a/template/profile-content.ctml +++ b/template/profile-content.ctml @@ -34,6 +34,13 @@ const url = link.href; console.log('Loading via AJAX:', url); + // Clear all intervals to prevent old page scripts from running + const highestId = window.setTimeout(() => {}, 0); + for (let i = 0; i < highestId; i++) { + window.clearInterval(i); + window.clearTimeout(i); + } + fetch(url) .then(response => response.text()) .then(html => { @@ -41,6 +48,23 @@ document.write(html); document.close(); + // Execute scripts in the new content + const scripts = document.querySelectorAll('script'); + scripts.forEach(oldScript => { + const newScript = document.createElement('script'); + if (oldScript.src) { + newScript.src = oldScript.src; + } else { + newScript.textContent = oldScript.textContent; + } + oldScript.parentNode.replaceChild(newScript, oldScript); + }); + + // Re-initialize spectrum analyzer after navigation + if (window.initializeSpectrumAnalyzer) { + setTimeout(() => window.initializeSpectrumAnalyzer(), 100); + } + if (window.history && window.history.pushState) { window.history.pushState({}, '', url); } diff --git a/template/status-content.ctml b/template/status-content.ctml new file mode 100644 index 0000000..a589043 --- /dev/null +++ b/template/status-content.ctml @@ -0,0 +1,99 @@ + + + +Status page coming soon...
+For now, administrators can view detailed status information in the Admin Dashboard.
+