From 25cfb96dfd4d1095c3693c4722c8b9699c29ca0d Mon Sep 17 00:00:00 2001 From: Andrew Date: Fri, 7 Feb 2025 21:55:15 +0300 Subject: [PATCH] Tile context menu tweaks (Loot panel) (#89114) ## About The Pull Request https://github.com/user-attachments/assets/7e81b46b-1ed7-4ef1-b246-0188846711a5 Changed the loot panel style to show items in a similar style to the regular shift context menu. Also made the window resize with contents. ## Why It's Good For The Game Better UX ## Changelog :cl: qol: Tile context menu tweaks (Loot panel) /:cl: --- .../interfaces/LootPanel/GroupedContents.tsx | 33 ++----- .../tgui/interfaces/LootPanel/IconDisplay.tsx | 4 +- .../tgui/interfaces/LootPanel/LootBox.tsx | 71 ++++++++------ .../tgui/interfaces/LootPanel/RawContents.tsx | 10 +- .../tgui/interfaces/LootPanel/index.tsx | 98 ++++++++++++------- .../tgui/styles/interfaces/LootPanel.scss | 35 ------- tgui/packages/tgui/styles/main.scss | 1 - 7 files changed, 114 insertions(+), 138 deletions(-) delete mode 100644 tgui/packages/tgui/styles/interfaces/LootPanel.scss diff --git a/tgui/packages/tgui/interfaces/LootPanel/GroupedContents.tsx b/tgui/packages/tgui/interfaces/LootPanel/GroupedContents.tsx index 706dac689b0..1a63c166f9c 100644 --- a/tgui/packages/tgui/interfaces/LootPanel/GroupedContents.tsx +++ b/tgui/packages/tgui/interfaces/LootPanel/GroupedContents.tsx @@ -1,47 +1,26 @@ -import { useMemo } from 'react'; -import { Flex } from 'tgui-core/components'; +import { Box } from 'tgui-core/components'; import { createSearch } from 'tgui-core/string'; import { LootBox } from './LootBox'; import { SearchGroup, SearchItem } from './types'; type Props = { - contents: SearchItem[]; + contents: Record; searchText: string; }; export function GroupedContents(props: Props) { const { contents, searchText } = props; - // limitations: items with different stack counts, charges etc. - const contentsByPath = useMemo(() => { - const acc: Record = {}; - - for (let i = 0; i < contents.length; i++) { - const item = contents[i]; - if (item.path) { - if (!acc[item.path]) { - acc[item.path] = []; - } - acc[item.path].push(item); - } else { - acc[item.ref] = [item]; - } - } - return acc; - }, [contents]); - - const filteredContents: SearchGroup[] = Object.entries(contentsByPath) + const filteredContents: SearchGroup[] = Object.entries(contents) .filter(createSearch(searchText, ([_, items]) => items[0].name)) .map(([_, items]) => ({ amount: items.length, item: items[0] })); return ( - + {filteredContents.map((group) => ( - - - + ))} - + ); } diff --git a/tgui/packages/tgui/interfaces/LootPanel/IconDisplay.tsx b/tgui/packages/tgui/interfaces/LootPanel/IconDisplay.tsx index a214a0a969e..2ccf5191c98 100644 --- a/tgui/packages/tgui/interfaces/LootPanel/IconDisplay.tsx +++ b/tgui/packages/tgui/interfaces/LootPanel/IconDisplay.tsx @@ -18,14 +18,14 @@ export function IconDisplay(props: Props) { size: { height, width }, } = props; - const fallback = ; + const fallback = ; if (!icon) { return fallback; } if (icon === 'n/a') { - return ; + return ; } if (icon_state) { diff --git a/tgui/packages/tgui/interfaces/LootPanel/LootBox.tsx b/tgui/packages/tgui/interfaces/LootPanel/LootBox.tsx index 33780434706..7614d4849f3 100644 --- a/tgui/packages/tgui/interfaces/LootPanel/LootBox.tsx +++ b/tgui/packages/tgui/interfaces/LootPanel/LootBox.tsx @@ -1,6 +1,6 @@ -import { Tooltip } from 'tgui-core/components'; +import { Button, Stack } from 'tgui-core/components'; import { BooleanLike } from 'tgui-core/react'; -import { capitalizeAll, capitalizeFirst } from 'tgui-core/string'; +import { capitalizeFirst } from 'tgui-core/string'; import { useBackend } from '../../backend'; import { IconDisplay } from './IconDisplay'; @@ -31,39 +31,48 @@ export function LootBox(props: Props) { item = props.item; } - const name = !item.name - ? '???' - : capitalizeFirst(item.name.split(' ')[0]).slice(0, 5); + const name = !item.name ? '???' : capitalizeFirst(item.name); - // So we can conditionally wrap tooltip const content = ( -
-
- act('grab', { - alt: event.altKey, - ctrl: event.ctrlKey, - ref: item.ref, - shift: event.shiftKey, - }) - } - onContextMenu={(event) => { - event.preventDefault(); - act('grab', { - right: true, - ref: item.ref, - }); - }} - > - - {amount > 1 &&
{amount}
} -
- {!is_blind && {name}} -
+ ); if (is_blind) return content; - return {content}; + return content; } diff --git a/tgui/packages/tgui/interfaces/LootPanel/RawContents.tsx b/tgui/packages/tgui/interfaces/LootPanel/RawContents.tsx index 9a4f97362f8..1b70487f2df 100644 --- a/tgui/packages/tgui/interfaces/LootPanel/RawContents.tsx +++ b/tgui/packages/tgui/interfaces/LootPanel/RawContents.tsx @@ -1,4 +1,4 @@ -import { Flex } from 'tgui-core/components'; +import { Box } from 'tgui-core/components'; import { createSearch } from 'tgui-core/string'; import { LootBox } from './LootBox'; @@ -17,12 +17,10 @@ export function RawContents(props: Props) { ); return ( - + {filteredContents.map((item) => ( - - - + ))} - + ); } diff --git a/tgui/packages/tgui/interfaces/LootPanel/index.tsx b/tgui/packages/tgui/interfaces/LootPanel/index.tsx index 6c3c358636a..a010982357d 100644 --- a/tgui/packages/tgui/interfaces/LootPanel/index.tsx +++ b/tgui/packages/tgui/interfaces/LootPanel/index.tsx @@ -1,6 +1,8 @@ import { useState } from 'react'; -import { Button, Input, Section, Stack } from 'tgui-core/components'; +import { useMemo } from 'react'; +import { Box, Button, Input, Section } from 'tgui-core/components'; import { isEscape } from 'tgui-core/keys'; +import { clamp } from 'tgui-core/math'; import { BooleanLike } from 'tgui-core/react'; import { useBackend } from '../../backend'; @@ -18,54 +20,78 @@ export function LootPanel(props) { const { act, data } = useBackend(); const { contents = [], searching } = data; + // limitations: items with different stack counts, charges etc. + const contentsByPathName = useMemo(() => { + const acc: Record = {}; + + for (let i = 0; i < contents.length; i++) { + const item = contents[i]; + if (item.path) { + if (!acc[item.path + item.name]) { + acc[item.path + item.name] = []; + } + acc[item.path + item.name].push(item); + } else { + acc[item.ref] = [item]; + } + } + return acc; + }, [contents]); + const [grouping, setGrouping] = useState(true); const [searchText, setSearchText] = useState(''); - const total = contents.length ? contents.length - 1 : 0; + const headerHeight = 38; + const itemHeight = 38; + const minHeight = headerHeight + itemHeight; + const maxHeight = headerHeight + itemHeight * 10; + const height: number = clamp( + headerHeight + + (!grouping ? contents.length : Object.keys(contentsByPathName).length) * + itemHeight, + minHeight, + maxHeight, + ); return ( - + + setSearchText(value)} + placeholder={`Search items...`} + /> +