;;;; recently-played.lisp - ParenScript version of recently-played.js ;;;; Recently Played Tracks functionality (in-package #:asteroid) (defparameter *recently-played-js* (ps:ps (progn ;; Get current channel from selector or localStorage (defun get-current-channel () (let ((selector (or (ps:chain document (get-element-by-id "stream-channel")) (ps:chain document (get-element-by-id "popout-stream-channel"))))) (if selector (ps:@ selector value) (or (ps:chain local-storage (get-item "stream-channel")) "curated")))) ;; Get current quality from selector or localStorage (defun get-current-quality () (let ((selector (or (ps:chain document (get-element-by-id "stream-quality")) (ps:chain document (get-element-by-id "popout-stream-quality"))))) (if selector (ps:@ selector value) (or (ps:chain local-storage (get-item "stream-quality")) "aac")))) ;; Get current mount from channel and quality selection (defun get-current-mount-for-recently-played () (let ((channel (get-current-channel)) (quality (get-current-quality))) (if (= channel "shuffle") "asteroid-shuffle.mp3" (cond ((= quality "low") "asteroid-low.mp3") ((= quality "mp3") "asteroid.mp3") (t "asteroid.aac"))))) ;; Update recently played tracks display (defun update-recently-played () (let ((mount (get-current-mount-for-recently-played))) (ps:chain (fetch (+ "/api/asteroid/recently-played?mount=" mount)) (then (lambda (response) (ps:chain response (json)))) (then (lambda (result) ;; Radiance wraps API responses in a data envelope (let ((data (or (ps:@ result data) result))) (if (and (equal (ps:@ data status) "success") (ps:@ data tracks) (> (ps:@ data tracks length) 0)) (let ((list-el (ps:chain document (get-element-by-id "recently-played-list")))) (when list-el ;; Build HTML for tracks (let ((html "
No tracks played yet
"))))))) (catch (lambda (error) (ps:chain console (error "Error fetching recently played:" error)) (let ((list-el (ps:chain document (get-element-by-id "recently-played-list")))) (when list-el (setf (aref list-el "innerHTML") "Error loading recently played tracks
")))))))) ;; Format timestamp as relative time (defun format-time-ago (timestamp) (let* ((now (floor (/ (ps:chain *date (now)) 1000))) (diff (- now timestamp))) (cond ((< diff 60) "Just now") ((< diff 3600) (+ (floor (/ diff 60)) "m ago")) ((< diff 86400) (+ (floor (/ diff 3600)) "h ago")) (t (+ (floor (/ diff 86400)) "d ago"))))) ;; Escape HTML to prevent XSS (defun escape-html (text) (when (ps:@ window document) (let ((div (ps:chain document (create-element "div")))) (setf (ps:@ div text-content) text) (aref div "innerHTML")))) ;; Initialize on page load (when (ps:@ window document) (ps:chain document (add-event-listener "DOMContentLoaded" (lambda () (let ((panel (ps:chain document (get-element-by-id "recently-played-panel")))) (if panel (progn (update-recently-played) ;; Refresh immediately when user switches channel (let ((channel-selector (or (ps:chain document (get-element-by-id "stream-channel")) (ps:chain document (get-element-by-id "popout-stream-channel"))))) (when channel-selector (ps:chain channel-selector (add-event-listener "change" (lambda (_ev) ;; Small delay so localStorage / UI state settles (ps:chain window (set-timeout update-recently-played 50))))))) ;; Update every 30 seconds (set-interval update-recently-played 30000)) (let ((list (ps:chain document (get-element-by-id "recently-played-list")))) (when list (update-recently-played) (set-interval update-recently-played 30000)))))))))) ) "Compiled JavaScript for recently played tracks - generated at load time" ) (defun generate-recently-played-js () "Generate JavaScript code for recently played tracks" *recently-played-js*)