Feature: success and failure toast and greener buttons
This commit is contained in:
@@ -4,6 +4,7 @@ import { SceneFeature } from './SceneFeature.js';
|
||||
import sceneFeatureManager from './SceneFeatureManager.js';
|
||||
import { MediaStorage } from '../core/media-storage.js';
|
||||
import { showStandbyScreen } from './projection-screen.js';
|
||||
import { showToast } from '../core/ui-utils.js';
|
||||
|
||||
export class ConfigUI extends SceneFeature {
|
||||
constructor() {
|
||||
@@ -357,12 +358,23 @@ export class ConfigUI extends SceneFeature {
|
||||
state.posterImage = URL.createObjectURL(file);
|
||||
MediaStorage.savePoster(file);
|
||||
showStandbyScreen();
|
||||
this.updateStatus();
|
||||
showToast('Poster loaded', 'success');
|
||||
}
|
||||
};
|
||||
document.body.appendChild(posterInput);
|
||||
|
||||
loadPosterBtn.onclick = () => {
|
||||
posterInput.click();
|
||||
if (state.posterImage) {
|
||||
URL.revokeObjectURL(state.posterImage);
|
||||
state.posterImage = null;
|
||||
MediaStorage.deletePoster();
|
||||
showStandbyScreen();
|
||||
this.updateStatus();
|
||||
posterInput.value = '';
|
||||
} else {
|
||||
posterInput.click();
|
||||
}
|
||||
};
|
||||
this.loadPosterBtn = loadPosterBtn;
|
||||
statusContainer.appendChild(loadPosterBtn);
|
||||
@@ -512,6 +524,7 @@ export class ConfigUI extends SceneFeature {
|
||||
if (file) {
|
||||
state.posterImage = URL.createObjectURL(file);
|
||||
showStandbyScreen();
|
||||
this.updateStatus();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -566,9 +579,12 @@ export class ConfigUI extends SceneFeature {
|
||||
|
||||
if (this.loadPosterBtn) {
|
||||
this.loadPosterBtn.style.backgroundColor = state.posterImage ? green : orange;
|
||||
this.loadPosterBtn.innerText = state.posterImage ? 'Clear Poster' : 'Load Poster';
|
||||
}
|
||||
if (state.loadTapeButton) {
|
||||
state.loadTapeButton.style.backgroundColor = (state.videoUrls && state.videoUrls.length > 0) ? green : orange;
|
||||
const hasTapes = state.videoUrls && state.videoUrls.length > 0;
|
||||
state.loadTapeButton.style.backgroundColor = hasTapes ? green : orange;
|
||||
state.loadTapeButton.innerText = hasTapes ? 'Change Tapes' : 'Load Tapes';
|
||||
}
|
||||
|
||||
// Update Tape List
|
||||
|
||||
@@ -3,6 +3,7 @@ import { SceneFeature } from './SceneFeature.js';
|
||||
import sceneFeatureManager from './SceneFeatureManager.js';
|
||||
import { showStandbyScreen } from './projection-screen.js';
|
||||
import { MediaStorage } from '../core/media-storage.js';
|
||||
import { showToast } from '../core/ui-utils.js';
|
||||
|
||||
export class MusicPlayer extends SceneFeature {
|
||||
constructor() {
|
||||
@@ -24,18 +25,40 @@ export class MusicPlayer extends SceneFeature {
|
||||
state.music.loudness = 0;
|
||||
state.music.loudnessAverage = 0;
|
||||
const loadButton = document.getElementById('loadMusicButton');
|
||||
const fileInput = document.getElementById('musicFileInput');
|
||||
|
||||
let fileInput = document.getElementById('musicFileInput');
|
||||
if (!fileInput) {
|
||||
fileInput = document.createElement('input');
|
||||
fileInput.id = 'musicFileInput';
|
||||
fileInput.type = 'file';
|
||||
fileInput.accept = 'audio/*';
|
||||
fileInput.style.display = 'none';
|
||||
document.body.appendChild(fileInput);
|
||||
}
|
||||
|
||||
// Reset value to allow re-selecting the same file
|
||||
fileInput.onclick = () => { fileInput.value = ''; };
|
||||
|
||||
// Hide the big start button as we use ConfigUI now
|
||||
if (loadButton) loadButton.style.display = 'none';
|
||||
|
||||
fileInput.addEventListener('change', (event) => {
|
||||
fileInput.onchange = (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
console.info(`[MusicPlayer] File selected from input: ${file.name} (${file.size} bytes)`);
|
||||
if (file.size === 0) {
|
||||
console.warn('[MusicPlayer] Selected file is empty (0 bytes). Check if file is a cloud placeholder or locked.');
|
||||
showToast(`Error: "${file.name}" is empty (0 bytes). Looks like the browser can't access it.`, 'error');
|
||||
return;
|
||||
}
|
||||
this.loadMusicFile(file);
|
||||
MediaStorage.saveMusic(file);
|
||||
MediaStorage.saveMusic(file).then(() => showToast(`Loaded "${file.name}"`, 'success'))
|
||||
.catch(e => {
|
||||
console.warn('[MusicPlayer] Failed to save music:', e);
|
||||
showToast('Warning: Failed to save song to storage.', 'warning');
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
state.music.player.addEventListener('ended', () => {
|
||||
this.stopParty();
|
||||
@@ -46,12 +69,18 @@ export class MusicPlayer extends SceneFeature {
|
||||
// Restore from storage
|
||||
MediaStorage.getMusic().then(file => {
|
||||
if (file) {
|
||||
console.info(`[MusicPlayer] Restored music from storage: ${file.name} (${file.size} bytes)`);
|
||||
this.loadMusicFile(file);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loadMusicFile(file) {
|
||||
if (!file || file.size === 0) {
|
||||
console.warn('[MusicPlayer] loadMusicFile called with invalid or empty file.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup Web Audio API if not already done
|
||||
if (!this.audioContext) {
|
||||
this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
||||
@@ -68,8 +97,13 @@ export class MusicPlayer extends SceneFeature {
|
||||
const songName = file.name.replace(/\.[^/.]+$/, "");
|
||||
state.music.songTitle = songName;
|
||||
|
||||
if (state.music.player.src && state.music.player.src.startsWith('blob:')) {
|
||||
URL.revokeObjectURL(state.music.player.src);
|
||||
}
|
||||
|
||||
const url = URL.createObjectURL(file);
|
||||
state.music.player.src = url;
|
||||
console.info(`[MusicPlayer] Audio source set to blob URL: ${url}`);
|
||||
|
||||
// Update Config UI
|
||||
const configUI = sceneFeatureManager.features.find(f => f.constructor.name === 'ConfigUI');
|
||||
|
||||
Reference in New Issue
Block a user