mirror of
https://github.com/SPLURT-Station/S.P.L.U.R.T-Station-13.git
synced 2025-12-09 16:07:40 +00:00
244 lines
6.6 KiB
JavaScript
244 lines
6.6 KiB
JavaScript
import { classes } from 'common/react';
|
|
import { createSearch } from 'common/string';
|
|
import { multiline } from 'common/string';
|
|
import { resolveAsset } from '../assets';
|
|
import { useBackend, useLocalState } from '../backend';
|
|
import { Box, Button, Divider, Flex, Icon, Input, Section } from '../components';
|
|
import { Window } from '../layouts';
|
|
|
|
const PATTERN_NUMBER = / \(([0-9]+)\)$/;
|
|
|
|
const searchFor = searchText => createSearch(searchText, thing => thing.name);
|
|
|
|
const compareString = (a, b) => a < b ? -1 : a > b;
|
|
|
|
const compareNumberedText = (a, b) => {
|
|
const aName = a.name;
|
|
const bName = b.name;
|
|
|
|
// Check if aName and bName are the same except for a number at the end
|
|
// e.g. Medibot (2) and Medibot (3)
|
|
const aNumberMatch = aName.match(PATTERN_NUMBER);
|
|
const bNumberMatch = bName.match(PATTERN_NUMBER);
|
|
|
|
if (aNumberMatch
|
|
&& bNumberMatch
|
|
&& aName.replace(PATTERN_NUMBER, "") === bName.replace(PATTERN_NUMBER, "")
|
|
) {
|
|
const aNumber = parseInt(aNumberMatch[1], 10);
|
|
const bNumber = parseInt(bNumberMatch[1], 10);
|
|
|
|
return aNumber - bNumber;
|
|
}
|
|
|
|
return compareString(aName, bName);
|
|
};
|
|
|
|
const BasicSection = (props, context) => {
|
|
const { act } = useBackend(context);
|
|
const { searchText, source, title } = props;
|
|
const things = source.filter(searchFor(searchText));
|
|
things.sort(compareNumberedText);
|
|
return source.length > 0 && (
|
|
<Section title={`${title} - (${source.length})`}>
|
|
{things.map(thing => (
|
|
<Button
|
|
key={thing.name}
|
|
content={thing.name}
|
|
onClick={() => act("orbit", {
|
|
ref: thing.ref,
|
|
})} />
|
|
))}
|
|
</Section>
|
|
);
|
|
};
|
|
|
|
const OrbitedButton = (props, context) => {
|
|
const { act } = useBackend(context);
|
|
const { color, thing } = props;
|
|
|
|
return (
|
|
<Button
|
|
color={color}
|
|
onClick={() => act("orbit", {
|
|
ref: thing.ref,
|
|
})}>
|
|
{thing.assignment && (
|
|
<Box inline ml={1}>
|
|
<Box
|
|
className={classes([
|
|
'jobs8x8',
|
|
thing.assignment,
|
|
])}
|
|
style={{
|
|
transform: 'scale(1.5) translate(-50%, 0)',
|
|
}} />
|
|
</Box>
|
|
)}
|
|
{thing.name}
|
|
{thing.orbiters && (
|
|
<Box inline ml={1}>
|
|
{"("}{thing.orbiters}{" "}
|
|
<Box
|
|
as="img"
|
|
src={resolveAsset('ghost.png')}
|
|
opacity={0.7} />
|
|
{")"}
|
|
</Box>
|
|
)}
|
|
</Button>
|
|
);
|
|
};
|
|
|
|
export const Orbit = (props, context) => {
|
|
const { act, data } = useBackend(context);
|
|
const {
|
|
alive,
|
|
antagonists,
|
|
auto_observe,
|
|
dead,
|
|
ghosts,
|
|
misc,
|
|
npcs,
|
|
} = data;
|
|
|
|
const [searchText, setSearchText] = useLocalState(context, "searchText", "");
|
|
|
|
const collatedAntagonists = {};
|
|
for (const antagonist of antagonists) {
|
|
if (collatedAntagonists[antagonist.antag] === undefined) {
|
|
collatedAntagonists[antagonist.antag] = [];
|
|
}
|
|
collatedAntagonists[antagonist.antag].push(antagonist);
|
|
}
|
|
|
|
const sortedAntagonists = Object.entries(collatedAntagonists);
|
|
sortedAntagonists.sort((a, b) => {
|
|
return compareString(a[0], b[0]);
|
|
});
|
|
|
|
const orbitMostRelevant = searchText => {
|
|
for (const source of [
|
|
sortedAntagonists.map(([_, antags]) => antags),
|
|
alive, ghosts, dead, npcs, misc,
|
|
]) {
|
|
const member = source
|
|
.filter(searchFor(searchText))
|
|
.sort(compareNumberedText)[0];
|
|
if (member !== undefined) {
|
|
act("orbit", { ref: member.ref });
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Window
|
|
title="Orbit"
|
|
width={350}
|
|
height={700}>
|
|
<Window.Content scrollable>
|
|
<Section>
|
|
<Flex>
|
|
<Flex.Item>
|
|
<Icon
|
|
name="search"
|
|
mr={1} />
|
|
</Flex.Item>
|
|
<Flex.Item grow={1}>
|
|
<Input
|
|
placeholder="Search..."
|
|
autoFocus
|
|
fluid
|
|
value={searchText}
|
|
onInput={(_, value) => setSearchText(value)}
|
|
onEnter={(_, value) => orbitMostRelevant(value)} />
|
|
</Flex.Item>
|
|
<Flex.Item>
|
|
<Divider vertical />
|
|
</Flex.Item>
|
|
<Flex.Item>
|
|
<Button
|
|
inline
|
|
color="transparent"
|
|
tooltip={multiline`Toggle Auto-Observe. When active, you'll
|
|
see the UI / full inventory of whoever you're orbiting. Neat!`}
|
|
tooltipPosition="bottom-start"
|
|
selected={auto_observe}
|
|
icon={auto_observe ? "toggle-on" : "toggle-off"}
|
|
onClick={() => act("toggle_observe")} />
|
|
<Button
|
|
inline
|
|
color="transparent"
|
|
tooltip="Refresh"
|
|
tooltipPosition="bottom-start"
|
|
icon="sync-alt"
|
|
onClick={() => act("refresh")} />
|
|
</Flex.Item>
|
|
</Flex>
|
|
</Section>
|
|
{antagonists.length > 0 && (
|
|
<Section title="Ghost-Visible Antagonists">
|
|
{sortedAntagonists.map(([name, antags]) => (
|
|
<Section key={name} title={name} level={2}>
|
|
{antags
|
|
.filter(searchFor(searchText))
|
|
.sort(compareNumberedText)
|
|
.map(antag => (
|
|
<OrbitedButton
|
|
key={antag.name}
|
|
color="bad"
|
|
thing={antag}
|
|
/>
|
|
))}
|
|
</Section>
|
|
))}
|
|
</Section>
|
|
)}
|
|
|
|
<Section title={`Alive - (${alive.length})`}>
|
|
{alive
|
|
.filter(searchFor(searchText))
|
|
.sort(compareNumberedText)
|
|
.map(thing => (
|
|
<OrbitedButton
|
|
key={thing.name}
|
|
color="good"
|
|
thing={thing} />
|
|
))}
|
|
</Section>
|
|
|
|
<Section title={`Ghosts - (${ghosts.length})`}>
|
|
{ghosts
|
|
.filter(searchFor(searchText))
|
|
.sort(compareNumberedText)
|
|
.map(thing => (
|
|
<OrbitedButton
|
|
key={thing.name}
|
|
color="grey"
|
|
thing={thing} />
|
|
))}
|
|
</Section>
|
|
|
|
<BasicSection
|
|
title="Dead"
|
|
source={dead}
|
|
searchText={searchText}
|
|
/>
|
|
|
|
<BasicSection
|
|
title="NPCs"
|
|
source={npcs}
|
|
searchText={searchText}
|
|
/>
|
|
|
|
<BasicSection
|
|
title="Misc"
|
|
source={misc}
|
|
searchText={searchText}
|
|
/>
|
|
</Window.Content>
|
|
</Window>
|
|
);
|
|
};
|