Enable strictNullChecks on tgui (#60961)

* Enable strictNullChecks

* ArgumentType -> ArgumentsOf

* Fix map signature
This commit is contained in:
Mothblocks
2021-08-22 22:58:14 -07:00
committed by GitHub
parent 86a7ef17f6
commit b5bab3738c
12 changed files with 347 additions and 304 deletions

View File

@@ -668,7 +668,7 @@ rules:
# react/sort-prop-types: error
## Enforce the state initialization style to be either in a constructor or
## with a class property
react/state-in-constructor: error
# react/state-in-constructor: error
## Enforces where React component static properties should be positioned.
# react/static-property-placement: error
## Enforce style prop value being an object

View File

@@ -4,64 +4,6 @@
* @license MIT
*/
/**
* Converts a given collection to an array.
*
* - Arrays are returned unmodified;
* - If object was provided, keys will be discarded;
* - Everything else will result in an empty array.
*
* @returns {any[]}
*/
export const toArray = collection => {
if (Array.isArray(collection)) {
return collection;
}
if (typeof collection === 'object') {
const hasOwnProperty = Object.prototype.hasOwnProperty;
const result = [];
for (let i in collection) {
if (hasOwnProperty.call(collection, i)) {
result.push(collection[i]);
}
}
return result;
}
return [];
};
/**
* Converts a given object to an array, and appends a key to every
* object inside of that array.
*
* Example input (object):
* ```
* {
* 'Foo': { info: 'Hello world!' },
* 'Bar': { info: 'Hello world!' },
* }
* ```
*
* Example output (array):
* ```
* [
* { key: 'Foo', info: 'Hello world!' },
* { key: 'Bar', info: 'Hello world!' },
* ]
* ```
*
* @template T
* @param {{ [key: string]: T }} obj Object, or in DM terms, an assoc array
* @param {string} keyProp Property, to which key will be assigned
* @returns {T[]} Array of keyed objects
*/
export const toKeyedArray = (obj, keyProp = 'key') => {
return map((item, key) => ({
[keyProp]: key,
...item,
}))(obj);
};
/**
* Iterates over elements of collection, returning an array of all elements
* iteratee returns truthy for. The predicate is invoked with three
@@ -72,21 +14,40 @@ export const toKeyedArray = (obj, keyProp = 'key') => {
*
* @returns {any[]}
*/
export const filter = iterateeFn => collection => {
if (collection === null || collection === undefined) {
return collection;
}
if (Array.isArray(collection)) {
const result = [];
for (let i = 0; i < collection.length; i++) {
const item = collection[i];
if (iterateeFn(item, i, collection)) {
result.push(item);
export const filter = <T>(iterateeFn: (
input: T,
index: number,
collection: T[],
) => boolean) =>
(collection: T[]): T[] => {
if (collection === null || collection === undefined) {
return collection;
}
}
return result;
}
throw new Error(`filter() can't iterate on type ${typeof collection}`);
if (Array.isArray(collection)) {
const result: T[] = [];
for (let i = 0; i < collection.length; i++) {
const item = collection[i];
if (iterateeFn(item, i, collection)) {
result.push(item);
}
}
return result;
}
throw new Error(`filter() can't iterate on type ${typeof collection}`);
};
type MapFunction = {
<T, U>(iterateeFn: (
value: T,
index: number,
collection: T[],
) => U): (collection: T[]) => U[];
<T, U, K extends string | number>(iterateeFn: (
value: T,
index: K,
collection: Record<K, T>,
) => U): (collection: Record<K, T>) => U[];
};
/**
@@ -96,32 +57,25 @@ export const filter = iterateeFn => collection => {
*
* If collection is 'null' or 'undefined', it will be returned "as is"
* without emitting any errors (which can be useful in some cases).
*
* @returns {any[]}
*/
export const map = iterateeFn => collection => {
if (collection === null || collection === undefined) {
return collection;
}
if (Array.isArray(collection)) {
const result = [];
for (let i = 0; i < collection.length; i++) {
result.push(iterateeFn(collection[i], i, collection));
export const map: MapFunction = <T, U>(iterateeFn) =>
(collection: T[]): U[] => {
if (collection === null || collection === undefined) {
return collection;
}
return result;
}
if (typeof collection === 'object') {
const hasOwnProperty = Object.prototype.hasOwnProperty;
const result = [];
for (let i in collection) {
if (hasOwnProperty.call(collection, i)) {
result.push(iterateeFn(collection[i], i, collection));
}
if (Array.isArray(collection)) {
return collection.map(iterateeFn);
}
return result;
}
throw new Error(`map() can't iterate on type ${typeof collection}`);
};
if (typeof collection === 'object') {
return Object.entries(collection).map(([key, value]) => {
return iterateeFn(value, key, collection);
});
}
throw new Error(`map() can't iterate on type ${typeof collection}`);
};
const COMPARATOR = (objA, objB) => {
const criteriaA = objA.criteria;
@@ -148,28 +102,35 @@ const COMPARATOR = (objA, objB) => {
*
* @returns {any[]}
*/
export const sortBy = (...iterateeFns) => array => {
if (!Array.isArray(array)) {
return array;
}
let length = array.length;
// Iterate over the array to collect criteria to sort it by
let mappedArray = [];
for (let i = 0; i < length; i++) {
const value = array[i];
mappedArray.push({
criteria: iterateeFns.map(fn => fn(value)),
value,
});
}
// Sort criteria using the base comparator
mappedArray.sort(COMPARATOR);
// Unwrap values
while (length--) {
mappedArray[length] = mappedArray[length].value;
}
return mappedArray;
};
export const sortBy = <T>(
...iterateeFns: ((input: T) => unknown)[]
) => (array: T[]): T[] => {
if (!Array.isArray(array)) {
return array;
}
let length = array.length;
// Iterate over the array to collect criteria to sort it by
let mappedArray: {
criteria: unknown[],
value: T,
}[] = [];
for (let i = 0; i < length; i++) {
const value = array[i];
mappedArray.push({
criteria: iterateeFns.map(fn => fn(value)),
value,
});
}
// Sort criteria using the base comparator
mappedArray.sort(COMPARATOR);
// Unwrap values
const values: T[] = [];
while (length--) {
values[length] = mappedArray[length].value;
}
return values;
};
export const sort = sortBy();
@@ -212,40 +173,38 @@ export const reduce = (reducerFn, initialValue) => array => {
* is determined by the order they occur in the array. The iteratee is
* invoked with one argument: value.
*/
/* eslint-disable indent */
export const uniqBy = <T extends unknown>(
iterateeFn?: (value: T) => unknown
) => (array: T[]) => {
const { length } = array;
const result = [];
const seen = iterateeFn ? [] : result;
let index = -1;
outer:
while (++index < length) {
let value: T | 0 = array[index];
const computed = iterateeFn ? iterateeFn(value) : value;
value = value !== 0 ? value : 0;
if (computed === computed) {
let seenIndex = seen.length;
while (seenIndex--) {
if (seen[seenIndex] === computed) {
continue outer;
) => (array: T[]): T[] => {
const { length } = array;
const result: T[] = [];
const seen: unknown[] = iterateeFn ? [] : result;
let index = -1;
outer:
while (++index < length) {
let value: T | 0 = array[index];
const computed = iterateeFn ? iterateeFn(value) : value;
if (computed === computed) {
let seenIndex = seen.length;
while (seenIndex--) {
if (seen[seenIndex] === computed) {
continue outer;
}
}
if (iterateeFn) {
seen.push(computed);
}
result.push(value);
}
if (iterateeFn) {
seen.push(computed);
else if (!seen.includes(computed)) {
if (seen !== result) {
seen.push(computed);
}
result.push(value);
}
result.push(value);
}
else if (!seen.includes(computed)) {
if (seen !== result) {
seen.push(computed);
}
result.push(value);
}
}
return result;
};
return result;
};
/* eslint-enable indent */
export const uniq = uniqBy();
@@ -261,17 +220,19 @@ type Zip<T extends unknown[][]> = {
*/
export const zip = <T extends unknown[][]>(...arrays: T): Zip<T> => {
if (arrays.length === 0) {
return;
return [];
}
const numArrays = arrays.length;
const numValues = arrays[0].length;
const result = [];
const result: Zip<T> = [];
for (let valueIndex = 0; valueIndex < numValues; valueIndex++) {
const entry = [];
const entry: unknown[] = [];
for (let arrayIndex = 0; arrayIndex < numArrays; arrayIndex++) {
entry.push(arrays[arrayIndex][valueIndex]);
}
result.push(entry);
// I tried everything to remove this any, and have no idea how to do it.
result.push(entry as any);
}
return result;
};
@@ -280,9 +241,8 @@ export const zip = <T extends unknown[][]>(...arrays: T): Zip<T> => {
* This method is like "zip" except that it accepts iteratee to
* specify how grouped values should be combined. The iteratee is
* invoked with the elements of each group.
*
* @returns {any[]}
*/
export const zipWith = iterateeFn => (...arrays) => {
return map(values => iterateeFn(...values))(zip(...arrays));
};
export const zipWith = <T, U>(iterateeFn: (...values: T[]) => U) =>
(...arrays: T[][]): U[] => {
return map((values: T[]) => iterateeFn(...values))(zip(...arrays));
};

View File

@@ -0,0 +1,5 @@
/**
* Returns the arguments of a function F as an array.
*/
export type ArgumentsOf<F extends Function>
= F extends (...args: infer A) => unknown ? A : never;

View File

@@ -93,9 +93,7 @@ export const halfUnit = (value: unknown): string | undefined => {
const isColorCode = (str: unknown) => !isColorClass(str);
const isColorClass = (str: unknown): boolean => {
if (typeof str === 'string') {
return CSS_COLORS.includes(str);
}
return typeof str === "string" && CSS_COLORS.includes(str);
};
const mapRawPropTo = attrName => (style, value) => {

View File

@@ -78,7 +78,7 @@ export const computeFlexItemProps = (props: FlexItemProps) => {
className: classes([
'Flex__item',
Byond.IS_LTE_IE10 && 'Flex__item--iefix',
Byond.IS_LTE_IE10 && grow > 0 && 'Flex__item--iefix--grow',
Byond.IS_LTE_IE10 && (grow && grow > 0) && 'Flex__item--iefix--grow',
className,
]),
style: {

View File

@@ -1,9 +1,10 @@
import { createPopper, OptionsGeneric } from "@popperjs/core";
import { ArgumentsOf } from "common/types";
import { Component, findDOMfromVNode, InfernoNode, render } from "inferno";
type PopperProps = {
popperContent: InfernoNode;
options?: Partial<OptionsGeneric<unknown>>;
options?: ArgumentsOf<typeof createPopper>[2];
additionalStyles?: CSSProperties,
};
@@ -35,17 +36,22 @@ export class Popper extends Component<PopperProps> {
this.renderPopperContent(() => {
document.body.appendChild(this.renderedContent);
// HACK: We don't want to create a wrapper, as it could break the layout
// of consumers, so we do the inferno equivalent of `findDOMNode(this)`.
// This is usually bad as refs are usually better, but refs did
// not work in this case, as they weren't propagating correctly.
// A previous attempt was made as a render prop that passed an ID,
// but this made consuming use too unwieldly.
// This code is copied from `findDOMNode` in inferno-extras.
// Because this component is written in TypeScript, we will know
// immediately if this internal variable is removed.
const domNode = findDOMfromVNode(this.$LI, true);
if (!domNode) {
return;
}
this.popperInstance = createPopper(
// HACK: We don't want to create a wrapper, as it could break the layout
// of consumers, so we do the inferno equivalent of `findDOMNode(this)`.
// This is usually bad as refs are usually better, but refs did
// not work in this case, as they weren't propagating correctly.
// A previous attempt was made as a render prop that passed an ID,
// but this made consuming use too unwieldly.
// This code is copied from `findDOMNode` in inferno-extras.
// Because this component is written in TypeScript, we will know
// immediately if this internal variable is removed.
findDOMfromVNode(this.$LI, true),
domNode,
this.renderedContent,
options,
);
@@ -59,7 +65,6 @@ export class Popper extends Component<PopperProps> {
componentWillUnmount() {
this.popperInstance?.destroy();
this.renderedContent.remove();
this.renderedContent = null;
}
renderPopperContent(callback: () => void) {

View File

@@ -16,13 +16,9 @@ type TooltipState = {
};
export class Tooltip extends Component<TooltipProps, TooltipState> {
constructor() {
super();
this.state = {
hovered: false,
};
}
state = {
hovered: false,
};
componentDidMount() {
// HACK: We don't want to create a wrapper, as it could break the layout
@@ -35,6 +31,10 @@ export class Tooltip extends Component<TooltipProps, TooltipState> {
// immediately if this internal variable is removed.
const domNode = findDOMfromVNode(this.$LI, true);
if (!domNode) {
return;
}
domNode.addEventListener("mouseenter", () => {
this.setState({
hovered: true,

View File

@@ -28,6 +28,11 @@ const AdventureEntry = (props, context) => {
const { data, act } = useBackend<AdventureBrowserData>(context);
const { entry_ref, close }: { entry_ref: string, close: () => void } = props;
const entry = data.adventures.find(x => x.ref === entry_ref);
if (!entry) {
return null;
}
return (
<Section>
<LabeledList>
@@ -62,7 +67,7 @@ const AdventureList = (props, context) => {
const [
openAdventure,
setOpenAdventure,
] = useLocalState(context, 'openAdventure', null);
] = useLocalState<string | null>(context, 'openAdventure', null);
return (
<>
@@ -78,13 +83,13 @@ const AdventureList = (props, context) => {
<Table.Cell color="label">Title</Table.Cell>
<Table.Cell color="label">Edit</Table.Cell>
</Table.Row>
{data.adventures.map(p => (
{data.adventures.map(adventure => (
<Table.Row
key={p.ref}
key={adventure.ref}
className="candystripe">
<Table.Cell>{p.id}</Table.Cell>
<Table.Cell>{p.name}</Table.Cell>
<Table.Cell><Button icon="edit" onClick={() => setOpenAdventure(p.ref)} /></Table.Cell>
<Table.Cell>{adventure.id}</Table.Cell>
<Table.Cell>{adventure.name}</Table.Cell>
<Table.Cell><Button icon="edit" onClick={() => setOpenAdventure(adventure.ref)} /></Table.Cell>
</Table.Row>
))}
<Table.Row>
@@ -104,7 +109,14 @@ const DebugPlayer = (props, context) => {
buttons={<Button onClick={() => act("end_play")}>End Playtest</Button>}>
{data.delay_time > 0
? <Box>DELAY {formatTime(data.delay_time)} / {data.delay_message}</Box>
: <AdventureScreen hide_status />}
: (
<AdventureScreen
adventure_data={data.adventure_data}
drone_integrity={100}
drone_max_integrity={100}
hide_status
/>
)}
</Section>);
};

View File

@@ -1,4 +1,3 @@
import { toArray } from 'common/collections';
import { useBackend, useSharedState } from '../backend';
import { AnimatedNumber, Box, Button, Flex, LabeledList, Section, Table, Tabs } from '../components';
import { formatMoney } from '../format';
@@ -135,7 +134,7 @@ export const CargoCatalog = (props, context) => {
self_paid,
app_cost,
} = data;
const supplies = toArray(data.supplies);
const supplies = Object.values(data.supplies);
const [
activeSupplyName,
setActiveSupplyName,

View File

@@ -75,35 +75,72 @@ type DroneBasicData = {
}
export type AdventureDataProvider = {
adventure_data?: AdventureData;
adventure_data: AdventureData,
}
type ExodroneConsoleData = AdventureDataProvider & {
signal_lost: boolean,
drone: boolean,
all_drones?: Array<DroneBasicData>
drone_status?: DroneStatusEnum,
drone_name?: string,
drone_integrity?: number,
drone_max_integrity?: number,
drone_travel_coefficent?: number,
drone_log?: Array<string>,
configurable?: boolean,
cargo?: Array<CargoData>,
can_travel?: boolean,
type DroneAdventure = AdventureDataProvider & {
drone_status: DroneStatusEnum.Adventure,
};
type DroneData = {
drone_name: string,
drone_integrity: number,
drone_max_integrity: number,
drone_travel_coefficent: number,
drone_log: Array<string>,
configurable: boolean,
cargo: Array<CargoData>,
can_travel: boolean,
travel_error: string,
sites?: Array<SiteData>,
site?: SiteData,
travel_time?: number,
travel_time_left?: number,
wait_time_left?: number,
wait_message?: string,
};
type DroneBusy = {
drone_status: DroneStatusEnum.Busy,
wait_time_left: number,
wait_message: string,
};
type DroneExploration = {
drone_status: DroneStatusEnum.Exploration,
sites: Array<SiteData>,
site: SiteData,
event?: FullEventData,
adventure_data?: AdventureData,
};
type DroneIdle = {
drone_status: DroneStatusEnum.Idle,
sites: Array<SiteData>,
site: null,
};
type DroneTravel = {
drone_status: DroneStatusEnum.Travel,
travel_time: number,
travel_time_left: number,
};
type ActiveDrone = DroneAdventure
| DroneBusy
| DroneExploration
| DroneIdle
| DroneTravel;
type ExodroneConsoleData = {
signal_lost: boolean;
// ui_static_data
all_tools: Record<string, ToolData>,
all_bands: Record<string, string>
}
all_tools: Record<string, ToolData>;
all_bands: Record<string, string>;
} & (
| (({
drone: true;
} & DroneData) &
ActiveDrone)
| {
all_drones: Array<DroneBasicData>;
drone: undefined;
}
);
type ToolData = {
description: string,
@@ -170,11 +207,11 @@ const SignalLostModal = (props, context) => {
);
};
const DroneSelectionSection = (props, context) => {
const { act, data } = useBackend<ExodroneConsoleData>(context);
const {
all_drones,
} = data;
const DroneSelectionSection = (props: {
all_drones: Array<DroneBasicData>,
}, context) => {
const { act } = useBackend<ExodroneConsoleData>(context);
const { all_drones } = props;
return (
<Section scrollable fill title="Exploration Drone Listing">
@@ -266,12 +303,13 @@ const ToolSelectionModal = (props, context) => {
);
};
const EquipmentBox = (props, context) => {
const EquipmentBox = (props: {
cargo: CargoData,
drone: DroneData,
}, context) => {
const { act, data } = useBackend<ExodroneConsoleData>(context);
const {
configurable,
all_tools = {},
} = data;
const { all_tools = {} } = data;
const { configurable } = props.drone;
const cargo = props.cargo;
const boxContents = cargo => {
switch (cargo.type) {
@@ -349,12 +387,14 @@ const EquipmentBox = (props, context) => {
);
};
const EquipmentGrid = (props, context) => {
const { act, data } = useBackend<ExodroneConsoleData>(context);
const EquipmentGrid = (props: {
drone: ActiveDrone & DroneData,
}, context) => {
const { act } = useBackend<ExodroneConsoleData>(context);
const {
cargo,
configurable,
} = data;
} = props.drone;
const [
choosingTools,
setChoosingTools,
@@ -399,6 +439,7 @@ const EquipmentGrid = (props, context) => {
<Stack wrap="wrap" width={10}>
{cargo.map(cargo_element => (
<EquipmentBox
drone={props.drone}
key={cargo_element.name}
cargo={cargo_element} />
))}
@@ -410,12 +451,14 @@ const EquipmentGrid = (props, context) => {
);
};
const DroneStatus = (props, context) => {
const { act, data } = useBackend<ExodroneConsoleData>(context);
const DroneStatus = (props: {
drone_integrity: number,
drone_max_integrity: number,
}, context) => {
const {
drone_integrity,
drone_max_integrity,
} = data;
} = props;
return (
<Stack ml={-40}>
@@ -459,18 +502,22 @@ const NoSiteDimmer = () => {
);
};
const TravelTargetSelectionScreen = (props, context) => {
const TravelTargetSelectionScreen = (props: {
drone: (DroneExploration | DroneIdle | DroneTravel) & DroneData,
showCancelButton?: boolean,
}, context) => {
// List of sites and eta travel times to each
const { act, data } = useBackend<ExodroneConsoleData>(context);
const { drone } = props;
const { all_bands } = data;
const {
sites,
site,
can_travel,
travel_error,
drone_travel_coefficent,
all_bands,
drone_status,
} = data;
} = drone;
const site = ("site" in drone) ? drone.site : null;
const sites = ("sites" in drone) ? drone.sites : null;
const travel_cost = target_site => {
if (site) {
@@ -500,12 +547,12 @@ const TravelTargetSelectionScreen = (props, context) => {
&& dest.band_info[s] !== 0;
return Object.keys(all_bands).filter(band_check);
};
const valid_destinations = !!sites && sites.filter(destination => (
const valid_destinations = sites && sites.filter(destination => (
!site || destination.ref !== site.ref
));
return (
drone_status === "travel" && (
<TravelDimmer />
drone.drone_status === DroneStatusEnum.Travel && (
<TravelDimmer drone={drone} />
) || (
<Section
title="Travel Destinations"
@@ -521,7 +568,10 @@ const TravelTargetSelectionScreen = (props, context) => {
onClick={() => setTravelDimmerShown(false)} />
)}
<Box mt={props.showCancelButton && -3.5}>
<DroneStatus />
<DroneStatus
drone_integrity={drone.drone_integrity}
drone_max_integrity={drone.drone_max_integrity}
/>
</Box>
</>
}>
@@ -544,7 +594,7 @@ const TravelTargetSelectionScreen = (props, context) => {
}
/>
)}
{valid_destinations.map(destination => (
{valid_destinations?.map(destination => (
<Section
key={destination.ref}
title={destination.name}
@@ -581,12 +631,10 @@ const TravelTargetSelectionScreen = (props, context) => {
);
};
const TravelDimmer = (props, context) => {
const { act, data } = useBackend<ExodroneConsoleData>(context);
const {
travel_time,
travel_time_left,
} = data;
const TravelDimmer = (props: {
drone: DroneTravel,
}, context) => {
const { travel_time_left } = props.drone;
return (
<Section fill>
<Dimmer>
@@ -607,12 +655,14 @@ const TravelDimmer = (props, context) => {
);
};
const TimeoutScreen = (props, context) => {
const { act, data } = useBackend<ExodroneConsoleData>(context);
const TimeoutScreen = (props: {
drone: DroneBusy,
}) => {
const {
wait_time_left,
wait_message,
} = data;
} = props.drone;
return (
<Section fill>
<Dimmer>
@@ -633,13 +683,12 @@ const TimeoutScreen = (props, context) => {
);
};
const ExplorationScreen = (props, context) => {
const { act, data } = useBackend<ExodroneConsoleData>(context);
const {
site,
event,
sites,
} = data;
const ExplorationScreen = (props: {
drone: DroneExploration & DroneData,
}, context) => {
const { act } = useBackend(context);
const { drone } = props;
const { site } = drone;
const [
TravelDimmerShown,
@@ -647,14 +696,20 @@ const ExplorationScreen = (props, context) => {
] = useLocalState(context, 'TravelDimmerShown', false);
if (TravelDimmerShown) {
return (<TravelTargetSelectionScreen showCancelButton />);
return (<TravelTargetSelectionScreen
drone={drone}
showCancelButton
/>);
}
return (
<Section
fill
title="Exploration"
buttons={
<DroneStatus />
<DroneStatus
drone_integrity={drone.drone_integrity}
drone_max_integrity={drone.drone_max_integrity}
/>
}>
<Stack vertical fill>
<Stack.Item grow>
@@ -688,22 +743,23 @@ const ExplorationScreen = (props, context) => {
);
};
const EventScreen = (props, context) => {
const { act, data } = useBackend<ExodroneConsoleData>(context);
const {
drone_status,
event,
} = data;
const EventScreen = (props: {
drone: DroneData,
event: FullEventData,
}, context) => {
const { act } = useBackend(context);
const { drone, event } = props;
return (
<Section
fill
title="Exploration"
buttons={
<DroneStatus />
<DroneStatus
drone_integrity={drone.drone_integrity}
drone_max_integrity={drone.drone_max_integrity}
/>
}>
{(drone_status && drone_status === "busy") && (
<TimeoutScreen />
)}
<Stack vertical fill textAlign="center">
<Stack.Item>
<Stack fill>
@@ -747,23 +803,28 @@ const EventScreen = (props, context) => {
);
};
type AdventureScreenProps = {
hide_status?: boolean
}
export const AdventureScreen = (props: AdventureScreenProps, context) => {
const { act, data } = useBackend<AdventureDataProvider>(context);
export const AdventureScreen = (props: {
adventure_data: AdventureData,
drone_integrity: number,
drone_max_integrity: number,
hide_status?: boolean,
}, context) => {
const { act } = useBackend(context);
const {
adventure_data,
} = data;
drone_integrity,
drone_max_integrity,
} = props;
const rawData = adventure_data.raw_image;
const imgSource = rawData ? rawData : resolveAsset(adventure_data.image);
return (
<Section
fill
title="Exploration"
buttons={!props.hide_status && <DroneStatus />}>
buttons={!props.hide_status && <DroneStatus
drone_integrity={drone_integrity}
drone_max_integrity={drone_max_integrity}
/>}>
<Stack>
<Stack.Item>
<BlockQuote preserveWhitespace>
@@ -799,42 +860,42 @@ export const AdventureScreen = (props: AdventureScreenProps, context) => {
);
};
const DroneScreen = (props, context) => {
const { act, data } = useBackend<ExodroneConsoleData>(context);
const {
drone_status,
event,
} = data;
switch (drone_status) {
case "busy":
return <TimeoutScreen />;
case "idle":
case "travel":
return <TravelTargetSelectionScreen />;
case "adventure":
return <AdventureScreen />;
case "exploration":
if (event) {
return <EventScreen />;
const DroneScreen = (props: {
drone: ActiveDrone & DroneData,
}) => {
const { drone } = props;
switch (drone.drone_status) {
case DroneStatusEnum.Busy:
return <TimeoutScreen drone={drone} />;
case DroneStatusEnum.Idle:
case DroneStatusEnum.Travel:
return <TravelTargetSelectionScreen drone={drone} />;
case DroneStatusEnum.Adventure:
return (<AdventureScreen
adventure_data={drone.adventure_data}
drone_integrity={drone.drone_integrity}
drone_max_integrity={drone.drone_max_integrity}
/>);
case DroneStatusEnum.Exploration:
if (drone.event) {
return <EventScreen drone={drone} event={drone.event} />;
}
else {
return <ExplorationScreen />;
return <ExplorationScreen drone={drone} />;
}
}
};
const ExodroneConsoleContent = (props, context) => {
const { act, data } = useBackend<ExodroneConsoleData>(context);
const {
drone,
drone_name,
drone_log,
} = data;
const { data } = useBackend<ExodroneConsoleData>(context);
if (!drone) {
return <DroneSelectionSection />;
if (!data.drone) {
return <DroneSelectionSection all_drones={data.all_drones} />;
}
const { drone_log } = data;
return (
<Stack fill vertical>
<Stack.Item grow>
@@ -842,10 +903,10 @@ const ExodroneConsoleContent = (props, context) => {
<Stack.Item grow>
<Stack fill>
<Stack.Item>
<EquipmentGrid />
<EquipmentGrid drone={data} />
</Stack.Item>
<Stack.Item grow basis={0}>
<DroneScreen />
<DroneScreen drone={data} />
</Stack.Item>
</Stack>
</Stack.Item>

View File

@@ -290,7 +290,9 @@ export const StripMenu = (props, context) => {
if (item === null) {
tooltip = slot.displayName;
} else if ("name" in item) {
alternateAction = ALTERNATE_ACTIONS[item.alternate];
if (item.alternate) {
alternateAction = ALTERNATE_ACTIONS[item.alternate];
}
content = (
<Box

View File

@@ -14,6 +14,7 @@
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": false,
"strictNullChecks": true,
"target": "ES3"
},
"include": [