Formatting was configured but never enforced: `bun run format:check` reported 199 unformatted files and ran in no workflow and in no git hook, so .prettierrc (printWidth 100, trailing commas) described an intention rather than the tree. This is the one-time sweep that makes the check gateable. Whitespace and token-reflow only -- no behavioural change: `bun run check` reports 0 errors and all 1053 frontend tests pass before and after. Kept out of every other commit on purpose. A 199-file diff mixed with real changes is unreviewable, and the next commit turns format:check into a hard CI gate so this cannot silently accumulate again.
112 lines
2.8 KiB
TypeScript
112 lines
2.8 KiB
TypeScript
/**
|
|
* Next Episode Store (Display Only - Backend-First Architecture)
|
|
*
|
|
* This store reflects next episode popup state from the backend.
|
|
* The backend handles all countdown logic and decisions.
|
|
*
|
|
* The backend emits ShowNextEpisodePopup and CountdownTick events to update this store.
|
|
*
|
|
* TRACES: UR-023 | DR-026, DR-047, DR-048
|
|
*/
|
|
|
|
import { writable, derived } from "svelte/store";
|
|
import type { MediaItem } from "$lib/api/types";
|
|
|
|
export interface NextEpisodeState {
|
|
// Popup visibility
|
|
isVisible: boolean;
|
|
|
|
// Episode data
|
|
nextEpisode: MediaItem | null;
|
|
currentEpisode: MediaItem | null;
|
|
|
|
// Countdown state (managed by backend)
|
|
countdownSeconds: number;
|
|
initialCountdownSeconds: number;
|
|
|
|
// Settings
|
|
autoPlayEnabled: boolean;
|
|
}
|
|
|
|
function createNextEpisodeStore() {
|
|
const initialState: NextEpisodeState = {
|
|
isVisible: false,
|
|
nextEpisode: null,
|
|
currentEpisode: null,
|
|
countdownSeconds: 10,
|
|
initialCountdownSeconds: 10,
|
|
autoPlayEnabled: true,
|
|
};
|
|
|
|
const { subscribe, set, update } = writable<NextEpisodeState>(initialState);
|
|
|
|
/**
|
|
* Show the next episode popup (called by playerEvents when backend emits event)
|
|
*/
|
|
function showPopup(
|
|
currentEpisode: MediaItem,
|
|
nextEpisode: MediaItem,
|
|
countdownSeconds: number,
|
|
autoPlayEnabled: boolean,
|
|
): void {
|
|
update((s) => ({
|
|
...s,
|
|
isVisible: true,
|
|
currentEpisode,
|
|
nextEpisode,
|
|
countdownSeconds,
|
|
initialCountdownSeconds: countdownSeconds,
|
|
autoPlayEnabled,
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Update countdown value (called by playerEvents on CountdownTick event)
|
|
*/
|
|
function updateCountdown(remainingSeconds: number): void {
|
|
update((s) => ({
|
|
...s,
|
|
countdownSeconds: remainingSeconds,
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Hide the popup
|
|
*/
|
|
function hidePopup(): void {
|
|
update((s) => ({
|
|
...s,
|
|
isVisible: false,
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Reset store to initial state
|
|
*/
|
|
function reset(): void {
|
|
set(initialState);
|
|
}
|
|
|
|
return {
|
|
subscribe,
|
|
showPopup,
|
|
updateCountdown,
|
|
hidePopup,
|
|
reset,
|
|
};
|
|
}
|
|
|
|
export const nextEpisode = createNextEpisodeStore();
|
|
|
|
// Derived stores for convenient access
|
|
export const isNextEpisodePopupVisible = derived(nextEpisode, ($ne) => $ne.isVisible);
|
|
export const nextEpisodeItem = derived(nextEpisode, ($ne) => $ne.nextEpisode);
|
|
export const currentEpisodeItem = derived(nextEpisode, ($ne) => $ne.currentEpisode);
|
|
export const countdownSeconds = derived(nextEpisode, ($ne) => $ne.countdownSeconds);
|
|
export const initialCountdownSeconds = derived(nextEpisode, ($ne) => $ne.initialCountdownSeconds);
|
|
export const isAutoPlayEnabled = derived(nextEpisode, ($ne) => $ne.autoPlayEnabled);
|
|
export const isCountdownActive = derived(
|
|
nextEpisode,
|
|
($ne) => $ne.isVisible && $ne.countdownSeconds > 0,
|
|
);
|