5180 fichiers 38.5 Mo 0 téléchargements 395 vues mis à jour il y a 7 heures ui
nitro-v36-with-renderer-v36 / src / components / friends / views / messenger / FriendsMessengerHabbiconPickerView.tsx
FriendsMessengerHabbiconPickerView.tsx 7.1 Ko Télécharger le fichier
import { FC, useEffect, useMemo, useRef, useState } from 'react';
import { HABBICON_GRID_COLUMNS, HabbiconEntry, localizeHabbiconName, localizeWithFallback, padHabbiconRow, useHabbiconCatalog } from '../../../../api';
import { LayoutHabbiconImageView, LayoutItemCountView } from '../../../../common';
import { InlineDialog } from '../../../../common/InlineDialog';
import { BaseButton } from '../../../../common/BaseButton';
import { Hint } from '../../../../common/Tooltip';
import { NativeTextTree } from '../../../../common/native-text/NativeTextTree';

type PickerSection = {
    id: string;
    title: string;
    entries: HabbiconEntry[];
};

const TILE_HEIGHT = 45;
const TILE_GAP = 2;
const TOP_BAR_HEIGHT = 42;
const BOTTOM_PADDING = 6;
const MENU_MIN_HEIGHT = 94;
const LIST_MIN_HEIGHT = 46;
const LIST_MAX_HEIGHT = 256;
const SECTION_TITLE_HEIGHT = 20;
const SECTION_EXTRA = 2;
const SECTION_SPACING = 4;

const measurePickerHeight = (sections: PickerSection[]) => {
    if (!sections.length) return { windowHeight: 138, listHeight: 88 };

    let contentHeight = 0;

    for (const [index, section] of sections.entries()) {
        const rows = Math.max(1, Math.ceil(section.entries.length / HABBICON_GRID_COLUMNS));
        const gridHeight = rows * TILE_HEIGHT + (rows - 1) * TILE_GAP;

        contentHeight += SECTION_TITLE_HEIGHT + gridHeight + SECTION_EXTRA;

        if (index < sections.length - 1) contentHeight += SECTION_SPACING;
    }

    const listHeight = Math.min(LIST_MAX_HEIGHT, Math.max(LIST_MIN_HEIGHT, contentHeight + 2));

    return {
        listHeight,
        windowHeight: Math.max(MENU_MIN_HEIGHT, TOP_BAR_HEIGHT + listHeight + BOTTOM_PADDING)
    };
};

export const FriendsMessengerHabbiconPickerView: FC<{
    onClose: () => void;
    onOpenHub: () => void;
    onSelect: (id: number, keepOpen?: boolean) => void;
}> = ({ onClose, onOpenHub, onSelect }) => {
    const catalog = useHabbiconCatalog();
    const [search, setSearch] = useState('');
    const pickerRef = useRef<HTMLDivElement>(null);

    useEffect(() => {
        const closeOnOutsideClick = (event: MouseEvent) => {
            if (!pickerRef.current?.contains(event.target as Node)) onClose();
        };
        const closeOnEscape = (event: KeyboardEvent) => {
            if (event.key !== 'Escape') return;

            if (search) {
                setSearch('');
                return;
            }

            onClose();
        };

        document.addEventListener('mousedown', closeOnOutsideClick);
        document.addEventListener('keydown', closeOnEscape);

        return () => {
            document.removeEventListener('mousedown', closeOnOutsideClick);
            document.removeEventListener('keydown', closeOnEscape);
        };
    }, [onClose, search]);

    const sections = useMemo<PickerSection[]>(() => {
        const query = search.trim().toLowerCase();

        if (query) {
            const matches = catalog.ownedEntries.filter(
                (entry) =>
                    localizeHabbiconName(entry).toLowerCase().includes(query) || entry.nameKey.toLowerCase().includes(query) || entry.id.toString() === query
            );

            return matches.length ? [{ id: 'search', title: localizeWithFallback('habbicon.search.results', 'Search results'), entries: matches }] : [];
        }

        const next: PickerSection[] = [];
        const byId = new Map(catalog.ownedEntries.map((entry) => [entry.id, entry]));
        const favorites = catalog.favoriteIds.map((id) => byId.get(id)).filter(Boolean) as HabbiconEntry[];
        const recent = catalog.recentIds.map((id) => byId.get(id)).filter(Boolean) as HabbiconEntry[];

        if (favorites.length) next.push({ id: 'favorites', title: localizeWithFallback('habbicons.favourites.title', 'Favorites'), entries: favorites });
        if (recent.length) next.push({ id: 'recent', title: localizeWithFallback('habbicon.recently.used', 'Recently used'), entries: recent });

        for (const set of catalog.ownedSets) {
            if (set.entries.length) next.push({ id: set.id, title: set.title, entries: set.entries });
        }

        return next;
    }, [catalog.ownedEntries, catalog.favoriteIds, catalog.recentIds, catalog.ownedSets, search]);

    const { listHeight, windowHeight } = useMemo(() => measurePickerHeight(sections), [sections]);

    const choose = (id: number, keepOpen = false) => {
        catalog.clearUnseen(id);
        onSelect(id, keepOpen);
    };

    return (
        <NativeTextTree>
            <InlineDialog ref={pickerRef} aria-label="Habbicons" className="messenger-habbicon-picker" closeOnEscape={false} style={{ height: windowHeight }}>
                <div className="messenger-habbicon-controls">
                    <div className="messenger-habbicon-search">
                        <input autoFocus maxLength={24} value={search} onChange={(event) => setSearch(event.target.value)} />
                        {!search && <span>{localizeWithFallback('generic.search', 'Search')}</span>}
                        {search && <BaseButton aria-label="Clear" type="button" onClick={() => setSearch('')} />}
                    </div>
                    <BaseButton className="messenger-btn messenger-habbicon-get-more" type="button" onClick={onOpenHub}>
                        {localizeWithFallback('habbicons.hud.get_more', 'Get more')}
                    </BaseButton>
                </div>
                {sections.length > 0 ? (
                    <div className="messenger-habbicon-scroll has-classic-scrollbar" style={{ height: listHeight }}>
                        {sections.map((section) => (
                            <section key={section.id}>
                                <strong>{section.title}</strong>
                                <div className="messenger-habbicon-grid">
                                    {padHabbiconRow(section.entries).map((entry, index) =>
                                        entry ? (
                                            <Hint key={entry.id} content={localizeHabbiconName(entry)}><BaseButton
                                                type="button"
                                                onClick={(event) => choose(entry.id, event.shiftKey)}
                                            >
                                                <LayoutHabbiconImageView id={entry.id} size={40} />
                                                {catalog.isUnseen(entry.id) && <LayoutItemCountView count={1} style={{ top: -3, right: -3 }} />}
                                            </BaseButton></Hint>
                                        ) : (
                                            <div className="empty" key={`empty-${section.id}-${index}`} />
                                        )
                                    )}
                                </div>
                            </section>
                        ))}
                    </div>
                ) : (
                    <div className="messenger-habbicon-empty">{localizeWithFallback('habbicons.no_habbicons', 'No Habicons')}</div>
                )}
            </InlineDialog>
        </NativeTextTree>
    );
};
15Sujets
1Dépôts
20Membres
3En 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