nitro-v36-with-renderer-v36
/
src /
components /
room /
widgets /
chat-input /
ChatInputHabbiconSelectorView.tsx
ChatInputHabbiconSelectorView.tsx 8.6 Ko
Télécharger le fichier
import { HabbiconAssetManager, TriggerHabbiconComposer } from '@octane/renderer';
import { FC, useEffect, useMemo, useState } from 'react';
import {
HABBICON_CELL_SIZE,
HABBICON_GRID_COLUMNS,
HabbiconEntry,
localizeHabbiconName,
localizeWithFallback,
padHabbiconRow,
SendMessageComposer,
useHabbiconCatalog
} from '../../../../api';
import { UseHabbiconIcon } from '../../../../assets/images/habbicons';
import { LayoutHabbiconImageView, LayoutItemCountView, Popover, PopoverContent, PopoverTrigger } from '../../../../common';
import { BaseButton } from '../../../../common/BaseButton';
import { NativeTextTree } from '../../../../common/native-text/NativeTextTree';
import { Hint } from '../../../../common/Tooltip';
type SelectorSection = {
id: string;
title: string;
entries: HabbiconEntry[];
};
const TOP_BAR_HEIGHT = 42;
const BOTTOM_PADDING = 6;
const MENU_MIN_HEIGHT = 91;
const LIST_MIN_HEIGHT = 46;
const LIST_MAX_HEIGHT = 244;
const SECTION_TITLE_HEIGHT = 20;
const SECTION_SPACING = 4;
const measureSelectorHeight = (sections: SelectorSection[]) => {
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 * HABBICON_CELL_SIZE + (rows - 1) * 2;
contentHeight += SECTION_TITLE_HEIGHT + gridHeight + 2;
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 ChatInputHabbiconSelectorView: FC = () => {
const catalog = useHabbiconCatalog();
const [selectorVisible, setSelectorVisible] = useState(false);
const [search, setSearch] = useState('');
const sections = useMemo<SelectorSection[]>(() => {
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().includes(query)
);
return matches.length ? [{ id: 'search', title: localizeWithFallback('habbicon.search.results', 'Search results'), entries: matches }] : [];
}
const next: SelectorSection[] = [];
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(() => measureSelectorHeight(sections), [sections]);
useEffect(() => {
if (!catalog.enabled || !catalog.baseUrl) return;
void HabbiconAssetManager.getInstance().preload();
}, [catalog.baseUrl, catalog.enabled]);
if (!catalog.enabled || !catalog.baseUrl) return null;
const applyHabbicon = async (habbiconId: number, keepOpen = false) => {
await HabbiconAssetManager.getInstance().preload();
catalog.clearUnseen(habbiconId);
SendMessageComposer(new TriggerHabbiconComposer(habbiconId));
if (!keepOpen) setSelectorVisible(false);
};
return (
<NativeTextTree>
<Popover
open={selectorVisible}
onOpenChange={(open) => {
setSelectorVisible(open);
if (!open) setSearch('');
}}
>
<PopoverTrigger className="habbicon-chat-trigger" title={localizeWithFallback('habbicons.hud.title', 'Habicons')} type="button">
{catalog.lastUsedCollectionId ? (
<LayoutHabbiconImageView collection outlined id={catalog.lastUsedCollectionId} size={22} />
) : (
<img alt="" src={UseHabbiconIcon} />
)}
{!selectorVisible && catalog.unseenCount > 0 && <span className="habbicon-unseen-count">{catalog.unseenCount}</span>}
</PopoverTrigger>
<PopoverContent align="start" avoidCollisions={false} className="habbicon-selector-popover" side="top" sideOffset={17}>
<div className="habbicon-selector-window" style={{ height: windowHeight }}>
<div className="habbicon-selector-controls">
<div className="habbicon-selector-search">
<input 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="habbicon-selector-book-button"
type="button"
onClick={() => {
catalog.setBookVisible(true);
setSelectorVisible(false);
}}
>
{localizeWithFallback('habbicons.hud.get_more', 'Get more')}
</BaseButton>
</div>
{sections.length > 0 ? (
<div className="habbicon-selector-sections has-classic-scrollbar" style={{ height: listHeight }}>
{sections.map((section) => (
<section className="habbicon-selector-section" key={section.id}>
<div className="habbicon-selector-section-title">{section.title}</div>
<div className="habbicon-selector-grid">
{padHabbiconRow(section.entries).map((entry, index) =>
entry ? (
<Hint key={entry.id} content={localizeHabbiconName(entry)}>
<BaseButton aria-label={localizeHabbiconName(entry)}
className="habbicon-selector-item filled"
type="button"
onClick={(event) => void applyHabbicon(entry.id, event.shiftKey)}
>
<LayoutHabbiconImageView id={entry.id} size={30} />
{catalog.isUnseen(entry.id) && <LayoutItemCountView count={1} style={{ top: -5, right: -5 }} />}
</BaseButton>
</Hint>
) : (
<div className="habbicon-selector-item empty" key={`empty-${section.id}-${index}`} />
)
)}
</div>
</section>
))}
</div>
) : (
<div className="habbicon-selector-empty">{localizeWithFallback('habbicons.no_habbicons', 'No Habicons')}</div>
)}
</div>
</PopoverContent>
</Popover>
</NativeTextTree>
);
};
