Files
shark/src/lib/stores/status.ts

204 lines
4.9 KiB
TypeScript

/**
* Status notification service
* Provides a reactive status bar that shows download progress, notifications, and app state
*/
import { writable, derived, get } from 'svelte/store';
import { downloadQueue } from './downloadQueue';
export type StatusLevel = 'idle' | 'info' | 'success' | 'warning' | 'error';
export interface StatusMessage {
id: number;
message: string;
level: StatusLevel;
timestamp: number;
expiresAt: number;
}
interface StatusState {
messages: StatusMessage[];
nextId: number;
}
// Default expiration times (in ms)
const EXPIRATION_TIMES: Record<StatusLevel, number> = {
idle: 0,
info: 3000,
success: 4000,
warning: 6000,
error: 8000
};
// Priority order (higher = more important)
const PRIORITY: Record<StatusLevel, number> = {
error: 4,
warning: 3,
success: 2,
info: 1,
idle: 0
};
// Internal store
const statusState = writable<StatusState>({
messages: [],
nextId: 0
});
/**
* Push a new status message
*/
export function pushStatus(
message: string,
level: StatusLevel = 'info',
duration?: number
): number {
const state = get(statusState);
const id = state.nextId;
const timestamp = Date.now();
const expiresAt = timestamp + (duration ?? EXPIRATION_TIMES[level]);
const newMessage: StatusMessage = {
id,
message,
level,
timestamp,
expiresAt
};
statusState.update(s => ({
messages: [...s.messages, newMessage],
nextId: s.nextId + 1
}));
// Auto-remove after expiration
if (duration !== 0 && (duration ?? EXPIRATION_TIMES[level]) > 0) {
setTimeout(() => {
removeStatus(id);
}, duration ?? EXPIRATION_TIMES[level]);
}
return id;
}
/**
* Remove a status message by ID
*/
export function removeStatus(id: number): void {
statusState.update(s => ({
...s,
messages: s.messages.filter(m => m.id !== id)
}));
}
/**
* Clear all messages of a specific level (or all if no level specified)
*/
export function clearStatus(level?: StatusLevel): void {
statusState.update(s => ({
...s,
messages: level ? s.messages.filter(m => m.level !== level) : []
}));
}
/**
* Convenience methods
*/
export function setInfo(message: string, duration?: number): number {
return pushStatus(message, 'info', duration);
}
export function setSuccess(message: string, duration?: number): number {
return pushStatus(message, 'success', duration);
}
export function setWarning(message: string, duration?: number): number {
return pushStatus(message, 'warning', duration);
}
export function setError(message: string, duration?: number): number {
return pushStatus(message, 'error', duration);
}
/**
* Derive download status from queue state
*/
function deriveDownloadStatus(queueState: any): string | null {
const activeDownloads = queueState.queueOrder.filter((id: string) => {
const item = queueState.queue[id];
return item && (item.status === 'queued' || item.status === 'downloading');
});
const currentItem = queueState.currentJob
? queueState.queue[queueState.currentJob]
: null;
if (currentItem && currentItem.status === 'downloading') {
if (currentItem.type === 'track') {
return `Downloading: ${currentItem.title}`;
} else {
const total = currentItem.totalTracks;
const completed = currentItem.completedTracks;
const failed = currentItem.failedTracks;
const remaining = total - completed - failed;
if (remaining > 0) {
return `Downloading ${currentItem.title} (${completed}/${total} tracks)`;
} else {
return `Finishing ${currentItem.title}...`;
}
}
}
if (activeDownloads.length > 0) {
return `${activeDownloads.length} download${activeDownloads.length !== 1 ? 's' : ''} queued`;
}
return null;
}
/**
* Main status store - shows the highest priority active message or derived download status
*/
export const status = derived(
[statusState, downloadQueue],
([$statusState, $downloadQueue]) => {
const now = Date.now();
// Filter out expired messages
const activeMessages = $statusState.messages.filter(m => m.expiresAt > now);
// Get highest priority message
if (activeMessages.length > 0) {
const sorted = activeMessages.sort((a, b) => {
// Sort by priority first
const priorityDiff = PRIORITY[b.level] - PRIORITY[a.level];
if (priorityDiff !== 0) return priorityDiff;
// Then by timestamp (newer first)
return b.timestamp - a.timestamp;
});
return sorted[0].message;
}
// No explicit messages - check download queue
const downloadStatus = deriveDownloadStatus($downloadQueue);
if (downloadStatus) {
return downloadStatus;
}
// Default to idle
return 'Idle';
}
);
// Clean up expired messages periodically
setInterval(() => {
const now = Date.now();
statusState.update(s => ({
...s,
messages: s.messages.filter(m => m.expiresAt > now)
}));
}, 1000);