5180 fichiers 38.5 Mo 0 téléchargements 227 vues mis à jour il y a 5 heures ui
RareValuesView.tsx 8.8 Ko Télécharger le fichier
import { AddLinkEventTracker, GetRoomEngine, GetSessionDataManager, ILinkEventTracker, IRareValue, RemoveLinkEventTracker } from '@octane/renderer';
import { FC, useEffect, useMemo, useRef, useState } from 'react';
import { LocalizeFormattedNumber, LocalizeText } from '../../api';
import { Column, Flex, LayoutCurrencyIcon, LayoutImage, Text } from '../../common';
import { useRareValues } from '../../hooks';
import { OctaneCard, OctaneInput } from '../../layout';
import '../../css/rare-values/RareValuesAirText.css';
import { NativeTextTree } from '../../common/native-text/NativeTextTree';

const PAGE_SIZE = 50;

interface RareValueRow {
    spriteId: number;
    name: string;
    iconUrl: string;
    value: IRareValue;
}

export const RareValuesView: FC<{}> = () => {
    const [isVisible, setIsVisible] = useState(false);
    const [searchValue, setSearchValue] = useState('');
    const [visibleCount, setVisibleCount] = useState(PAGE_SIZE);
    const { values = null, loaded = false } = useRareValues();
    const scrollRef = useRef<HTMLDivElement>(null);
    const sentinelRef = useRef<HTMLDivElement>(null);

    useEffect(() => {
        const linkTracker: ILinkEventTracker = {
            linkReceived: (url: string) => {
                const parts = url.split('/');
                if (parts.length < 2) return;

                switch (parts[1]) {
                    case 'show':
                        setIsVisible(true);
                        return;
                    case 'hide':
                        setIsVisible(false);
                        return;
                    case 'toggle':
                        setIsVisible((prev) => !prev);
                        return;
                }
            },
            eventUrlPrefix: 'rare-values/'
        };

        AddLinkEventTracker(linkTracker);

        return () => RemoveLinkEventTracker(linkTracker);
    }, []);

    const rows = useMemo<RareValueRow[]>(() => {
        if (!values) return [];

        const list: RareValueRow[] = [];

        values.forEach((value, spriteId) => {
            if (value.points <= 0) return;

            const floorData = GetSessionDataManager().getFloorItemData(spriteId);
            const wallData = floorData ? null : GetSessionDataManager().getWallItemData(spriteId);
            const data = floorData ?? wallData;

            if (!data) return;

            const iconUrl = floorData ? GetRoomEngine().getFurnitureFloorIconUrl(spriteId) : GetRoomEngine().getFurnitureWallIconUrl(spriteId);

            list.push({ spriteId, name: data.name || data.className || `#${spriteId}`, iconUrl, value });
        });

        list.sort((a, b) => b.value.points - a.value.points);

        return list;
    }, [values]);

    const filtered = useMemo<RareValueRow[]>(() => {
        const query = searchValue.trim().toLocaleLowerCase();

        if (!query) return rows;

        return rows.filter((row) => row.name.toLocaleLowerCase().includes(query));
    }, [rows, searchValue]);

    // Reset paging when the underlying list changes (typed in search, data loaded).
    useEffect(() => {
        setVisibleCount(PAGE_SIZE);
        if (scrollRef.current) scrollRef.current.scrollTop = 0;
    }, [filtered]);

    // Infinite scroll: grow visibleCount by PAGE_SIZE whenever the sentinel
    // enters the viewport. The root is the scroll container so the trigger
    // fires reliably inside an in-app modal (no document scroll).
    useEffect(() => {
        if (!isVisible) return;
        if (visibleCount >= filtered.length) return;

        const sentinel = sentinelRef.current;
        const root = scrollRef.current;
        if (!sentinel || !root) return;

        const observer = new IntersectionObserver(
            (entries) => {
                if (entries.some((entry) => entry.isIntersecting)) {
                    setVisibleCount((prev) => Math.min(prev + PAGE_SIZE, filtered.length));
                }
            },
            { root, rootMargin: '120px 0px' }
        );

        observer.observe(sentinel);
        return () => observer.disconnect();
    }, [isVisible, visibleCount, filtered.length]);

    if (!isVisible) return null;

    const visibleRows = filtered.slice(0, visibleCount);
    const hasMore = visibleCount < filtered.length;

    return (
        <NativeTextTree>
            <OctaneCard className="octane-rare-values-window w-[460px] h-[520px]" uniqueKey="rare-values">
                <OctaneCard.Header headerText={LocalizeText('rarevalues.title')} onCloseClick={() => setIsVisible(false)} />
                <OctaneCard.Content>
                    <Column gap={2} className="h-full p-2">
                        <Flex alignItems="center" gap={2} className="rounded border border-black/10 bg-white px-2 py-1 shadow-inner">
                            <span className="text-black/40">🔍</span>
                            <OctaneInput
                                placeholder={LocalizeText('generic.search')}
                                value={searchValue}
                                onChange={(event) => setSearchValue(event.target.value)}
                                className="grow !border-0 !bg-transparent !p-0 !shadow-none focus:!ring-0 text-[12px] font-normal"
                            />
                        </Flex>
                        {loaded && (
                            <Flex alignItems="center" justifyContent="between" className="px-1 text-[11px] text-black/55">
                                <span>
                                    {filtered.length} {LocalizeText('rarevalues.title').toLowerCase()}
                                </span>
                                {hasMore && (
                                    <span>
                                        {visibleRows.length} / {filtered.length}
                                    </span>
                                )}
                            </Flex>
                        )}
                        <div
                            ref={scrollRef}
                            className="grow overflow-auto rounded border border-black/10 bg-gradient-to-b from-[#f6f8fb] to-[#eaf1f6] shadow-inner"
                        >
                            {!loaded && (
                                <div className="p-6 text-center">
                                    <Text className="text-black/55">{LocalizeText('rarevalues.loading')}</Text>
                                </div>
                            )}
                            {loaded && !filtered.length && (
                                <div className="p-6 text-center">
                                    <Text className="text-black/55">{LocalizeText('rarevalues.empty')}</Text>
                                </div>
                            )}
                            {visibleRows.map((row, index) => (
                                <Flex
                                    key={row.spriteId}
                                    alignItems="center"
                                    gap={2}
                                    className={`border-b border-black/[0.06] px-2 py-1.5 transition-colors hover:bg-[#cfe4f1] ${index % 2 ? 'bg-white/60' : 'bg-[#e9f1f7]'}`}
                                >
                                    <div className="flex h-11 w-11 shrink-0 items-center justify-center rounded border border-black/10 bg-white shadow-sm">
                                        <LayoutImage imageUrl={row.iconUrl} className="h-9 w-9 bg-contain bg-center bg-no-repeat" />
                                    </div>
                                    <Text truncate className="grow text-[12px] text-black">
                                        {row.name}
                                    </Text>
                                    <Flex alignItems="center" gap={1} className="shrink-0 rounded-full bg-white/80 px-2 py-0.5 shadow-sm">
                                        <Text bold textEnd className="text-[12px] text-[#2f6f95]">
                                            {LocalizeFormattedNumber(row.value.points)}
                                        </Text>
                                        <LayoutCurrencyIcon type={row.value.pointsType} />
                                    </Flex>
                                </Flex>
                            ))}
                            {hasMore && (
                                <div ref={sentinelRef} className="flex items-center justify-center py-3">
                                    <Text className="text-[11px] text-black/45">
                                        {LocalizeText('rarevalues.loading.more')}
                                    </Text>
                                </div>
                            )}
                        </div>
                    </Column>
                </OctaneCard.Content>
            </OctaneCard>
        </NativeTextTree>
    );
};
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