mirror of
https://github.com/KuekHaoYang/KVideo.git
synced 2026-08-12 23:33:43 +08:00
117 lines
3.3 KiB
TypeScript
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 });
|
|
},
|
|
};
|