5180 fichiers 38.5 Mo 0 téléchargements 221 vues mis à jour il y a 5 heures ui
nitro-v36-with-renderer-v36 / src / hooks / wired / wiredLeaderboardStore.ts
wiredLeaderboardStore.ts 8.3 Ko Télécharger le fichier
import { IWiredLeaderboardDescriptor, IWiredLeaderboardRow } from '@octane/renderer';
import { ILeaderboardConfig, LEADERBOARD_CHUNK_SIZE, LEADERBOARD_MAX_CHOICES } from '../../api/wired/WiredLeaderboard';
import { createOctaneStore } from '../../state/createOctaneStore';

/** What this player looks at on one panel: the variable, the page and the rows received for it. */
export interface IWiredLeaderboardPanelState {
    descriptor: IWiredLeaderboardDescriptor;
    /** Chosen variable; null when the panel offers none. Local to this player, as in AIR. */
    variableId: string | null;
    page: number;
    /** Exact total once a chunk told it, else null. */
    total: number | null;
    /** Rows by chunk index, for the current revision and variable only. */
    chunks: Record<number, IWiredLeaderboardRow[]>;
    movingForward: boolean;
}

export type WiredLeaderboardState = {
    /** Whether a snapshot arrived since entering the room; chunks before it are ignored. */
    ready: boolean;
    panels: Record<number, IWiredLeaderboardPanelState>;
    /** Panel ids in snapshot order. */
    order: number[];
    /** Grows with every snapshot, so subscriptions are sent again after the server reset them. */
    snapshotSeq: number;
    /**
     * The config being edited in an open leaderboard box: its panel (or, unpublished, a preview)
     * shows it live and can be dragged. editOverlayId is 0 for a box not published yet.
     */
    editDraft: ILeaderboardConfig | null;
    editOverlayId: number;
};

export type WiredLeaderboardActions = {
    applySnapshot(descriptors: IWiredLeaderboardDescriptor[]): void;
    applyChunk(overlayId: number, revision: number, variableId: string, chunkIndex: number, totalCount: number, rows: IWiredLeaderboardRow[]): void;
    selectVariable(overlayId: number, variableId: string): void;
    movePage(overlayId: number, delta: number): void;
    /** The subscription now names only these chunks; the server stops updating the others. */
    keepChunks(overlayId: number, chunkIndices: number[]): void;
    beginEdit(overlayId: number, config: ILeaderboardConfig): void;
    updateDraft(changes: Partial<ILeaderboardConfig>): void;
    endEdit(): void;
    clear(): void;
};

/** The official client's check of a descriptor; a panel that fails it is ignored. */
export const isLeaderboardDescriptorValid = (descriptor: IWiredLeaderboardDescriptor): boolean => {
    if (!descriptor || descriptor.overlayId <= 0) return false;
    if (descriptor.columns.length < 1 || descriptor.columns.length > 4 || descriptor.columns[0].type !== 0) return false;
    if (descriptor.choices.length > LEADERBOARD_MAX_CHOICES) return false;
    if (descriptor.pageSizeOption < 0 || descriptor.pageSizeOption > 2) return false;
    if (descriptor.columns.some((column) => column.width < 0 || column.width > 3)) return false;

    const ids = new Set<string>();

    for (const choice of descriptor.choices) {
        if (!choice.variableId || ids.has(choice.variableId)) return false;

        ids.add(choice.variableId);
    }

    return descriptor.choices.length ? ids.has(descriptor.defaultVariableId) : descriptor.defaultVariableId === '0';
};

const resolveSelection = (descriptor: IWiredLeaderboardDescriptor, previous: string | null): string | null => {
    if (!descriptor.choices.length) return null;
    if (previous && descriptor.choices.some((choice) => choice.variableId === previous)) return previous;

    return descriptor.defaultVariableId;
};

const freshScope = (panel: IWiredLeaderboardPanelState): IWiredLeaderboardPanelState => ({ ...panel, page: 0, total: null, chunks: {}, movingForward: false });

/**
 * The room's variable leaderboard panels as this player sees them. The snapshot says which panels
 * exist; each panel keeps the chunks of rows the server sent for the variable and revision it shows.
 */
export const useWiredLeaderboardStore = createOctaneStore<WiredLeaderboardState & WiredLeaderboardActions>()((set) => ({
    ready: false,
    panels: {},
    order: [],
    snapshotSeq: 0,
    editDraft: null,
    editOverlayId: 0,

    applySnapshot: (descriptors) =>
        set((state) => {
            const panels: Record<number, IWiredLeaderboardPanelState> = {};
            const order: number[] = [];

            for (const descriptor of descriptors ?? []) {
                if (order.length >= 4 || !isLeaderboardDescriptorValid(descriptor) || panels[descriptor.overlayId]) continue;

                const previous = state.panels[descriptor.overlayId];

                // A descriptor older than the one held is ignored, as the official client does.
                if (previous && descriptor.revision < previous.descriptor.revision) {
                    panels[descriptor.overlayId] = previous;
                    order.push(descriptor.overlayId);
                    continue;
                }

                const variableId = resolveSelection(descriptor, previous?.variableId ?? null);
                const panel: IWiredLeaderboardPanelState = previous
                    ? { ...previous, descriptor, variableId }
                    : { descriptor, variableId, page: 0, total: null, chunks: {}, movingForward: false };
                const changed = !previous || previous.descriptor.revision !== descriptor.revision || previous.variableId !== variableId;

                panels[descriptor.overlayId] = changed ? freshScope(panel) : panel;
                order.push(descriptor.overlayId);
            }

            return { ready: true, panels, order, snapshotSeq: state.snapshotSeq + 1 };
        }),

    applyChunk: (overlayId, revision, variableId, chunkIndex, totalCount, rows) =>
        set((state) => {
            const panel = state.panels[overlayId];

            if (!state.ready || !panel || panel.descriptor.revision !== revision || panel.variableId !== variableId) return state;
            if (totalCount < 0 || chunkIndex < 0 || rows.length > LEADERBOARD_CHUNK_SIZE) return state;

            const entriesPerPage = [10, 15, 20][panel.descriptor.pageSizeOption] ?? 10;
            const lastPage = Math.max(0, Math.ceil(totalCount / entriesPerPage) - 1);

            return {
                panels: {
                    ...state.panels,
                    [overlayId]: { ...panel, total: totalCount, page: Math.min(panel.page, lastPage), chunks: { ...panel.chunks, [chunkIndex]: rows } }
                }
            };
        }),

    selectVariable: (overlayId, variableId) =>
        set((state) => {
            const panel = state.panels[overlayId];

            if (!panel || panel.variableId === variableId || !panel.descriptor.choices.some((choice) => choice.variableId === variableId)) return state;

            return { panels: { ...state.panels, [overlayId]: freshScope({ ...panel, variableId }) } };
        }),

    movePage: (overlayId, delta) =>
        set((state) => {
            const panel = state.panels[overlayId];

            if (!panel || panel.total === null) return state;

            const entriesPerPage = [10, 15, 20][panel.descriptor.pageSizeOption] ?? 10;
            const lastPage = Math.max(0, Math.ceil(panel.total / entriesPerPage) - 1);
            const page = Math.max(0, Math.min(lastPage, panel.page + delta));

            if (page === panel.page) return state;

            return { panels: { ...state.panels, [overlayId]: { ...panel, page, movingForward: delta > 0 } } };
        }),

    keepChunks: (overlayId, chunkIndices) =>
        set((state) => {
            const panel = state.panels[overlayId];

            if (!panel) return state;

            const kept: Record<number, IWiredLeaderboardRow[]> = {};
            let dropped = false;

            for (const key of Object.keys(panel.chunks)) {
                const index = Number(key);

                if (chunkIndices.includes(index)) kept[index] = panel.chunks[index];
                else dropped = true;
            }

            return dropped ? { panels: { ...state.panels, [overlayId]: { ...panel, chunks: kept } } } : state;
        }),

    beginEdit: (overlayId, config) => set({ editOverlayId: overlayId, editDraft: config }),

    updateDraft: (changes) => set((state) => (state.editDraft ? { editDraft: { ...state.editDraft, ...changes } } : state)),

    endEdit: () => set({ editOverlayId: 0, editDraft: null }),

    clear: () => set({ ready: false, panels: {}, order: [], editDraft: null, editOverlayId: 0 })
}));
15Sujets
1Dépôts
20Membres
2En ligne
Profile
Inscrit
Dernière visite
Messages
0
Votes positifs
0
Votes négatifs
0

Nous utilisons des cookies pour assurer le bon fonctionnement du site et améliorer votre expérience. en savoir plus

Autoriser