import { Controller } from "@hotwired/stimulus"
const STORAGE_KEY = "evaluation_music_enabled"
const FADE_DURATION = 500 // ms
export default class extends Controller {
static targets = ["toggleButton", "toggleIcon"]
static values = {
musicUrls: Array
}
connect() {
this.audio = null
this.isFading = false
// Load preference from localStorage
this.musicEnabled = localStorage.getItem(STORAGE_KEY) === "true"
this.updateToggleUI()
// Start music if enabled
if (this.musicEnabled) {
this.startMusic()
}
// Listen for modal close event (dialog element stays in DOM when closed)
this.dialog = this.element.closest("dialog")
if (this.dialog) {
this.boundHandleDialogClose = this.handleDialogClose.bind(this)
this.dialog.addEventListener("close", this.boundHandleDialogClose)
}
// Also listen for Turbo events that might replace the content
this.boundHandleTurboBeforeCache = this.handleTurboBeforeCache.bind(this)
document.addEventListener("turbo:before-cache", this.boundHandleTurboBeforeCache)
}
disconnect() {
this.stopMusic()
// Clean up event listeners
if (this.dialog && this.boundHandleDialogClose) {
this.dialog.removeEventListener("close", this.boundHandleDialogClose)
}
document.removeEventListener("turbo:before-cache", this.boundHandleTurboBeforeCache)
}
handleDialogClose() {
this.fadeOutAndStop()
}
handleTurboBeforeCache() {
this.stopMusic()
}
toggle() {
this.musicEnabled = !this.musicEnabled
localStorage.setItem(STORAGE_KEY, this.musicEnabled.toString())
this.updateToggleUI()
if (this.musicEnabled) {
this.startMusic()
} else {
this.fadeOutAndStop()
}
}
startMusic() {
if (this.audio || this.musicUrlsValue.length === 0) return
// Pick a random track
const randomIndex = Math.floor(Math.random() * this.musicUrlsValue.length)
const musicUrl = this.musicUrlsValue[randomIndex]
this.audio = new Audio(musicUrl)
this.audio.loop = true
this.audio.volume = 0
// Start playing and fade in
this.audio.play()
.then(() => {
this.fadeIn()
})
.catch((error) => {
console.log("Audio autoplay blocked:", error)
// If autoplay is blocked, we'll try again on next user interaction
this.audio = null
})
}
stopMusic() {
if (this.audio) {
this.audio.pause()
this.audio.src = ""
this.audio = null
}
}
fadeIn() {
if (!this.audio || this.isFading) return
this.isFading = true
const targetVolume = 0.5
const steps = 20
const stepDuration = FADE_DURATION / steps
const volumeStep = targetVolume / steps
let currentStep = 0
const fadeInterval = setInterval(() => {
currentStep++
if (currentStep >= steps || !this.audio) {
if (this.audio) this.audio.volume = targetVolume
clearInterval(fadeInterval)
this.isFading = false
} else {
this.audio.volume = Math.min(targetVolume, volumeStep * currentStep)
}
}, stepDuration)
}
fadeOutAndStop() {
if (!this.audio || this.isFading) {
this.stopMusic()
return
}
this.isFading = true
const startVolume = this.audio.volume
const steps = 20
const stepDuration = FADE_DURATION / steps
const volumeStep = startVolume / steps
let currentStep = 0
const fadeInterval = setInterval(() => {
currentStep++
if (currentStep >= steps || !this.audio) {
clearInterval(fadeInterval)
this.stopMusic()
this.isFading = false
} else {
this.audio.volume = Math.max(0, startVolume - (volumeStep * currentStep))
}
}, stepDuration)
}
updateToggleUI() {
if (this.hasToggleIconTarget) {
this.toggleIconTarget.innerHTML = this.musicEnabled ? this.speakerOnIcon() : this.speakerOffIcon()
}
if (this.hasToggleButtonTarget) {
this.toggleButtonTarget.classList.toggle("btn-primary", this.musicEnabled)
this.toggleButtonTarget.classList.toggle("btn-ghost", !this.musicEnabled)
}
}
speakerOnIcon() {
// Heroicon: speaker-wave (outline)
return ``
}
speakerOffIcon() {
// Heroicon: speaker-x-mark (outline)
return ``
}
}