Files
KVideo/lib/store/user-sources-store.ts

117 lines
3.3 KiB
TypeScript

/**
* User Sources Store - Per-user video sources and danmaku APIs
* Uses localStorage keyed by profileId for isolation between users
*/
import { getProfileId } from './auth-store';
import type { VideoSource } from '@/lib/types';
export interface DanmakuApiEntry {
id: string;
name: string;
url: string;
}
interface UserSourcesState {
sources: VideoSource[];
danmakuApis: DanmakuApiEntry[];
activeDanmakuApiId: string | null;
}
function getStorageKey(): string {
const profileId = getProfileId();
return `kvideo-user-sources-${profileId}`;
}
function getState(): UserSourcesState {
if (typeof window === 'undefined') {
return { sources: [], danmakuApis: [], activeDanmakuApiId: null };
}
try {
const raw = localStorage.getItem(getStorageKey());
if (!raw) return { sources: [], danmakuApis: [], activeDanmakuApiId: null };
const parsed = JSON.parse(raw);
return {
sources: Array.isArray(parsed.sources) ? parsed.sources : [],
danmakuApis: Array.isArray(parsed.danmakuApis) ? parsed.danmakuApis : [],
activeDanmakuApiId: parsed.activeDanmakuApiId ?? null,
};
} catch {
return { sources: [], danmakuApis: [], activeDanmakuApiId: null };
}
}
function saveState(state: UserSourcesState): void {
if (typeof window === 'undefined') return;
localStorage.setItem(getStorageKey(), JSON.stringify(state));
userSourcesStore.notifyListeners();
}
export const userSourcesStore = {
listeners: new Set<() => void>(),
subscribe(listener: () => void): () => void {
this.listeners.add(listener);
return () => { this.listeners.delete(listener); };
},
notifyListeners(): void {
this.listeners.forEach(l => l());
},
getState,
getSources(): VideoSource[] {
return getState().sources;
},
getDanmakuApis(): DanmakuApiEntry[] {
return getState().danmakuApis;
},
getActiveDanmakuApi(): DanmakuApiEntry | null {
const state = getState();
if (!state.activeDanmakuApiId) return null;
return state.danmakuApis.find(a => a.id === state.activeDanmakuApiId) ?? null;
},
addSource(source: VideoSource): void {
const state = getState();
if (state.sources.find(s => s.id === source.id)) return;
saveState({ ...state, sources: [...state.sources, { ...source, enabled: true }] });
},
removeSource(id: string): void {
const state = getState();
saveState({ ...state, sources: state.sources.filter(s => s.id !== id) });
},
toggleSource(id: string): void {
const state = getState();
saveState({
...state,
sources: state.sources.map(s =>
s.id === id ? { ...s, enabled: !s.enabled } : s
),
});
},
addDanmakuApi(entry: DanmakuApiEntry): void {
const state = getState();
if (state.danmakuApis.find(a => a.id === entry.id)) return;
saveState({ ...state, danmakuApis: [...state.danmakuApis, entry] });
},
removeDanmakuApi(id: string): void {
const state = getState();
const newApis = state.danmakuApis.filter(a => a.id !== id);
const newActiveId = state.activeDanmakuApiId === id ? null : state.activeDanmakuApiId;
saveState({ ...state, danmakuApis: newApis, activeDanmakuApiId: newActiveId });
},
setActiveDanmakuApi(id: string | null): void {
const state = getState();
saveState({ ...state, activeDanmakuApiId: id });
},
};