Skip to content

Commit

Permalink
Port and use VirtualList in TTSSeedsExplorer (#1448)
Browse files Browse the repository at this point in the history
<!-- Пишите **НИЖЕ** заголовков и **ВЫШЕ** комментариев, иначе что то
может пойти не так. -->
<!-- Вы можете прочитать Contributing.MD, если хотите узнать больше. -->

## Что этот PR делает
Кое-какой порт VirtualList с ТГ, и использование его в ТТС эксплорере
Плюс слегка переделанный интерфейс под стать ТГ

## Почему это хорошо для игры
Чуть меньше лагает, чуть лучше выглядит

## Изображения изменений

![image](https://github.com/user-attachments/assets/255d8ed9-58a0-4b6b-bd94-fa3bd4f0e597)

## Changelog

:cl:
tweak: ТТС эксплорер стал ещё немного лучше
/:cl:
  • Loading branch information
AyIong authored Aug 27, 2024
1 parent 057aa32 commit 9187314
Show file tree
Hide file tree
Showing 3 changed files with 227 additions and 131 deletions.
73 changes: 73 additions & 0 deletions tgui/packages/tgui/components/VirtualList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { Component, createRef } from 'inferno';

interface Props {
children: any;
}

/**
* A vertical list that renders items to fill space up to the extents of the
* current window, and then defers rendering of other items until they come
* into view.
*/
export class VirtualList extends Component<Props, any> {
containerRef: any;
intervalId: any;

constructor(props: Props) {
super(props);

this.containerRef = createRef();
this.state = {
visibleElements: 1,
padding: 0,
};
}

adjustExtents = () => {
const { children } = this.props;
const { visibleElements } = this.state;
const current = this.containerRef.current;

if (!children || !Array.isArray(children) || !current || visibleElements >= children.length) {
return;
}

const unusedArea = document.body.offsetHeight - current.getBoundingClientRect().bottom;
const averageItemHeight = Math.ceil(current.offsetHeight / visibleElements);

if (unusedArea > 0) {
const newVisibleElements = Math.min(
children.length,
visibleElements + Math.max(1, Math.ceil(unusedArea / averageItemHeight))
);

this.setState({
visibleElements: newVisibleElements,
padding: (children.length - newVisibleElements) * averageItemHeight,
});
}
};

componentDidMount() {
this.adjustExtents();
this.intervalId = setInterval(this.adjustExtents, 100);
}

componentWillUnmount() {
clearInterval(this.intervalId);
}

render() {
const { children } = this.props;
const { visibleElements, padding } = this.state;

return (
<div className={'VirtualList'}>
<div className={'VirtualList__Container'} ref={this.containerRef}>
{Array.isArray(children) ? children.slice(0, visibleElements) : null}
</div>
<div className={'VirtualList__Padding'} style={{ 'padding-bottom': `${padding}px` }} />
</div>
);
}
}
133 changes: 78 additions & 55 deletions tgui/packages/tgui/interfaces/TTSSeedsExplorer.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { BooleanLike } from 'common/react';
import { useBackend, useLocalState } from '../backend';
import { Button, LabeledList, Table, Section, Dropdown, Input, BlockQuote, Box, Icon, Stack } from '../components';
import { VirtualList } from '../components/VirtualList';
import { Window } from '../layouts';

type Seed = {
Expand All @@ -26,7 +27,7 @@ type TTSData = {
};

const donatorTiers = {
0: 'Бесплатные',
0: 'Free',
1: 'Tier I',
2: 'Tier II',
3: 'Tier III',
Expand Down Expand Up @@ -76,9 +77,9 @@ const getCheckboxGroup = (itemsList, selectedList, setSelected, contentKey = nul

export const TTSSeedsExplorer = () => {
return (
<Window width={600} height={800}>
<Window width={1000} height={685}>
<Window.Content>
<Stack fill vertical>
<Stack fill>
<TTSSeedsExplorerContent />
</Stack>
</Window.Content>
Expand All @@ -91,7 +92,10 @@ export const TTSSeedsExplorerContent = (props, context) => {

const { providers, seeds, selected_seed, phrases, donator_level, character_gender } = data;

const categories = seeds.map((seed) => seed.category).filter((category, i, a) => a.indexOf(category) === i);
const categories = seeds
.map((seed) => seed.category)
.filter((category, i, a) => a.indexOf(category) === i)
.sort((a, b) => a.localeCompare(b));
const genders = seeds.map((seed) => seed.gender).filter((gender, i, a) => a.indexOf(gender) === i);
const donatorLevels = seeds
.map((seed) => seed.required_donator_level)
Expand All @@ -115,6 +119,7 @@ export const TTSSeedsExplorerContent = (props, context) => {
);
const [selectedPhrase, setSelectedPhrase] = useLocalState(context, 'selectedPhrase', phrases[0]);
const [searchtext, setSearchtext] = useLocalState(context, 'searchtext', '');
const [searchToggle, setSearchToggle] = useLocalState(context, 'searchToggle', false);

let providerCheckboxes = getCheckboxGroup(providers, selectedProviders, setSelectedProviders, 'name');
let genderesCheckboxes = getCheckboxGroup(genders, selectedGenders, setSelectedGenders);
Expand All @@ -125,12 +130,23 @@ export const TTSSeedsExplorerContent = (props, context) => {
<Dropdown
options={phrases}
selected={selectedPhrase.replace(/(.{60})..+/, '$1...')}
width="445px"
width="100%"
onSelected={(value) => setSelectedPhrase(value)}
/>
);

let searchBar = <Input placeholder="Название..." width="100%" onInput={(e, value) => setSearchtext(value)} />;
let searchBar = (
<>
{searchToggle && <Input placeholder="Название..." width={20} onInput={(e, value) => setSearchtext(value)} />}
<Button
icon="magnifying-glass"
tooltip="Переключить поиск"
tooltipPosition="bottom-end"
selected={searchToggle}
onClick={() => setSearchToggle(!searchToggle)}
/>
</>
);

const availableSeeds = seeds
.sort((a, b) => {
Expand Down Expand Up @@ -206,57 +222,64 @@ export const TTSSeedsExplorerContent = (props, context) => {

return (
<>
<Stack.Item height="175px">
<Section fill scrollable title="Фильтры">
<LabeledList>
<LabeledList.Item label="Провайдеры">{providerCheckboxes}</LabeledList.Item>
<LabeledList.Item label="Пол">{genderesCheckboxes}</LabeledList.Item>
<LabeledList.Item label="Уровень подписки">{donatorLevelsCheckboxes}</LabeledList.Item>
<LabeledList.Item label="Фраза">{phrasesSelect}</LabeledList.Item>
<LabeledList.Item label="Поиск">{searchBar}</LabeledList.Item>
</LabeledList>
</Section>
</Stack.Item>
<Stack.Item height="25%">
<Section
fill
scrollable
title="Категории"
buttons={
<>
<Button
icon="times"
content="Убрать всё"
disabled={selectedCategories.length === 0}
onClick={() => setSelectedCategories([])}
/>
<Button
icon="check"
content="Выбрать всё"
disabled={selectedCategories.length === categories.length}
onClick={() => setSelectedCategories(categories)}
/>
</>
}
>
{categoriesCheckboxes}
</Section>
<Stack.Item basis="30.5em">
<Stack fill vertical>
<Stack.Item height="12.5em">
<Section fill title="Фильтры">
<LabeledList>
<LabeledList.Item label="Провайдеры">{providerCheckboxes}</LabeledList.Item>
<LabeledList.Item label="Пол">{genderesCheckboxes}</LabeledList.Item>
<LabeledList.Item label="Тир">{donatorLevelsCheckboxes}</LabeledList.Item>
<LabeledList.Item label="Фраза">{phrasesSelect}</LabeledList.Item>
</LabeledList>
</Section>
</Stack.Item>
<Stack.Item grow>
<Section
fill
scrollable
title="Категории"
buttons={
<>
<Button
icon="times"
disabled={selectedCategories.length === 0}
onClick={() => setSelectedCategories([])}
>
Убрать всё
</Button>
<Button
icon="check"
disabled={selectedCategories.length === categories.length}
onClick={() => setSelectedCategories(categories)}
>
Выбрать всё
</Button>
</>
}
>
{categoriesCheckboxes}
</Section>
</Stack.Item>
<Stack.Item>
<Section>
<BlockQuote>
<Box fontSize="11px">
{`Для поддержания и развития сообщества в условиях растущих расходов часть голосов пришлось сделать доступными только за материальную поддержку сообщества.`}
</Box>
<Box mt={1.5} italic color="gray" fontSize="10px">
{`Подробнее об этом можно узнать в нашем Discord-сообществе.`}
</Box>
</BlockQuote>
</Section>
</Stack.Item>
</Stack>
</Stack.Item>
<Stack.Item grow>
<Section fill scrollable title={`Голоса (${availableSeeds.length}/${seeds.length})`}>
<Table>{seedsRow}</Table>
</Section>
</Stack.Item>
<Stack.Item>
<Section>
<BlockQuote>
<Box>
{`Для поддержания и развития сообщества в условиях растущих расходов часть голосов пришлось сделать доступными только за материальную поддержку сообщества.`}
</Box>
<Box mt={2} italic>
{`Подробнее об этом можно узнать в нашем Discord-сообществе.`}
</Box>
</BlockQuote>
<Section fill scrollable title={`Голоса (${availableSeeds.length}/${seeds.length})`} buttons={searchBar}>
<Table>
<VirtualList>{seedsRow}</VirtualList>
</Table>
</Section>
</Stack.Item>
</>
Expand Down
152 changes: 76 additions & 76 deletions tgui/public/tgui.bundle.js

Large diffs are not rendered by default.

0 comments on commit 9187314

Please sign in to comment.