Merge branch 'master' of https://github.com/Citadel-Station-13/Citadel-Station-13 into new-decal-painter

This commit is contained in:
SandPoot
2023-02-06 20:24:35 -03:00
140 changed files with 1040 additions and 418 deletions
+21
View File
@@ -306,3 +306,24 @@ export const zip = <T extends unknown[][]>(...arrays: T): Zip<T> => {
export const zipWith = iterateeFn => (...arrays) => {
return map(values => iterateeFn(...values))(zip(...arrays));
};
/**
* This method takes a collection of items and a number, returning a collection
* of collections, where the maximum amount of items in each is that second arg
*/
export const paginate = <T>(collection: T[], maxPerPage: number): T[][] => {
const pages: T[][] = [];
let page: T[] = [];
let itemsToAdd = maxPerPage;
for (const item of collection) {
page.push(item);
itemsToAdd--;
if (!itemsToAdd) {
itemsToAdd = maxPerPage;
pages.push(page);
page = [];
}
}
return pages;
};
@@ -0,0 +1,197 @@
import { paginate } from 'common/collections';
import { useBackend, useLocalState } from '../backend';
import { Stack, Button, Icon, Input, Section, Tabs } from '../components';
import { Window } from '../layouts';
const CATEGORY_PAGE_ITEMS = 4;
const EVENT_PAGE_ITEMS = 2;
const EVENT_PAGE_MAXCHARS = 48;
/**
* Same as paginate, but respecting event names with a character max length
* that will also create a new page if created
*/
const paginateEvents = (events: Event[], maxPerPage: number): Event[][] => {
const pages: Event[][] = [];
let page: Event[] = [];
// conditions that make a new page
let itemsToAdd = maxPerPage;
let maxChars = EVENT_PAGE_MAXCHARS;
for (const event of events) {
maxChars -= event.name.length;
if (maxChars <= 0) {
// would overflow the next line over
itemsToAdd = maxPerPage;
maxChars = EVENT_PAGE_MAXCHARS - event.name.length;
pages.push(page);
page = [];
}
page.push(event);
itemsToAdd--;
if (!itemsToAdd) {
// max amount of items we allow
itemsToAdd = maxPerPage;
maxChars = EVENT_PAGE_MAXCHARS;
pages.push(page);
page = [];
}
}
if (page.length) {
pages.push(page);
}
return pages;
};
type Event = {
name: string;
description: string;
type: string;
category: string;
};
type Category = {
name: string;
icon: string;
};
type ForceEventData = {
categories: Category[];
events: Event[];
};
export const ForceEvent = (props, context) => {
return (
<Window theme="admin" title="Force Event" width={450} height={450}>
<Window.Content>
<Stack vertical fill>
<Stack.Item>
<EventTabs />
</Stack.Item>
<Stack.Item grow>
<EventSection />
</Stack.Item>
</Stack>
</Window.Content>
</Window>
);
};
export const PanelOptions = (props, context) => {
const [searchQuery, setSearchQuery] = useLocalState(
context,
'searchQuery',
''
);
const [announce, setAnnounce] = useLocalState(context, 'announce', true);
return (
<Stack width="240px">
<Stack.Item>
<Icon name="search" />
</Stack.Item>
<Stack.Item grow>
<Input
autoFocus
fluid
onInput={(e) => setSearchQuery(e.target.value)}
placeholder="Search..."
value={searchQuery}
/>
</Stack.Item>
<Stack.Item>
<Button.Checkbox
fluid
checked={announce}
onClick={() => setAnnounce(!announce)}>
Announce
</Button.Checkbox>
</Stack.Item>
</Stack>
);
};
export const EventSection = (props, context) => {
const { data, act } = useBackend<ForceEventData>(context);
const { categories, events } = data;
const [category] = useLocalState(context, 'category', categories[0]);
const [searchQuery] = useLocalState(context, 'searchQuery', '');
const [announce] = useLocalState(context, 'announce', true);
const preparedEvents = paginateEvents(
events.filter((event) => {
// remove events not in the category you're looking at
if (!searchQuery && event.category !== category.name) {
return false;
}
// remove events not being searched for, if a search is active
if (searchQuery && !event.name.toLowerCase().includes(searchQuery)) {
return false;
}
return true;
}),
EVENT_PAGE_ITEMS
);
const sectionTitle = searchQuery ? 'Searching...' : category.name + ' Events';
return (
<Section scrollable fill title={sectionTitle} buttons={<PanelOptions />}>
<Stack vertical>
{preparedEvents.map((eventPage, i) => (
<Stack.Item key={i}>
<Stack>
{eventPage.map((event) => (
<Stack.Item grow key={event.type}>
<Button
tooltip={event.description}
fluid
onClick={() =>
act('forceevent', {
type: event.type,
announce: announce,
})}>
{event.name}
</Button>
</Stack.Item>
))}
</Stack>
</Stack.Item>
))}
</Stack>
</Section>
);
};
export const EventTabs = (props, context) => {
const { data } = useBackend<ForceEventData>(context);
const { categories } = data;
const [category, setCategory] = useLocalState(
context,
'category',
categories[0]
);
const layerCats = paginate(categories, CATEGORY_PAGE_ITEMS);
return (
<Section mb="-6px">
{layerCats.map((page, i) => (
<Tabs mb="-3px" fluid key={i}>
{page.map((cat) => (
<Tabs.Tab
selected={category === cat}
icon={cat.icon}
key={cat.icon}
onClick={() => setCategory(cat)}>
{cat.name}
</Tabs.Tab>
))}
</Tabs>
))}
</Section>
);
};
@@ -127,7 +127,7 @@ const PatientStateView = (props, context) => {
</Fragment>
)}
</LabeledList.Item>
{!!data.alternative_step && (
{procedure.alternative_step && (
<LabeledList.Item label="Alternative Step">
{procedure.alternative_step}
{procedure.alt_chems_needed && (
+3 -3
View File
@@ -28,7 +28,7 @@ export const NtosWindow = (props, context) => {
PC_stationtime,
PC_programheaders = [],
PC_showexitprogram,
PC_showpeneject,
TABLET_show_pen_eject,
} = data;
return (
<Window
@@ -79,7 +79,7 @@ export const NtosWindow = (props, context) => {
src={resolveAsset(PC_apclinkicon)} />
</Box>
)}
{!!PC_showpeneject && (
{!!TABLET_show_pen_eject && (
<Button
width="26px"
lineHeight="22px"
@@ -88,7 +88,7 @@ export const NtosWindow = (props, context) => {
icon="pen-alt"
tooltip="Eject pen"
tooltipPosition="bottom-end"
onClick={() => act('eject_pen')} />
onClick={() => act('TABLET_eject_pen')} />
)}
{!!PC_showexitprogram && (
<Button
+3 -3
View File
@@ -4042,12 +4042,12 @@ __metadata:
linkType: hard
"follow-redirects@npm:^1.10.0":
version: 1.13.2
resolution: "follow-redirects@npm:1.13.2"
version: 1.15.2
resolution: "follow-redirects@npm:1.15.2"
peerDependenciesMeta:
debug:
optional: true
checksum: c17f14c186e8a5cc9e6f179f87286306151017a919735d68e2d440fd353da9f9d15789e03ea057a1c2b49dda9d662b05bb4370c7350f70de43b18d3ead2b36a0
checksum: faa66059b66358ba65c234c2f2a37fcec029dc22775f35d9ad6abac56003268baf41e55f9ee645957b32c7d9f62baf1f0b906e68267276f54ec4b4c597c2b190
languageName: node
linkType: hard