Ctrl K
ComunidadGuías…

src/renderer/store/lyrics-overrides.store.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import { devtools, persist, subscribeWithSelector } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
import { createWithEqualityFn } from 'zustand/traditional';

import { identityPersistMigrate, PERSIST_VERSION_INITIAL } from '/@/renderer/store/persist-migrate';
import { LyricsOverride } from '/@/shared/types/domain-types';

export type LyricsOverrideEntry = {
    offsetMs?: number;
    override?: LyricsOverride;
    structuredIndex?: number;
    suppressed?: boolean;
    updatedAt: number;
};

interface LyricsOverridesState {
    actions: {
        clearOffset: (key: string) => void;
        clearOverride: (key: string) => void;
        clearSuppressed: (key: string) => void;
        setOffset: (key: string, offsetMs: number) => void;
        setOverride: (key: string, override: LyricsOverride) => void;
        setStructuredIndex: (key: string, index: number) => void;
        suppress: (key: string) => void;
    };
    entries: Record<string, LyricsOverrideEntry>;
}

const MAX_ENTRIES = 500;

const evictIfOverCap = (entries: Record<string, LyricsOverrideEntry>) => {
    const keys = Object.keys(entries);
    if (keys.length <= MAX_ENTRIES) return;
    const sorted = keys.sort((a, b) => (entries[a].updatedAt ?? 0) - (entries[b].updatedAt ?? 0));
    const toRemove = sorted.slice(0, keys.length - MAX_ENTRIES);
    for (const key of toRemove) delete entries[key];
};

const upsert = (
    entries: Record<string, LyricsOverrideEntry>,
    key: string,
    patch: Partial<LyricsOverrideEntry>,
) => {
    entries[key] = {
        ...(entries[key] ?? { updatedAt: 0 }),
        ...patch,
        updatedAt: Date.now(),
    };
    evictIfOverCap(entries);
};

const dropEmpty = (entries: Record<string, LyricsOverrideEntry>, key: string) => {
    const entry = entries[key];
    if (!entry) return;
    if (
        entry.offsetMs === undefined &&
        entry.override === undefined &&
        entry.structuredIndex === undefined &&
        !entry.suppressed
    ) {
        delete entries[key];
    }
};

export const useLyricsOverridesStore = createWithEqualityFn<LyricsOverridesState>()(
    persist(
        subscribeWithSelector(
            devtools(
                immer((set) => ({
                    actions: {
                        clearOffset: (key) =>
                            set((state) => {
                                if (!state.entries[key]) return;
                                delete state.entries[key].offsetMs;
                                state.entries[key].updatedAt = Date.now();
                                dropEmpty(state.entries, key);
                            }),
                        clearOverride: (key) =>
                            set((state) => {
                                if (!state.entries[key]) return;
                                delete state.entries[key].override;
                                state.entries[key].updatedAt = Date.now();
                                dropEmpty(state.entries, key);
                            }),
                        clearSuppressed: (key) =>
                            set((state) => {
                                if (!state.entries[key]) return;
                                delete state.entries[key].suppressed;
                                state.entries[key].updatedAt = Date.now();
                                dropEmpty(state.entries, key);
                            }),
                        setOffset: (key, offsetMs) =>
                            set((state) => {
                                upsert(state.entries, key, { offsetMs });
                            }),
                        setOverride: (key, override) =>
                            set((state) => {
                                upsert(state.entries, key, {
                                    override,
                                    suppressed: false,
                                });
                            }),
                        setStructuredIndex: (key, structuredIndex) =>
                            set((state) => {
                                upsert(state.entries, key, { structuredIndex });
                            }),
                        suppress: (key) =>
                            set((state) => {
                                upsert(state.entries, key, { suppressed: true });
                            }),
                    },
                    entries: {},
                })),
                { name: 'store_lyrics_overrides' },
            ),
        ),
        {
            migrate: identityPersistMigrate<Pick<LyricsOverridesState, 'entries'>>,
            name: 'store_lyrics_overrides',
            partialize: (state) => ({ entries: state.entries }),
            version: PERSIST_VERSION_INITIAL,
        },
    ),
);

export const lyricsKey = (serverId: string | undefined, songId: string | undefined) =>
    serverId && songId ? `${serverId}:${songId}` : null;

export const useLyricsOverrideEntry = (key: null | string): LyricsOverrideEntry | undefined =>
    useLyricsOverridesStore((state) => (key ? state.entries[key] : undefined));

export const useLyricsOverridesActions = () => useLyricsOverridesStore((state) => state.actions);