From 63f828621dd86ff4f8dac8613cb5dccaa8ef8191 Mon Sep 17 00:00:00 2001 From: Paul Mullen <101871009+mullenpaul@users.noreply.github.com> Date: Wed, 29 May 2024 18:21:23 +0100 Subject: [PATCH] Ghost orbit refactor (#6346) # About the pull request Some usability changes to the orbit menu and general refactoring. - Marines now appear per squad - Marine job icons now appear in line with the name - Marine job is sorted by job, similar to squad UI - Xenos now appear per hive # Explain why it's good for the game Improving usability is a good thing # Testing Photographs and Procedure
Screenshots & Videos Put screenshots and videos here with an empty line between the screenshots and the `
` tags. https://media.discordapp.net/attachments/964684928161808384/1244701305751212084/image.png?ex=6656baac&is=6655692c&hm=6d0bb3234ac1d511e55978a046414f080c95f8c5dd2bedc51bfb3787ee1ff754&=&format=webp&quality=lossless&width=958&height=1342 https://media.discordapp.net/attachments/964684928161808384/1244783033517871154/image.png?ex=665706ca&is=6655b54a&hm=591f86a278ab6217d28a6890efef7a344f4467b25432a8c1bcab3723004e223b&=&format=webp&quality=lossless&width=958&height=1342
# Changelog :cl: ui: orbit menu now splits marine squads ui: orbit menu now splits xeno hives ui: orbit menu sorts marines by job /:cl: --- tgui/packages/tgui/interfaces/Orbit/index.tsx | 307 +++++++++++------- tgui/packages/tgui/interfaces/Orbit/types.ts | 21 ++ 2 files changed, 213 insertions(+), 115 deletions(-) diff --git a/tgui/packages/tgui/interfaces/Orbit/index.tsx b/tgui/packages/tgui/interfaces/Orbit/index.tsx index 76a2ce874497..bc440e733939 100644 --- a/tgui/packages/tgui/interfaces/Orbit/index.tsx +++ b/tgui/packages/tgui/interfaces/Orbit/index.tsx @@ -1,8 +1,8 @@ -import { filter, sortBy } from 'common/collections'; import { capitalizeFirst } from 'common/string'; -import { useState } from 'react'; +import { createContext, useContext, useState } from 'react'; import { useBackend } from 'tgui/backend'; import { + Box, Button, Collapsible, ColorBox, @@ -21,39 +21,49 @@ import { getMostRelevant, isJobOrNameMatch, } from './helpers'; -import type { Observable, OrbitData } from './types'; +import { + buildSquadObservable, + groupSorter, + type Observable, + type OrbitData, + splitter, +} from './types'; + +type search = { + value: string; + setValue: (value: string) => void; +}; -export const Orbit = (props) => { +const SearchContext = createContext({ value: '', setValue: () => {} }); + +export const Orbit = () => { const [searchQuery, setSearchQuery] = useState(''); return ( - - - - - -
- -
-
-
+ + + + + + +
+ +
+
+
+
); }; /** Controls filtering out the list of observables via search */ -const ObservableSearch = (props: { - readonly searchQuery: string; - readonly setSearchQuery: React.Dispatch>; -}) => { +const ObservableSearch = () => { const { act, data } = useBackend(); - const { searchQuery, setSearchQuery } = props; const { humans = [], marines = [], survivors = [], xenos = [] } = data; let auto_observe = data.auto_observe; @@ -74,6 +84,8 @@ const ObservableSearch = (props: { } }; + const { value, setValue } = useContext(SearchContext); + return (
@@ -85,9 +97,9 @@ const ObservableSearch = (props: { autoFocus fluid onEnter={(event, value) => orbitMostRelevant(value)} - onInput={(event, value) => setSearchQuery(value)} + onInput={(event, value) => setValue(value)} placeholder="Search..." - value={searchQuery} + value={value} /> @@ -96,7 +108,7 @@ const ObservableSearch = (props: { color={auto_observe ? 'good' : 'transparent'} icon={auto_observe ? 'toggle-on' : 'toggle-off'} onClick={() => act('toggle_auto_observe')} - tooltip={`Toggle Full Observe. When active, you'll see the UI / full inventory of whoever you're orbiting. Neat!`} + tooltip={`Toggle Full Observe. When active, you'll see the UI / full inventory of whoever you're orbiting.`} tooltipPosition="bottom-start" /> @@ -114,14 +126,137 @@ const ObservableSearch = (props: { ); }; +const xenoSplitter = (members: Array) => { + const primeHive: Array = []; + const corruptedHive: Array = []; + + members.forEach((x) => { + if (x.full_name?.includes('Corrupted')) { + corruptedHive.push(x); + } else { + primeHive.push(x); + } + }); + const squads = [ + buildSquadObservable('Prime', 'xeno', primeHive), + buildSquadObservable('Corrupted', 'green', corruptedHive), + ]; + return squads; +}; + +const marineSplitter = (members: Array) => { + const alphaSquad: Array = []; + const bravoSquad: Array = []; + const charlieSquad: Array = []; + const deltaSquad: Array = []; + const foxtrotSquad: Array = []; + const other: Array = []; + + members.forEach((x) => { + if (x.job?.includes('Alpha')) { + alphaSquad.push(x); + } else if (x.job?.includes('Bravo')) { + bravoSquad.push(x); + } else if (x.job?.includes('Charlie')) { + charlieSquad.push(x); + } else if (x.job?.includes('Delta')) { + deltaSquad.push(x); + } else if (x.job?.includes('Foxtrot')) { + foxtrotSquad.push(x); + } else { + other.push(x); + } + }); + + const squads = [ + buildSquadObservable('Alpha', 'red', alphaSquad), + buildSquadObservable('Bravo', 'yellow', bravoSquad), + buildSquadObservable('Charlie', 'purple', charlieSquad), + buildSquadObservable('Delta', 'blue', deltaSquad), + buildSquadObservable('Foxtrot', 'teal', foxtrotSquad), + buildSquadObservable('Other', 'grey', other), + ]; + return squads; +}; + +const rankList = [ + 'Rifleman', + 'Spotter', + 'Hospital Corpsman', + 'Combat Technician', + 'Smartgunner', + 'Weapons Specialist', + 'Fireteam Leader', + 'Squad Leader', +]; +const marineSort = (a: Observable, b: Observable) => { + const a_index = rankList.findIndex((str) => a.job?.includes(str)) ?? 0; + const b_index = rankList.findIndex((str) => b.job?.includes(str)) ?? 0; + if (a_index === b_index) { + return a.full_name.localeCompare(b.full_name); + } + return a_index > b_index ? -1 : 1; +}; + +const GroupedObservable = (props: { + readonly color?: string; + readonly section: Array; + readonly title: string; + readonly splitter: splitter; + readonly sorter?: groupSorter; +}) => { + const { color, section = [], title } = props; + + const { value: searchQuery } = useContext(SearchContext); + + if (!section.length) { + return null; + } + + const filteredSection = section + .filter((observable) => isJobOrNameMatch(observable, searchQuery)) + .sort((a, b) => + a.full_name + .toLocaleLowerCase() + .localeCompare(b.full_name.toLocaleLowerCase()), + ); + + if (!filteredSection.length) { + return null; + } + + const squads = props.splitter(filteredSection); + + return ( + + + + {squads.map((x) => ( + + ))} + + + + ); +}; + /** * The primary content display for points of interest. * Renders a scrollable section replete with subsections for each * observable group. */ -const ObservableContent = (props: { readonly searchQuery: string }) => { +const ObservableContent = () => { const { data } = useBackend(); - const { searchQuery } = props; const { humans = [], marines = [], @@ -150,138 +285,76 @@ const ObservableContent = (props: { readonly searchQuery: string }) => { return ( - - - + + - + - + - - - - - - - - - + + + + + + + + ); }; @@ -294,21 +367,22 @@ const ObservableSection = (props: { readonly color?: string; readonly section: Array; readonly title: string; - readonly searchQuery: string; }) => { - const { color, section = [], title, searchQuery } = props; + const { color, section = [], title } = props; + + const { value: searchQuery } = useContext(SearchContext); if (!section.length) { return null; } - const filteredSection = sortBy( - filter(section, (observable) => isJobOrNameMatch(observable, searchQuery)), - (observable) => - getDisplayName(observable.full_name, observable.nickname) - .replace(/^"/, '') - .toLowerCase(), - ); + const filteredSection = section + .filter((observable) => isJobOrNameMatch(observable, searchQuery)) + .sort((a, b) => + a.full_name + .toLocaleLowerCase() + .localeCompare(b.full_name.toLocaleLowerCase()), + ); if (!filteredSection.length) { return null; @@ -356,6 +430,9 @@ const ObservableItem = (props: { tooltipPosition="bottom-start" > {displayHealth && } + {!!icon && ( + + )} {capitalizeFirst(getDisplayName(full_name, nickname))} {!!orbiters && ( <> diff --git a/tgui/packages/tgui/interfaces/Orbit/types.ts b/tgui/packages/tgui/interfaces/Orbit/types.ts index afbed5b16468..8318a91f1c89 100644 --- a/tgui/packages/tgui/interfaces/Orbit/types.ts +++ b/tgui/packages/tgui/interfaces/Orbit/types.ts @@ -39,3 +39,24 @@ export type Observable = { orbiters?: number; ref: string; }; + +export type SquadObservable = { + members: Array; + color: string; + title: string; +}; + +export const buildSquadObservable: ( + title: string, + color: string, + members: Array, +) => SquadObservable = (title, color, members = []) => { + return { + members: members, + color: color, + title: title, + }; +}; + +export type splitter = (members: Array) => Array; +export type groupSorter = (a: Observable, b: Observable) => number;