mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-28 07:38:05 +01:00
Remove several functions from collections.js which have ES5 equivalents (#82417)
This commit is contained in:
+121
-118
@@ -12,33 +12,36 @@
|
||||
* If collection is 'null' or 'undefined', it will be returned "as is"
|
||||
* without emitting any errors (which can be useful in some cases).
|
||||
*/
|
||||
export const filter =
|
||||
<T>(iterateeFn: (input: T, index: number, collection: T[]) => boolean) =>
|
||||
(collection: T[]): T[] => {
|
||||
if (collection === null || collection === undefined) {
|
||||
return 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);
|
||||
}
|
||||
export const filter = <T>(
|
||||
collection: T[],
|
||||
iterateeFn: (input: T, index: number, collection: T[]) => boolean,
|
||||
): T[] => {
|
||||
if (collection === null || collection === undefined) {
|
||||
return 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}`);
|
||||
};
|
||||
return result;
|
||||
}
|
||||
throw new Error(`filter() can't iterate on type ${typeof collection}`);
|
||||
};
|
||||
|
||||
type MapFunction = {
|
||||
<T, U>(
|
||||
collection: T[],
|
||||
iterateeFn: (value: T, index: number, collection: T[]) => U,
|
||||
): (collection: T[]) => U[];
|
||||
): U[];
|
||||
|
||||
<T, U, K extends string | number>(
|
||||
collection: Record<K, T>,
|
||||
iterateeFn: (value: T, index: K, collection: Record<K, T>) => U,
|
||||
): (collection: Record<K, T>) => U[];
|
||||
): U[];
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -49,44 +52,30 @@ type MapFunction = {
|
||||
* If collection is 'null' or 'undefined', it will be returned "as is"
|
||||
* without emitting any errors (which can be useful in some cases).
|
||||
*/
|
||||
export const map: MapFunction =
|
||||
<T, U>(iterateeFn) =>
|
||||
(collection: T[]): U[] => {
|
||||
if (collection === null || collection === undefined) {
|
||||
return collection;
|
||||
}
|
||||
|
||||
if (Array.isArray(collection)) {
|
||||
return collection.map(iterateeFn);
|
||||
}
|
||||
|
||||
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}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a collection, will run each element through an iteratee function.
|
||||
* Will then filter out undefined values.
|
||||
*/
|
||||
export const filterMap = <T, U>(
|
||||
collection: T[],
|
||||
iterateeFn: (value: T) => U | undefined,
|
||||
): U[] => {
|
||||
const finalCollection: U[] = [];
|
||||
|
||||
for (const value of collection) {
|
||||
const output = iterateeFn(value);
|
||||
if (output !== undefined) {
|
||||
finalCollection.push(output);
|
||||
}
|
||||
export const map: MapFunction = (collection, iterateeFn) => {
|
||||
if (collection === null || collection === undefined) {
|
||||
return collection;
|
||||
}
|
||||
|
||||
return finalCollection;
|
||||
if (Array.isArray(collection)) {
|
||||
const result: unknown[] = [];
|
||||
for (let i = 0; i < collection.length; i++) {
|
||||
result.push(iterateeFn(collection[i], i, collection));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
if (typeof collection === 'object') {
|
||||
const result: unknown[] = [];
|
||||
for (let i in collection) {
|
||||
if (Object.prototype.hasOwnProperty.call(collection, i)) {
|
||||
result.push(iterateeFn(collection[i], i, collection));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
throw new Error(`map() can't iterate on type ${typeof collection}`);
|
||||
};
|
||||
|
||||
const COMPARATOR = (objA, objB) => {
|
||||
@@ -112,39 +101,38 @@ const COMPARATOR = (objA, objB) => {
|
||||
*
|
||||
* Iteratees are called with one argument (value).
|
||||
*/
|
||||
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);
|
||||
export const sortBy = <T>(
|
||||
array: T[],
|
||||
...iterateeFns: ((input: T) => unknown)[]
|
||||
): 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;
|
||||
};
|
||||
// Unwrap values
|
||||
const values: T[] = [];
|
||||
while (length--) {
|
||||
values[length] = mappedArray[length].value;
|
||||
}
|
||||
return values;
|
||||
};
|
||||
|
||||
export const sort = sortBy();
|
||||
|
||||
export const sortStrings = sortBy<string>();
|
||||
export const sort = <T>(array: T[]): T[] => sortBy(array);
|
||||
|
||||
/**
|
||||
* Returns a range of numbers from start to end, exclusively.
|
||||
@@ -153,12 +141,34 @@ export const sortStrings = sortBy<string>();
|
||||
export const range = (start: number, end: number): number[] =>
|
||||
new Array(end - start).fill(null).map((_, index) => index + start);
|
||||
|
||||
type ReduceFunction = {
|
||||
<T, U>(
|
||||
array: T[],
|
||||
reducerFn: (
|
||||
accumulator: U,
|
||||
currentValue: T,
|
||||
currentIndex: number,
|
||||
array: T[],
|
||||
) => U,
|
||||
initialValue: U,
|
||||
): U;
|
||||
<T>(
|
||||
array: T[],
|
||||
reducerFn: (
|
||||
accumulator: T,
|
||||
currentValue: T,
|
||||
currentIndex: number,
|
||||
array: T[],
|
||||
) => T,
|
||||
): T;
|
||||
};
|
||||
|
||||
/**
|
||||
* A fast implementation of reduce.
|
||||
*/
|
||||
export const reduce = (reducerFn, initialValue) => (array) => {
|
||||
export const reduce: ReduceFunction = (array, reducerFn, initialValue?) => {
|
||||
const length = array.length;
|
||||
let i;
|
||||
let i: number;
|
||||
let result;
|
||||
if (initialValue === undefined) {
|
||||
i = 1;
|
||||
@@ -184,15 +194,16 @@ export const reduce = (reducerFn, initialValue) => (array) => {
|
||||
* is determined by the order they occur in the array. The iteratee is
|
||||
* invoked with one argument: value.
|
||||
*/
|
||||
export const uniqBy =
|
||||
<T extends unknown>(iterateeFn?: (value: T) => unknown) =>
|
||||
(array: T[]): T[] => {
|
||||
const { length } = array;
|
||||
const result: T[] = [];
|
||||
const seen: unknown[] = iterateeFn ? [] : result;
|
||||
let index = -1;
|
||||
// prettier-ignore
|
||||
outer:
|
||||
export const uniqBy = <T extends unknown>(
|
||||
array: T[],
|
||||
iterateeFn?: (value: T) => unknown,
|
||||
): T[] => {
|
||||
const { length } = array;
|
||||
const result: T[] = [];
|
||||
const seen: unknown[] = iterateeFn ? [] : result;
|
||||
let index = -1;
|
||||
// prettier-ignore
|
||||
outer:
|
||||
while (++index < length) {
|
||||
let value: T | 0 = array[index];
|
||||
const computed = iterateeFn ? iterateeFn(value) : value;
|
||||
@@ -214,10 +225,10 @@ export const uniqBy =
|
||||
result.push(value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
return result;
|
||||
};
|
||||
|
||||
export const uniq = uniqBy();
|
||||
export const uniq = <T>(array: T[]): T[] => uniqBy(array);
|
||||
|
||||
type Zip<T extends unknown[][]> = {
|
||||
[I in keyof T]: T[I] extends (infer U)[] ? U : never;
|
||||
@@ -247,17 +258,6 @@ export const zip = <T extends unknown[][]>(...arrays: T): Zip<T> => {
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
export const zipWith =
|
||||
<T, U>(iterateeFn: (...values: T[]) => U) =>
|
||||
(...arrays: T[][]): U[] => {
|
||||
return map((values: T[]) => iterateeFn(...values))(zip(...arrays));
|
||||
};
|
||||
|
||||
const binarySearch = <T, U = unknown>(
|
||||
getKey: (value: T) => U,
|
||||
collection: readonly T[],
|
||||
@@ -293,13 +293,15 @@ const binarySearch = <T, U = unknown>(
|
||||
return compare > insertingKey ? middle : middle + 1;
|
||||
};
|
||||
|
||||
export const binaryInsertWith =
|
||||
<T, U = unknown>(getKey: (value: T) => U) =>
|
||||
(collection: readonly T[], value: T) => {
|
||||
const copy = [...collection];
|
||||
copy.splice(binarySearch(getKey, collection, value), 0, value);
|
||||
return copy;
|
||||
};
|
||||
export const binaryInsertWith = <T, U = unknown>(
|
||||
collection: readonly T[],
|
||||
value: T,
|
||||
getKey: (value: T) => U,
|
||||
): T[] => {
|
||||
const copy = [...collection];
|
||||
copy.splice(binarySearch(getKey, collection, value), 0, value);
|
||||
return copy;
|
||||
};
|
||||
|
||||
/**
|
||||
* This method takes a collection of items and a number, returning a collection
|
||||
@@ -325,7 +327,8 @@ export const paginate = <T>(collection: T[], maxPerPage: number): T[][] => {
|
||||
return pages;
|
||||
};
|
||||
|
||||
const isObject = (obj: unknown) => typeof obj === 'object' && obj !== null;
|
||||
const isObject = (obj: unknown): obj is object =>
|
||||
typeof obj === 'object' && obj !== null;
|
||||
|
||||
// Does a deep merge of two objects. DO NOT FEED CIRCULAR OBJECTS!!
|
||||
export const deepMerge = (...objects: any[]): any => {
|
||||
|
||||
@@ -23,27 +23,3 @@ export const flow = (...funcs) => (input, ...rest) => {
|
||||
}
|
||||
return output;
|
||||
};
|
||||
|
||||
/**
|
||||
* Composes single-argument functions from right to left.
|
||||
*
|
||||
* All functions might accept a context in form of additional arguments.
|
||||
* If the resulting function is called with more than 1 argument, rest of
|
||||
* the arguments are passed to all functions unchanged.
|
||||
*
|
||||
* @param {...Function} funcs The functions to compose
|
||||
* @returns {Function} A function obtained by composing the argument functions
|
||||
* from right to left. For example, compose(f, g, h) is identical to doing
|
||||
* (input, ...rest) => f(g(h(input, ...rest), ...rest), ...rest)
|
||||
*/
|
||||
export const compose = (...funcs) => {
|
||||
if (funcs.length === 0) {
|
||||
return (arg) => arg;
|
||||
}
|
||||
if (funcs.length === 1) {
|
||||
return funcs[0];
|
||||
}
|
||||
// prettier-ignore
|
||||
return funcs.reduce((a, b) => (value, ...rest) =>
|
||||
a(b(value, ...rest), ...rest));
|
||||
};
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
/**
|
||||
* N-dimensional vector manipulation functions.
|
||||
*
|
||||
* Vectors are plain number arrays, i.e. [x, y, z].
|
||||
*
|
||||
* @file
|
||||
* @copyright 2020 Aleksej Komarov
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { map, reduce, zipWith } from './collections';
|
||||
|
||||
const ADD = (a, b) => a + b;
|
||||
const SUB = (a, b) => a - b;
|
||||
const MUL = (a, b) => a * b;
|
||||
const DIV = (a, b) => a / b;
|
||||
|
||||
export const vecAdd = (...vecs) => {
|
||||
return reduce((a, b) => zipWith(ADD)(a, b))(vecs);
|
||||
};
|
||||
|
||||
export const vecSubtract = (...vecs) => {
|
||||
return reduce((a, b) => zipWith(SUB)(a, b))(vecs);
|
||||
};
|
||||
|
||||
export const vecMultiply = (...vecs) => {
|
||||
return reduce((a, b) => zipWith(MUL)(a, b))(vecs);
|
||||
};
|
||||
|
||||
export const vecDivide = (...vecs) => {
|
||||
return reduce((a, b) => zipWith(DIV)(a, b))(vecs);
|
||||
};
|
||||
|
||||
export const vecScale = (vec, n) => {
|
||||
return map((x) => x * n)(vec);
|
||||
};
|
||||
|
||||
export const vecInverse = (vec) => {
|
||||
return map((x) => -x)(vec);
|
||||
};
|
||||
|
||||
export const vecLength = (vec) => {
|
||||
return Math.sqrt(reduce(ADD)(zipWith(MUL)(vec, vec)));
|
||||
};
|
||||
|
||||
export const vecNormalize = (vec) => {
|
||||
return vecDivide(vec, vecLength(vec));
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* N-dimensional vector manipulation functions.
|
||||
*
|
||||
* Vectors are plain number arrays, i.e. [x, y, z].
|
||||
*
|
||||
* @file
|
||||
* @copyright 2020 Aleksej Komarov
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { map, reduce, zip } from './collections';
|
||||
|
||||
const ADD = (a: number, b: number): number => a + b;
|
||||
const SUB = (a: number, b: number): number => a - b;
|
||||
const MUL = (a: number, b: number): number => a * b;
|
||||
const DIV = (a: number, b: number): number => a / b;
|
||||
|
||||
export type Vector = number[];
|
||||
|
||||
export const vecAdd = (...vecs: Vector[]): Vector => {
|
||||
return map(zip(...vecs), (x) => reduce(x, ADD));
|
||||
};
|
||||
|
||||
export const vecSubtract = (...vecs: Vector[]): Vector => {
|
||||
return map(zip(...vecs), (x) => reduce(x, SUB));
|
||||
};
|
||||
|
||||
export const vecMultiply = (...vecs: Vector[]): Vector => {
|
||||
return map(zip(...vecs), (x) => reduce(x, MUL));
|
||||
};
|
||||
|
||||
export const vecDivide = (...vecs: Vector[]): Vector => {
|
||||
return map(zip(...vecs), (x) => reduce(x, DIV));
|
||||
};
|
||||
|
||||
export const vecScale = (vec: Vector, n: number): Vector => {
|
||||
return map(vec, (x) => x * n);
|
||||
};
|
||||
|
||||
export const vecInverse = (vec: Vector): Vector => {
|
||||
return map(vec, (x) => -x);
|
||||
};
|
||||
|
||||
export const vecLength = (vec: Vector): number => {
|
||||
return Math.sqrt(reduce(vecMultiply(vec, vec), ADD));
|
||||
};
|
||||
|
||||
export const vecNormalize = (vec: Vector): Vector => {
|
||||
const length = vecLength(vec);
|
||||
return map(vec, (c) => c / length);
|
||||
};
|
||||
@@ -9,7 +9,7 @@ import { map } from 'common/collections';
|
||||
export const selectChat = (state) => state.chat;
|
||||
|
||||
export const selectChatPages = (state) =>
|
||||
map((id: string) => state.chat.pageById[id])(state.chat.pages);
|
||||
map(state.chat.pages, (id: string) => state.chat.pageById[id]);
|
||||
|
||||
export const selectCurrentChatPage = (state) =>
|
||||
state.chat.pageById[state.chat.currentPageId];
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { map, zipWith } from 'common/collections';
|
||||
import { map, zip } from 'common/collections';
|
||||
import { Component, createRef, RefObject } from 'react';
|
||||
|
||||
import { Box, BoxProps } from './Box';
|
||||
@@ -37,8 +37,8 @@ const normalizeData = (
|
||||
return [];
|
||||
}
|
||||
|
||||
const min = zipWith(Math.min)(...data);
|
||||
const max = zipWith(Math.max)(...data);
|
||||
const min = map(zip(...data), (p) => Math.min(...p));
|
||||
const max = map(zip(...data), (p) => Math.max(...p));
|
||||
|
||||
if (rangeX !== undefined) {
|
||||
min[0] = rangeX[0];
|
||||
@@ -50,11 +50,12 @@ const normalizeData = (
|
||||
max[1] = rangeY[1];
|
||||
}
|
||||
|
||||
const normalized = map((point: Point) => {
|
||||
return zipWith((value: number, min: number, max: number, scale: number) => {
|
||||
return ((value - min) / (max - min)) * scale;
|
||||
})(point, min, max, scale);
|
||||
})(data);
|
||||
const normalized = map(data, (point) =>
|
||||
map(
|
||||
zip(point, min, max, scale),
|
||||
([value, min, max, scale]) => ((value - min) / (max - min)) * scale,
|
||||
),
|
||||
);
|
||||
|
||||
return normalized;
|
||||
};
|
||||
|
||||
@@ -209,7 +209,7 @@ export const dragStartHandler = (event) => {
|
||||
dragPointOffset = vecSubtract(
|
||||
[event.screenX, event.screenY],
|
||||
getWindowPosition(),
|
||||
);
|
||||
) as [number, number];
|
||||
// Focus click target
|
||||
(event.target as HTMLElement)?.focus();
|
||||
document.addEventListener('mousemove', dragMoveHandler);
|
||||
@@ -234,7 +234,10 @@ const dragMoveHandler = (event: MouseEvent) => {
|
||||
}
|
||||
event.preventDefault();
|
||||
setWindowPosition(
|
||||
vecSubtract([event.screenX, event.screenY], dragPointOffset),
|
||||
vecSubtract([event.screenX, event.screenY], dragPointOffset) as [
|
||||
number,
|
||||
number,
|
||||
],
|
||||
);
|
||||
};
|
||||
|
||||
@@ -247,7 +250,7 @@ export const resizeStartHandler =
|
||||
dragPointOffset = vecSubtract(
|
||||
[event.screenX, event.screenY],
|
||||
getWindowPosition(),
|
||||
);
|
||||
) as [number, number];
|
||||
initialSize = getWindowSize();
|
||||
// Focus click target
|
||||
(event.target as HTMLElement)?.focus();
|
||||
@@ -278,7 +281,10 @@ const resizeMoveHandler = (event: MouseEvent) => {
|
||||
);
|
||||
const delta = vecSubtract(currentOffset, dragPointOffset);
|
||||
// Extra 1x1 area is added to ensure the browser can see the cursor
|
||||
size = vecAdd(initialSize, vecMultiply(resizeMatrix, delta), [1, 1]);
|
||||
size = vecAdd(initialSize, vecMultiply(resizeMatrix, delta), [1, 1]) as [
|
||||
number,
|
||||
number,
|
||||
];
|
||||
// Sane window size values
|
||||
size[0] = Math.max(size[0], 150 * pixelRatio);
|
||||
size[1] = Math.max(size[1], 50 * pixelRatio);
|
||||
|
||||
@@ -160,18 +160,21 @@ const ApcControlScene = (props) => {
|
||||
const [sortByField] = useLocalState('sortByField', 'name');
|
||||
|
||||
const apcs = flow([
|
||||
map((apc, i) => ({
|
||||
...apc,
|
||||
// Generate a unique id
|
||||
id: apc.name + i,
|
||||
})),
|
||||
sortByField === 'name' && sortBy((apc) => apc.name),
|
||||
sortByField === 'charge' && sortBy((apc) => -apc.charge),
|
||||
(apcs) =>
|
||||
map(apcs, (apc, i) => ({
|
||||
...apc,
|
||||
// Generate a unique id
|
||||
id: apc.name + i,
|
||||
})),
|
||||
sortByField === 'name' && ((apcs) => sortBy(apcs, (apc) => apc.name)),
|
||||
sortByField === 'charge' && ((apcs) => sortBy(apcs, (apc) => -apc.charge)),
|
||||
sortByField === 'draw' &&
|
||||
sortBy(
|
||||
(apc) => -powerRank(apc.load),
|
||||
(apc) => -parseFloat(apc.load),
|
||||
),
|
||||
((apcs) =>
|
||||
sortBy(
|
||||
apcs,
|
||||
(apc) => -powerRank(apc.load),
|
||||
(apc) => -parseFloat(apc.load),
|
||||
)),
|
||||
])(data.apcs);
|
||||
return (
|
||||
<Box height={30}>
|
||||
@@ -255,14 +258,11 @@ const ApcControlScene = (props) => {
|
||||
const LogPanel = (props) => {
|
||||
const { data } = useBackend();
|
||||
|
||||
const logs = flow([
|
||||
map((line, i) => ({
|
||||
...line,
|
||||
// Generate a unique id
|
||||
id: line.entry + i,
|
||||
})),
|
||||
(logs) => logs.reverse(),
|
||||
])(data.logs);
|
||||
const logs = map(data.logs, (line, i) => ({
|
||||
...line,
|
||||
// Generate a unique id
|
||||
id: line.entry + i,
|
||||
})).reverse();
|
||||
return (
|
||||
<Box m={-0.5}>
|
||||
{logs.map((line) => (
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { map, sortBy } from 'common/collections';
|
||||
import { flow } from 'common/fp';
|
||||
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button, Flex, Section, Table } from '../components';
|
||||
@@ -7,14 +6,14 @@ import { Window } from '../layouts';
|
||||
|
||||
export const AtmosControlPanel = (props) => {
|
||||
const { act, data } = useBackend();
|
||||
const groups = flow([
|
||||
map((group, i) => ({
|
||||
const groups = sortBy(
|
||||
map(data.excited_groups, (group, i) => ({
|
||||
...group,
|
||||
// Generate a unique id
|
||||
id: group.area + i,
|
||||
})),
|
||||
sortBy((group) => group.id),
|
||||
])(data.excited_groups);
|
||||
(group) => group.id,
|
||||
);
|
||||
return (
|
||||
<Window title="SSAir Control Panel" width={900} height={500}>
|
||||
<Section m={1}>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { filter, sortBy } from 'common/collections';
|
||||
import { flow } from 'common/fp';
|
||||
import { toFixed } from 'common/math';
|
||||
import { BooleanLike } from 'common/react';
|
||||
import { multiline } from 'common/string';
|
||||
@@ -43,10 +42,10 @@ export const BluespaceSender = (props) => {
|
||||
const { act, data } = useBackend<Data>();
|
||||
const { gas_transfer_rate, credits, bluespace_network_gases = [], on } = data;
|
||||
|
||||
const gases: Gas[] = flow([
|
||||
filter<Gas>((gas) => gas.amount >= 0.01),
|
||||
sortBy<Gas>((gas) => -gas.amount),
|
||||
])(bluespace_network_gases);
|
||||
const gases: Gas[] = sortBy(
|
||||
filter(bluespace_network_gases, (gas) => gas.amount >= 0.01),
|
||||
(gas) => -gas.amount,
|
||||
);
|
||||
|
||||
const gasMax = Math.max(1, ...gases.map((gas) => gas.amount));
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { filter, sortBy } from 'common/collections';
|
||||
import { flow } from 'common/fp';
|
||||
import { toFixed } from 'common/math';
|
||||
import { BooleanLike } from 'common/react';
|
||||
import { multiline } from 'common/string';
|
||||
@@ -50,10 +49,10 @@ export const BluespaceVendor = (props) => {
|
||||
tank_full,
|
||||
} = data;
|
||||
|
||||
const gases: Gas[] = flow([
|
||||
filter<Gas>((gas) => gas.amount >= 0.01),
|
||||
sortBy<Gas>((gas) => -gas.amount),
|
||||
])(bluespace_network_gases);
|
||||
const gases: Gas[] = sortBy(
|
||||
filter(bluespace_network_gases, (gas) => gas.amount >= 0.01),
|
||||
(gas) => -gas.amount,
|
||||
);
|
||||
|
||||
const gasMax = Math.max(1, ...gases.map((gas) => gas.amount));
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { filter, sortBy } from 'common/collections';
|
||||
import { flow } from 'common/fp';
|
||||
import { filter, sort } from 'common/collections';
|
||||
import { BooleanLike, classes } from 'common/react';
|
||||
import { createSearch } from 'common/string';
|
||||
import { useState } from 'react';
|
||||
@@ -68,15 +67,17 @@ const prevNextCamera = (
|
||||
* Filters cameras, applies search terms and sorts the alphabetically.
|
||||
*/
|
||||
const selectCameras = (cameras: Camera[], searchText = ''): Camera[] => {
|
||||
const testSearch = createSearch(searchText, (camera: Camera) => camera.name);
|
||||
let queriedCameras = filter(cameras, (camera: Camera) => !!camera.name);
|
||||
if (searchText) {
|
||||
const testSearch = createSearch(
|
||||
searchText,
|
||||
(camera: Camera) => camera.name,
|
||||
);
|
||||
queriedCameras = filter(queriedCameras, testSearch);
|
||||
}
|
||||
queriedCameras = sort(queriedCameras);
|
||||
|
||||
return flow([
|
||||
filter((camera: Camera) => !!camera.name),
|
||||
// Optional search term
|
||||
searchText && filter(testSearch),
|
||||
// Slightly expensive, but way better than sorting in BYOND
|
||||
sortBy((camera: Camera) => camera),
|
||||
])(cameras);
|
||||
return queriedCameras;
|
||||
};
|
||||
|
||||
export const CameraConsole = (props) => {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { filter, sortBy } from 'common/collections';
|
||||
import { flow } from 'common/fp';
|
||||
import { filter } from 'common/collections';
|
||||
|
||||
import { useBackend, useSharedState } from '../backend';
|
||||
import {
|
||||
@@ -159,16 +158,17 @@ const CargoStatus = (props) => {
|
||||
const searchForSupplies = (supplies, search) => {
|
||||
search = search.toLowerCase();
|
||||
|
||||
return flow([
|
||||
(categories) => categories.flatMap((category) => category.packs),
|
||||
const queriedSupplies = sortBy(
|
||||
filter(
|
||||
supplies.flatMap((category) => category.packs),
|
||||
(pack) =>
|
||||
pack.name?.toLowerCase().includes(search.toLowerCase()) ||
|
||||
pack.desc?.toLowerCase().includes(search.toLowerCase()),
|
||||
),
|
||||
sortBy((pack) => pack.name),
|
||||
(packs) => packs.slice(0, 25),
|
||||
])(supplies);
|
||||
(pack) => pack.name,
|
||||
);
|
||||
|
||||
return queriedSupplies.slice(0, 25);
|
||||
};
|
||||
|
||||
export const CargoCatalog = (props) => {
|
||||
|
||||
@@ -29,10 +29,12 @@ const SWIPE_NEEDED = 'SWIPE_NEEDED';
|
||||
const EMAG_SHUTTLE_NOTICE =
|
||||
'This shuttle is deemed significantly dangerous to the crew, and is only supplied by the Syndicate.';
|
||||
|
||||
const sortShuttles = sortBy(
|
||||
(shuttle) => !shuttle.emagOnly,
|
||||
(shuttle) => shuttle.initial_cost,
|
||||
);
|
||||
const sortShuttles = (shuttles) =>
|
||||
sortBy(
|
||||
shuttles,
|
||||
(shuttle) => !shuttle.emagOnly,
|
||||
(shuttle) => shuttle.initial_cost,
|
||||
);
|
||||
|
||||
const AlertButton = (props) => {
|
||||
const { act, data } = useBackend();
|
||||
|
||||
@@ -86,7 +86,7 @@ export const CrewConsole = () => {
|
||||
|
||||
const CrewTable = (props) => {
|
||||
const { act, data } = useBackend();
|
||||
const sensors = sortBy((s) => s.ijob)(data.sensors ?? []);
|
||||
const sensors = sortBy(data.sensors ?? [], (s) => s.ijob);
|
||||
return (
|
||||
<Table>
|
||||
<Table.Row>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { map, sortBy } from 'common/collections';
|
||||
import { flow } from 'common/fp';
|
||||
|
||||
import { useBackend } from '../backend';
|
||||
import { Button, Section, Stack } from '../components';
|
||||
@@ -25,13 +24,17 @@ type DestinationInfo = {
|
||||
* @returns The alphetically sorted list of destinations.
|
||||
*/
|
||||
const sortDestinations = (locations: string[]): DestinationInfo[] => {
|
||||
return flow([
|
||||
map<string, DestinationInfo>((name, index) => ({
|
||||
name: name.toUpperCase(),
|
||||
sorting_id: index + 1,
|
||||
})),
|
||||
sortBy<DestinationInfo>((dest) => dest.name),
|
||||
])(locations);
|
||||
return sortBy(
|
||||
map(
|
||||
locations,
|
||||
(name, index) =>
|
||||
({
|
||||
name: name.toUpperCase(),
|
||||
sorting_id: index + 1,
|
||||
}) as DestinationInfo,
|
||||
),
|
||||
(dest) => dest.name,
|
||||
);
|
||||
};
|
||||
|
||||
export const DestinationTagger = (props) => {
|
||||
|
||||
@@ -211,7 +211,7 @@ const StorageButtons = (props) => {
|
||||
const StorageChromosomes = (props) => {
|
||||
const { data, act } = useBackend();
|
||||
const chromos = data.chromoStorage ?? [];
|
||||
const uniqueChromos = uniqBy((chromo) => chromo.Name)(chromos);
|
||||
const uniqueChromos = uniqBy(chromos, (chromo) => chromo.Name);
|
||||
const chromoName = data.view.storageChromoName;
|
||||
const chromo = chromos.find((chromo) => chromo.Name === chromoName);
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { filter, uniqBy } from 'common/collections';
|
||||
import { flow } from 'common/fp';
|
||||
|
||||
import { useBackend } from '../../backend';
|
||||
import {
|
||||
@@ -122,10 +121,10 @@ export const MutationInfo = (props) => {
|
||||
isSameMutation(x, mutation),
|
||||
);
|
||||
const savedToDisk = diskMutations.find((x) => isSameMutation(x, mutation));
|
||||
const combinedMutations = flow([
|
||||
uniqBy((mutation) => mutation.Name),
|
||||
filter((x) => x.Name !== mutation.Name),
|
||||
])([...diskMutations, ...mutationStorage]);
|
||||
const combinedMutations = filter(
|
||||
uniqBy([...diskMutations, ...mutationStorage], (mutation) => mutation.Name),
|
||||
(x) => x.Name !== mutation.Name,
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<LabeledList>
|
||||
|
||||
@@ -109,7 +109,7 @@ export const ExperimentConfigure = (props) => {
|
||||
const { always_active, has_start_callback } = data;
|
||||
let techwebs = data.techwebs ?? [];
|
||||
|
||||
const experiments = sortBy((exp) => exp.name)(data.experiments ?? []);
|
||||
const experiments = sortBy(data.experiments ?? [], (exp) => exp.name);
|
||||
|
||||
// Group servers together by web
|
||||
let webs = new Map();
|
||||
|
||||
@@ -229,8 +229,9 @@ export const DesignBrowser = <T extends Design = Design>(
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{sortBy((category: Category) => category.title)(
|
||||
{sortBy(
|
||||
Object.values(root.subcategories),
|
||||
(category: Category) => category.title,
|
||||
).map((category) => (
|
||||
<DesignBrowserTab
|
||||
key={category.title}
|
||||
@@ -272,8 +273,9 @@ export const DesignBrowser = <T extends Design = Design>(
|
||||
<Section fill style={{ overflow: 'auto' }}>
|
||||
{searchText.length > 0 ? (
|
||||
<VirtualList>
|
||||
{sortBy((design: T) => design.name)(
|
||||
{sortBy(
|
||||
Object.values(root.descendants),
|
||||
(design: T) => design.name,
|
||||
)
|
||||
.filter((design) =>
|
||||
design.name
|
||||
@@ -290,8 +292,9 @@ export const DesignBrowser = <T extends Design = Design>(
|
||||
</VirtualList>
|
||||
) : selectedCategory === ALL_CATEGORY ? (
|
||||
<VirtualList>
|
||||
{sortBy((design: T) => design.name)(
|
||||
{sortBy(
|
||||
Object.values(root.descendants),
|
||||
(design: T) => design.name,
|
||||
).map((design) =>
|
||||
buildRecipeElement(
|
||||
design,
|
||||
@@ -380,8 +383,9 @@ const DesignBrowserTab = <T extends Design = Design>(
|
||||
Object.entries(category.subcategories).length > 0 &&
|
||||
selectedCategory === category.title && (
|
||||
<div className="FabricatorTabs">
|
||||
{sortBy((category: Category) => category.title)(
|
||||
{sortBy(
|
||||
Object.values(category.subcategories),
|
||||
(category: Category) => category.title,
|
||||
).map((subcategory) => (
|
||||
<DesignBrowserTab
|
||||
key={subcategory.title}
|
||||
@@ -462,7 +466,7 @@ const CategoryView = <T extends Design = Design>(
|
||||
|
||||
const body = (
|
||||
<VirtualList>
|
||||
{sortBy((design: T) => design.name)(category.children).map((design) =>
|
||||
{sortBy(category.children, (design: T) => design.name).map((design) =>
|
||||
buildRecipeElement(
|
||||
design,
|
||||
availableMaterials || {},
|
||||
|
||||
@@ -55,7 +55,7 @@ export const MaterialAccessBar = (props: MaterialAccessBarProps) => {
|
||||
|
||||
return (
|
||||
<Flex wrap>
|
||||
{sortBy((m: Material) => MATERIAL_RARITY[m.name])(availableMaterials).map(
|
||||
{sortBy(availableMaterials, (m: Material) => MATERIAL_RARITY[m.name]).map(
|
||||
(material) => (
|
||||
<Flex.Item grow basis={4.5} key={material.name}>
|
||||
<MaterialCounter
|
||||
|
||||
@@ -39,13 +39,14 @@ export const Fax = (props) => {
|
||||
const { act } = useBackend();
|
||||
const { data } = useBackend<FaxData>();
|
||||
const faxes = data.faxes
|
||||
? sortBy((sortFax: FaxInfo) => sortFax.fax_name)(
|
||||
? sortBy(
|
||||
data.syndicate_network
|
||||
? data.faxes.filter((filterFax: FaxInfo) => filterFax.visible)
|
||||
: data.faxes.filter(
|
||||
(filterFax: FaxInfo) =>
|
||||
filterFax.visible && !filterFax.syndicate_network,
|
||||
),
|
||||
(sortFax: FaxInfo) => sortFax.fax_name,
|
||||
)
|
||||
: [];
|
||||
return (
|
||||
|
||||
@@ -155,10 +155,9 @@ const FilterFlagsEntry = (props) => {
|
||||
|
||||
const filterInfo = data.filter_info;
|
||||
const flags = filterInfo[filterType]['flags'];
|
||||
return map((bitField, flagName) => (
|
||||
return map(flags, (bitField, flagName) => (
|
||||
<Button.Checkbox
|
||||
checked={value & bitField}
|
||||
content={flagName}
|
||||
onClick={() =>
|
||||
act('modify_filter_value', {
|
||||
name: filterName,
|
||||
@@ -167,8 +166,11 @@ const FilterFlagsEntry = (props) => {
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
))(flags);
|
||||
key={flagName}
|
||||
>
|
||||
{flagName}
|
||||
</Button.Checkbox>
|
||||
));
|
||||
};
|
||||
|
||||
const FilterDataEntry = (props) => {
|
||||
@@ -340,9 +342,9 @@ export const Filteriffic = (props) => {
|
||||
{!hasFilters ? (
|
||||
<Box>No filters</Box>
|
||||
) : (
|
||||
map((entry, key) => (
|
||||
map(filters, (entry, key) => (
|
||||
<FilterEntry filterDataEntry={entry} name={key} key={key} />
|
||||
))(filters)
|
||||
))
|
||||
)}
|
||||
</Section>
|
||||
</Window.Content>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { sortBy } from 'common/collections';
|
||||
import { flow } from 'common/fp';
|
||||
import { classes } from 'common/react';
|
||||
import { capitalize } from 'common/string';
|
||||
import { useState } from 'react';
|
||||
@@ -38,9 +37,7 @@ type FishCatalogData = {
|
||||
export const FishCatalog = (props) => {
|
||||
const { act, data } = useBackend<FishCatalogData>();
|
||||
const { fish_info, sponsored_by } = data;
|
||||
const fish_by_name = flow([sortBy((fish: FishInfo) => fish.name)])(
|
||||
fish_info || [],
|
||||
);
|
||||
const fish_by_name = sortBy(fish_info || [], (fish: FishInfo) => fish.name);
|
||||
const [currentFish, setCurrentFish] = useState<FishInfo | null>(null);
|
||||
return (
|
||||
<Window width={500} height={300}>
|
||||
|
||||
@@ -7,30 +7,36 @@ import { useBackend } from '../backend';
|
||||
import { Box, Button, Icon, LabeledList, Section, Table } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
const coordsToVec = (coords) => map(parseFloat)(coords.split(', '));
|
||||
const coordsToVec = (coords) => map(coords.split(', '), parseFloat);
|
||||
|
||||
export const Gps = (props) => {
|
||||
const { act, data } = useBackend();
|
||||
const { currentArea, currentCoords, globalmode, power, tag, updating } = data;
|
||||
const signals = flow([
|
||||
map((signal, index) => {
|
||||
// Calculate distance to the target. BYOND distance is capped to 127,
|
||||
// that's why we roll our own calculations here.
|
||||
const dist =
|
||||
signal.dist &&
|
||||
Math.round(
|
||||
vecLength(
|
||||
vecSubtract(coordsToVec(currentCoords), coordsToVec(signal.coords)),
|
||||
),
|
||||
);
|
||||
return { ...signal, dist, index };
|
||||
}),
|
||||
sortBy(
|
||||
// Signals with distance metric go first
|
||||
(signal) => signal.dist === undefined,
|
||||
// Sort alphabetically
|
||||
(signal) => signal.entrytag,
|
||||
),
|
||||
(signals) =>
|
||||
map(signals, (signal, index) => {
|
||||
// Calculate distance to the target. BYOND distance is capped to 127,
|
||||
// that's why we roll our own calculations here.
|
||||
const dist =
|
||||
signal.dist &&
|
||||
Math.round(
|
||||
vecLength(
|
||||
vecSubtract(
|
||||
coordsToVec(currentCoords),
|
||||
coordsToVec(signal.coords),
|
||||
),
|
||||
),
|
||||
);
|
||||
return { ...signal, dist, index };
|
||||
}),
|
||||
(signals) =>
|
||||
sortBy(
|
||||
signals,
|
||||
// Signals with distance metric go first
|
||||
(signal) => signal.dist === undefined,
|
||||
// Sort alphabetically
|
||||
(signal) => signal.entrytag,
|
||||
),
|
||||
])(data.signals || []);
|
||||
return (
|
||||
<Window title="Global Positioning System" width={470} height={700}>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { filter, sortBy } from 'common/collections';
|
||||
import { flow } from 'common/fp';
|
||||
import { toFixed } from 'common/math';
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import {
|
||||
@@ -90,10 +89,10 @@ const GasList = (props: GasListProps) => {
|
||||
} = props;
|
||||
const { start_power, start_cooling } = data;
|
||||
|
||||
const gases: HypertorusGas[] = flow([
|
||||
filter((gas: HypertorusGas) => gas.amount >= 0.01),
|
||||
sortBy((gas: HypertorusGas) => -gas.amount),
|
||||
])(raw_gases);
|
||||
const gases: HypertorusGas[] = sortBy(
|
||||
filter(raw_gases, (gas) => gas.amount >= 0.01),
|
||||
(gas) => -gas.amount,
|
||||
);
|
||||
|
||||
if (stickyGases) {
|
||||
ensure_gases(gases, stickyGases);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { sortBy } from 'common/collections';
|
||||
import { flow } from 'common/fp';
|
||||
|
||||
import { BooleanLike } from '../../common/react';
|
||||
import { useBackend } from '../backend';
|
||||
@@ -32,7 +31,7 @@ export const Jukebox = () => {
|
||||
const { act, data } = useBackend<Data>();
|
||||
const { active, looping, track_selected, volume, songs } = data;
|
||||
|
||||
const songs_sorted: Song[] = flow([sortBy((song: Song) => song.name)])(songs);
|
||||
const songs_sorted: Song[] = sortBy(songs, (song: Song) => song.name);
|
||||
const song_selected: Song | undefined = songs.find(
|
||||
(song) => song.name === track_selected,
|
||||
);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { map, sortBy } from 'common/collections';
|
||||
import { flow } from 'common/fp';
|
||||
import { capitalize } from 'common/string';
|
||||
import { useState } from 'react';
|
||||
|
||||
@@ -81,10 +80,14 @@ type Book = {
|
||||
category: string;
|
||||
title: string;
|
||||
id: number;
|
||||
};
|
||||
|
||||
type AdminBook = Book & {
|
||||
author_ckey: string;
|
||||
deleted: boolean;
|
||||
};
|
||||
|
||||
type DisplayBook = Book & {
|
||||
type DisplayAdminBook = AdminBook & {
|
||||
key: number;
|
||||
};
|
||||
|
||||
@@ -120,14 +123,18 @@ const SearchAndDisplay = (props) => {
|
||||
view_raw,
|
||||
show_deleted,
|
||||
} = data;
|
||||
const books = flow([
|
||||
map<Book, DisplayBook>((book, i) => ({
|
||||
...book,
|
||||
// Generate a unique id
|
||||
key: i,
|
||||
})),
|
||||
sortBy<DisplayBook>((book) => book.key),
|
||||
])(pages);
|
||||
const books = sortBy(
|
||||
map(
|
||||
pages,
|
||||
(book, i) =>
|
||||
({
|
||||
...book,
|
||||
// Generate a unique id
|
||||
key: i,
|
||||
}) as DisplayAdminBook,
|
||||
),
|
||||
(book) => book.key,
|
||||
);
|
||||
return (
|
||||
<Section>
|
||||
<Stack justify="space-between">
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { map, sortBy } from 'common/collections';
|
||||
import { flow } from 'common/fp';
|
||||
import { classes } from 'common/react';
|
||||
import { useState } from 'react';
|
||||
|
||||
@@ -136,14 +135,14 @@ export const Inventory = (props) => {
|
||||
|
||||
export const InventoryDetails = (props) => {
|
||||
const { act, data } = useBackend();
|
||||
const inventory = flow([
|
||||
map((book, i) => ({
|
||||
const inventory = sortBy(
|
||||
map(data.inventory, (book, i) => ({
|
||||
...book,
|
||||
// Generate a unique id
|
||||
key: i,
|
||||
})),
|
||||
sortBy((book) => book.key),
|
||||
])(data.inventory);
|
||||
(book) => book.key,
|
||||
);
|
||||
return (
|
||||
<Section>
|
||||
<Table>
|
||||
@@ -261,14 +260,14 @@ export const CheckoutEntries = (props) => {
|
||||
|
||||
const CheckoutModal = (props) => {
|
||||
const { act, data } = useBackend();
|
||||
const inventory = flow([
|
||||
map((book, i) => ({
|
||||
const inventory = sortBy(
|
||||
map(data.inventory, (book, i) => ({
|
||||
...book,
|
||||
// Generate a unique id
|
||||
key: i,
|
||||
})),
|
||||
sortBy((book) => book.key),
|
||||
])(data.inventory);
|
||||
(book) => book.key,
|
||||
);
|
||||
|
||||
const [checkoutBook, setCheckoutBook] = useLocalState('CheckoutBook', false);
|
||||
const [bookName, setBookName] = useState('Insert Book name...');
|
||||
@@ -387,14 +386,14 @@ export const SearchAndDisplay = (props) => {
|
||||
params_changed,
|
||||
can_db_request,
|
||||
} = data;
|
||||
const records = flow([
|
||||
map((record, i) => ({
|
||||
const records = sortBy(
|
||||
map(data.pages, (record, i) => ({
|
||||
...record,
|
||||
// Generate a unique id
|
||||
key: i,
|
||||
})),
|
||||
sortBy((record) => record.key),
|
||||
])(data.pages);
|
||||
(record) => record.key,
|
||||
);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { map, sortBy } from 'common/collections';
|
||||
import { flow } from 'common/fp';
|
||||
|
||||
import { useBackend } from '../backend';
|
||||
import {
|
||||
@@ -71,14 +70,14 @@ const SearchAndDisplay = (props) => {
|
||||
author,
|
||||
params_changed,
|
||||
} = data;
|
||||
const records = flow([
|
||||
map((record, i) => ({
|
||||
const records = sortBy(
|
||||
map(data.pages, (record, i) => ({
|
||||
...record,
|
||||
// Generate a unique id
|
||||
key: i,
|
||||
})),
|
||||
sortBy((record) => record.key),
|
||||
])(data.pages);
|
||||
(record) => record.key,
|
||||
);
|
||||
return (
|
||||
<Section>
|
||||
<Stack justify="space-between">
|
||||
|
||||
@@ -118,7 +118,7 @@ export const MatMarket = (props) => {
|
||||
</Stack>
|
||||
</Section>
|
||||
</Section>
|
||||
{sortBy((tempmat: Material) => tempmat.rarity)(materials).map(
|
||||
{sortBy(materials, (tempmat: Material) => tempmat.rarity).map(
|
||||
(material, i) => (
|
||||
<Section key={i}>
|
||||
<Stack fill>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { filter, sortBy } from 'common/collections';
|
||||
import { flow } from 'common/fp';
|
||||
import { useState } from 'react';
|
||||
import { useBackend, useLocalState } from 'tgui/backend';
|
||||
import {
|
||||
@@ -28,10 +27,10 @@ export const MedicalRecordTabs = (props) => {
|
||||
|
||||
const [search, setSearch] = useState('');
|
||||
|
||||
const sorted: MedicalRecord[] = flow([
|
||||
filter((record: MedicalRecord) => isRecordMatch(record, search)),
|
||||
sortBy((record: MedicalRecord) => record.name?.toLowerCase()),
|
||||
])(records);
|
||||
const sorted: MedicalRecord[] = sortBy(
|
||||
filter(records, (record) => isRecordMatch(record, search)),
|
||||
(record) => record.name?.toLowerCase(),
|
||||
);
|
||||
|
||||
return (
|
||||
<Stack fill vertical>
|
||||
|
||||
@@ -20,7 +20,7 @@ export const NtosCrewManifest = (props) => {
|
||||
/>
|
||||
}
|
||||
>
|
||||
{map((entries, department) => (
|
||||
{map(manifest, (entries, department) => (
|
||||
<Section key={department} level={2} title={department}>
|
||||
<Table>
|
||||
{entries.map((entry) => (
|
||||
@@ -31,7 +31,7 @@ export const NtosCrewManifest = (props) => {
|
||||
))}
|
||||
</Table>
|
||||
</Section>
|
||||
))(manifest)}
|
||||
))}
|
||||
</Section>
|
||||
</NtosWindow.Content>
|
||||
</NtosWindow>
|
||||
|
||||
@@ -100,7 +100,8 @@ const ContactsScreen = (props: any) => {
|
||||
|
||||
const [searchUser, setSearchUser] = useState('');
|
||||
|
||||
const sortByUnreads = sortBy<NtChat>((chat) => chat.unread_messages);
|
||||
const sortByUnreads = (array: NtChat[]) =>
|
||||
sortBy(array, (chat) => chat.unread_messages);
|
||||
|
||||
const searchChatByName = createSearch(
|
||||
searchUser,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { filter, sortBy } from 'common/collections';
|
||||
import { flow } from 'common/fp';
|
||||
import { scale, toFixed } from 'common/math';
|
||||
import { BooleanLike } from 'common/react';
|
||||
import { createSearch } from 'common/string';
|
||||
@@ -70,20 +69,22 @@ export const NtosNetDownloader = (props) => {
|
||||
searchItem,
|
||||
(program) => program.filedesc,
|
||||
);
|
||||
const items = flow([
|
||||
let items =
|
||||
searchItem.length > 0
|
||||
? // If we have a query, search everything for it.
|
||||
filter(search)
|
||||
filter(programs, search)
|
||||
: // Otherwise, show respective programs for the category.
|
||||
filter((program: ProgramData) => program.category === selectedCategory),
|
||||
// This sorts all programs in the lists by name and compatibility
|
||||
sortBy(
|
||||
(program: ProgramData) => !program.compatible,
|
||||
(program: ProgramData) => program.filedesc,
|
||||
),
|
||||
filter(programs, (program) => program.category === selectedCategory);
|
||||
// This sorts all programs in the lists by name and compatibility
|
||||
items = sortBy(
|
||||
items,
|
||||
(program: ProgramData) => !program.compatible,
|
||||
(program: ProgramData) => program.filedesc,
|
||||
);
|
||||
if (!emagged) {
|
||||
// This filters the list to only contain verified programs
|
||||
!emagged && filter((program: ProgramData) => program.verifiedsource === 1),
|
||||
])(programs);
|
||||
items = filter(items, (program) => program.verifiedsource === 1);
|
||||
}
|
||||
const disk_free_space = downloading
|
||||
? disk_size - Number(toFixed(disk_used + downloadcompletion))
|
||||
: disk_size - disk_used;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { filter, sortBy } from 'common/collections';
|
||||
import { flow } from 'common/fp';
|
||||
|
||||
import { HEALTH, THREAT } from './constants';
|
||||
import type { AntagGroup, Antagonist, Observable } from './types';
|
||||
@@ -18,7 +17,7 @@ export const getAntagCategories = (antagonists: Antagonist[]) => {
|
||||
categories[antag_group].push(player);
|
||||
});
|
||||
|
||||
return sortBy<AntagGroup>(([key]) => key)(Object.entries(categories));
|
||||
return sortBy<AntagGroup>(Object.entries(categories), ([key]) => key);
|
||||
};
|
||||
|
||||
/** Returns a disguised name in case the person is wearing someone else's ID */
|
||||
@@ -43,15 +42,19 @@ export const getMostRelevant = (
|
||||
searchQuery: string,
|
||||
observables: Observable[][],
|
||||
): Observable => {
|
||||
return flow([
|
||||
// Filters out anything that doesn't match search
|
||||
filter<Observable>((observable) =>
|
||||
isJobOrNameMatch(observable, searchQuery),
|
||||
),
|
||||
const queriedObservables =
|
||||
// Sorts descending by orbiters
|
||||
sortBy<Observable>((observable) => -(observable.orbiters || 0)),
|
||||
// Makes a single Observables list for an easy search
|
||||
])(observables.flat())[0];
|
||||
sortBy(
|
||||
// Filters out anything that doesn't match search
|
||||
filter(
|
||||
observables
|
||||
// Makes a single Observables list for an easy search
|
||||
.flat(),
|
||||
(observable) => isJobOrNameMatch(observable, searchQuery),
|
||||
),
|
||||
(observable) => -(observable.orbiters || 0),
|
||||
);
|
||||
return queriedObservables[0];
|
||||
};
|
||||
|
||||
/** Returns the display color for certain health percentages */
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { filter, sortBy } from 'common/collections';
|
||||
import { flow } from 'common/fp';
|
||||
import { capitalizeFirst, multiline } from 'common/string';
|
||||
import { useBackend, useLocalState } from 'tgui/backend';
|
||||
import {
|
||||
@@ -204,16 +203,13 @@ const ObservableSection = (props: {
|
||||
|
||||
const [searchQuery] = useLocalState<string>('searchQuery', '');
|
||||
|
||||
const filteredSection: Observable[] = flow([
|
||||
filter<Observable>((observable) =>
|
||||
isJobOrNameMatch(observable, searchQuery),
|
||||
),
|
||||
sortBy<Observable>((observable) =>
|
||||
const filteredSection = sortBy(
|
||||
filter(section, (observable) => isJobOrNameMatch(observable, searchQuery)),
|
||||
(observable) =>
|
||||
getDisplayName(observable.full_name, observable.name)
|
||||
.replace(/^"/, '')
|
||||
.toLowerCase(),
|
||||
),
|
||||
])(section);
|
||||
);
|
||||
|
||||
if (!filteredSection.length) {
|
||||
return null;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { filter, sortBy } from 'common/collections';
|
||||
import { flow } from 'common/fp';
|
||||
import { BooleanLike, classes } from 'common/react';
|
||||
import { createSearch } from 'common/string';
|
||||
import { useState } from 'react';
|
||||
@@ -183,44 +182,44 @@ export const PersonalCrafting = (props) => {
|
||||
const [activeType, setFoodType] = useState(
|
||||
Object.keys(craftability).length ? 'Can Make' : data.foodtypes[0],
|
||||
);
|
||||
const material_occurences = flow([
|
||||
sortBy<Material>((material) => -material.occurences),
|
||||
])(data.material_occurences);
|
||||
const material_occurences = sortBy(
|
||||
data.material_occurences,
|
||||
(material) => -material.occurences,
|
||||
);
|
||||
const [activeMaterial, setMaterial] = useState(
|
||||
material_occurences[0].atom_id,
|
||||
);
|
||||
const [tabMode, setTabMode] = useState(0);
|
||||
const searchName = createSearch(searchText, (item: Recipe) => item.name);
|
||||
let recipes = flow([
|
||||
filter<Recipe>(
|
||||
(recipe) =>
|
||||
// If craftable only is selected, then filter by craftability
|
||||
(!display_craftable_only || Boolean(craftability[recipe.ref])) &&
|
||||
// Ignore categories and types when searching
|
||||
(searchText.length > 0 ||
|
||||
// Is foodtype mode and the active type matches
|
||||
(tabMode === TABS.foodtype &&
|
||||
mode === MODE.cooking &&
|
||||
((activeType === 'Can Make' && Boolean(craftability[recipe.ref])) ||
|
||||
recipe.foodtypes?.includes(activeType))) ||
|
||||
// Is material mode and the active material or catalysts match
|
||||
(tabMode === TABS.material &&
|
||||
Object.keys(recipe.reqs).includes(activeMaterial)) ||
|
||||
// Is category mode and the active categroy matches
|
||||
(tabMode === TABS.category &&
|
||||
((activeCategory === 'Can Make' &&
|
||||
Boolean(craftability[recipe.ref])) ||
|
||||
recipe.category === activeCategory))),
|
||||
),
|
||||
sortBy<Recipe>((recipe) => [
|
||||
activeCategory === 'Can Make'
|
||||
? 99 - Object.keys(recipe.reqs).length
|
||||
: Number(craftability[recipe.ref]),
|
||||
recipe.name.toLowerCase(),
|
||||
]),
|
||||
])(data.recipes);
|
||||
let recipes = filter(
|
||||
data.recipes,
|
||||
(recipe) =>
|
||||
// If craftable only is selected, then filter by craftability
|
||||
(!display_craftable_only || Boolean(craftability[recipe.ref])) &&
|
||||
// Ignore categories and types when searching
|
||||
(searchText.length > 0 ||
|
||||
// Is foodtype mode and the active type matches
|
||||
(tabMode === TABS.foodtype &&
|
||||
mode === MODE.cooking &&
|
||||
((activeType === 'Can Make' && Boolean(craftability[recipe.ref])) ||
|
||||
recipe.foodtypes?.includes(activeType))) ||
|
||||
// Is material mode and the active material or catalysts match
|
||||
(tabMode === TABS.material &&
|
||||
Object.keys(recipe.reqs).includes(activeMaterial)) ||
|
||||
// Is category mode and the active categroy matches
|
||||
(tabMode === TABS.category &&
|
||||
((activeCategory === 'Can Make' &&
|
||||
Boolean(craftability[recipe.ref])) ||
|
||||
recipe.category === activeCategory))),
|
||||
);
|
||||
recipes = sortBy(recipes, (recipe) => [
|
||||
activeCategory === 'Can Make'
|
||||
? 99 - Object.keys(recipe.reqs).length
|
||||
: Number(craftability[recipe.ref]),
|
||||
recipe.name.toLowerCase(),
|
||||
]);
|
||||
if (searchText.length > 0) {
|
||||
recipes = recipes.filter(searchName);
|
||||
recipes = filter(recipes, searchName);
|
||||
}
|
||||
const canMake = ['Can Make'];
|
||||
const categories = canMake
|
||||
|
||||
@@ -178,7 +178,7 @@ const Blanks = (props) => {
|
||||
const { act, data } = useBackend();
|
||||
const { blanks, categories, category } = data;
|
||||
|
||||
const sortedBlanks = sortBy((blank) => blank.name)(blanks || []);
|
||||
const sortedBlanks = sortBy(blanks || [], (blank) => blank.name);
|
||||
|
||||
const selectedCategory = category ?? categories[0];
|
||||
const visibleBlanks = sortedBlanks.filter(
|
||||
|
||||
@@ -158,7 +158,7 @@ const sortConnectionRefs = function (
|
||||
direction: ConnectionDirection,
|
||||
connectSources: AssocConnected,
|
||||
) {
|
||||
refs = sortBy((connection: ConnectionRef) => connection.sort_by)(refs);
|
||||
refs = sortBy(refs, (connection: ConnectionRef) => connection.sort_by);
|
||||
refs.map((connection, index) => {
|
||||
let connectSource = connectSources[connection.ref];
|
||||
if (direction === ConnectionDirection.Outgoing) {
|
||||
@@ -264,14 +264,15 @@ const positionPlanes = (connectSources: AssocConnected) => {
|
||||
// and get rid of the now unneeded parent refs
|
||||
const stack = depth_stack.map((layer) =>
|
||||
flow([
|
||||
sortBy((plane: string) => plane_info[plane].plane),
|
||||
sortBy((plane: string) => {
|
||||
const read_from = plane_info[layer[plane]];
|
||||
if (!read_from) {
|
||||
return 0;
|
||||
}
|
||||
return read_from.plane;
|
||||
}),
|
||||
(planes) => sortBy(planes, (plane: string) => plane_info[plane].plane),
|
||||
(planes) =>
|
||||
sortBy(planes, (plane: string) => {
|
||||
const read_from = plane_info[layer[plane]];
|
||||
if (!read_from) {
|
||||
return 0;
|
||||
}
|
||||
return read_from.plane;
|
||||
}),
|
||||
])(Object.keys(layer)),
|
||||
);
|
||||
|
||||
@@ -932,7 +933,7 @@ const AddModal = (props) => {
|
||||
);
|
||||
|
||||
const plane_list = Object.keys(plane_info).map((plane) => plane_info[plane]);
|
||||
const planes = sortBy((plane: Plane) => -plane.plane)(plane_list);
|
||||
const planes = sortBy(plane_list, (plane: Plane) => -plane.plane);
|
||||
|
||||
const plane_options = planes.map((plane) => plane.name);
|
||||
|
||||
|
||||
@@ -26,8 +26,9 @@ export const PortableChemMixer = (props) => {
|
||||
const { act, data } = useBackend<Data>();
|
||||
const { beaker } = data;
|
||||
const beakerTransferAmounts = beaker ? beaker.transferAmounts : [];
|
||||
const chemicals = sortBy((chem: DispensableReagent) => chem.id)(
|
||||
const chemicals = sortBy(
|
||||
data.chemicals,
|
||||
(chem: DispensableReagent) => chem.id,
|
||||
);
|
||||
return (
|
||||
<Window width={500} height={500}>
|
||||
|
||||
@@ -48,18 +48,22 @@ export const PowerMonitorContent = (props) => {
|
||||
const maxValue = Math.max(PEAK_DRAW, ...history.supply, ...history.demand);
|
||||
// Process area data
|
||||
const areas = flow([
|
||||
map((area, i) => ({
|
||||
...area,
|
||||
// Generate a unique id
|
||||
id: area.name + i,
|
||||
})),
|
||||
sortByField === 'name' && sortBy((area) => area.name),
|
||||
sortByField === 'charge' && sortBy((area) => -area.charge),
|
||||
(areas) =>
|
||||
map(areas, (area, i) => ({
|
||||
...area,
|
||||
// Generate a unique id
|
||||
id: area.name + i,
|
||||
})),
|
||||
sortByField === 'name' && ((areas) => sortBy(areas, (area) => area.name)),
|
||||
sortByField === 'charge' &&
|
||||
((areas) => sortBy(areas, (area) => -area.charge)),
|
||||
sortByField === 'draw' &&
|
||||
sortBy(
|
||||
(area) => -powerRank(area.load),
|
||||
(area) => -parseFloat(area.load),
|
||||
),
|
||||
((areas) =>
|
||||
sortBy(
|
||||
areas,
|
||||
(area) => -powerRank(area.load),
|
||||
(area) => -parseFloat(area.load),
|
||||
)),
|
||||
])(data.areas);
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -25,9 +25,10 @@ const antagsByCategory = new Map<Category, Antagonist[]>();
|
||||
|
||||
// This will break at priorities higher than 10, but that almost definitely
|
||||
// will not happen.
|
||||
const binaryInsertAntag = binaryInsertWith((antag: Antagonist) => {
|
||||
return `${antag.priority}_${antag.name}`;
|
||||
});
|
||||
const binaryInsertAntag = (collection: Antagonist[], value: Antagonist) =>
|
||||
binaryInsertWith(collection, value, (antag) => {
|
||||
return `${antag.priority}_${antag.name}`;
|
||||
});
|
||||
|
||||
for (const antagKey of requireAntag.keys()) {
|
||||
const antag = requireAntag<{
|
||||
|
||||
@@ -13,11 +13,13 @@ type PreferenceChild = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
const binaryInsertPreference = binaryInsertWith<PreferenceChild>(
|
||||
(child) => child.name,
|
||||
);
|
||||
const binaryInsertPreference = (
|
||||
collection: PreferenceChild[],
|
||||
value: PreferenceChild,
|
||||
) => binaryInsertWith(collection, value, (child) => child.name);
|
||||
|
||||
const sortByName = sortBy<[string, PreferenceChild[]]>(([name]) => name);
|
||||
const sortByName = (array: [string, PreferenceChild[]][]) =>
|
||||
sortBy(array, ([name]) => name);
|
||||
|
||||
export const GamePreferencesPage = (props) => {
|
||||
const { act, data } = useBackend<PreferencesMenuData>();
|
||||
|
||||
@@ -14,10 +14,11 @@ import {
|
||||
import { ServerPreferencesFetcher } from './ServerPreferencesFetcher';
|
||||
|
||||
const sortJobs = (entries: [string, Job][], head?: string) =>
|
||||
sortBy<[string, Job]>(
|
||||
sortBy(
|
||||
entries,
|
||||
([key, _]) => (key === head ? -1 : 1),
|
||||
([key, _]) => key,
|
||||
)(entries);
|
||||
);
|
||||
|
||||
const PRIORITY_BUTTON_SIZE = '18px';
|
||||
|
||||
|
||||
@@ -67,15 +67,14 @@ const KEY_CODE_TO_BYOND: Record<string, string> = {
|
||||
*/
|
||||
const DOM_KEY_LOCATION_NUMPAD = 3;
|
||||
|
||||
const sortKeybindings = sortBy(([_, keybinding]: [string, Keybinding]) => {
|
||||
return keybinding.name;
|
||||
});
|
||||
const sortKeybindings = (array: [string, Keybinding][]) =>
|
||||
sortBy(array, ([_, keybinding]) => {
|
||||
return keybinding.name;
|
||||
});
|
||||
|
||||
const sortKeybindingsByCategory = sortBy(
|
||||
([category, _]: [string, Record<string, Keybinding>]) => {
|
||||
return category;
|
||||
},
|
||||
);
|
||||
const sortKeybindingsByCategory = (
|
||||
array: [string, Record<string, Keybinding>][],
|
||||
) => sortBy(array, ([category, _]) => category);
|
||||
|
||||
const formatKeyboardEvent = (event: KeyboardEvent): string => {
|
||||
let text = '';
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { filterMap, sortBy } from 'common/collections';
|
||||
import { filter, map, sortBy } from 'common/collections';
|
||||
import { classes } from 'common/react';
|
||||
import { createSearch } from 'common/string';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { filter } from '../../../common/collections';
|
||||
import { flow } from '../../../common/fp';
|
||||
import { createSearch } from '../../../common/string';
|
||||
import { sendAct, useBackend } from '../../backend';
|
||||
import {
|
||||
Autofocus,
|
||||
@@ -202,8 +200,14 @@ const ChoicedSelection = (props: {
|
||||
};
|
||||
|
||||
const searchInCatalog = (searchText = '', catalog: Record<string, string>) => {
|
||||
const maybeSearch = createSearch(searchText, ([name, _icon]) => name);
|
||||
return flow([searchText && filter(maybeSearch)])(Object.entries(catalog));
|
||||
let items = Object.entries(catalog);
|
||||
if (searchText) {
|
||||
items = filter(
|
||||
items,
|
||||
createSearch(searchText, ([name, _icon]) => name),
|
||||
);
|
||||
}
|
||||
return items;
|
||||
};
|
||||
|
||||
const GenderButton = (props: {
|
||||
@@ -368,10 +372,11 @@ const createSetRandomization =
|
||||
});
|
||||
};
|
||||
|
||||
const sortPreferences = sortBy<[string, unknown]>(([featureId, _]) => {
|
||||
const feature = features[featureId];
|
||||
return feature?.name;
|
||||
});
|
||||
const sortPreferences = (array: [string, unknown][]) =>
|
||||
sortBy(array, ([featureId, _]) => {
|
||||
const feature = features[featureId];
|
||||
return feature?.name;
|
||||
});
|
||||
|
||||
export const PreferenceList = (props: {
|
||||
act: typeof sendAct;
|
||||
@@ -451,22 +456,20 @@ export const getRandomization = (
|
||||
|
||||
const { data } = useBackend<PreferencesMenuData>();
|
||||
|
||||
if (!randomBodyEnabled) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return Object.fromEntries(
|
||||
filterMap(Object.keys(preferences), (preferenceKey) => {
|
||||
if (serverData.random.randomizable.indexOf(preferenceKey) === -1) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!randomBodyEnabled) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return [
|
||||
preferenceKey,
|
||||
data.character_preferences.randomization[preferenceKey] ||
|
||||
RandomSetting.Disabled,
|
||||
];
|
||||
}),
|
||||
map(
|
||||
filter(Object.keys(preferences), (key) =>
|
||||
serverData.random.randomizable.includes(key),
|
||||
),
|
||||
(key) => [
|
||||
key,
|
||||
data.character_preferences.randomization[key] || RandomSetting.Disabled,
|
||||
],
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { filterMap } from 'common/collections';
|
||||
import { filter } from 'common/collections';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { useBackend } from '../../backend';
|
||||
@@ -23,13 +23,9 @@ function getCorrespondingPreferences(
|
||||
relevant_preferences: Record<string, string>,
|
||||
) {
|
||||
return Object.fromEntries(
|
||||
filterMap(Object.keys(relevant_preferences), (key) => {
|
||||
if (!customization_options.includes(key)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return [key, relevant_preferences[key]];
|
||||
}),
|
||||
filter(Object.entries(relevant_preferences), ([key, value]) =>
|
||||
customization_options.includes(key),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,9 +21,11 @@ type NameWithKey = {
|
||||
name: Name;
|
||||
};
|
||||
|
||||
const binaryInsertName = binaryInsertWith<NameWithKey>(({ key }) => key);
|
||||
const binaryInsertName = (collection: NameWithKey[], value: NameWithKey) =>
|
||||
binaryInsertWith(collection, value, ({ key }) => key);
|
||||
|
||||
const sortNameWithKeyEntries = sortBy<[string, NameWithKey[]]>(([key]) => key);
|
||||
const sortNameWithKeyEntries = (array: [string, NameWithKey[]][]) =>
|
||||
sortBy(array, ([key]) => key);
|
||||
|
||||
export const MultiNameInput = (props: {
|
||||
handleClose: () => void;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { sortBy, sortStrings } from 'common/collections';
|
||||
import { sort, sortBy } from 'common/collections';
|
||||
import { BooleanLike, classes } from 'common/react';
|
||||
import {
|
||||
ComponentType,
|
||||
@@ -21,7 +21,8 @@ import {
|
||||
import { createSetPreference, PreferencesMenuData } from '../../data';
|
||||
import { ServerPreferencesFetcher } from '../../ServerPreferencesFetcher';
|
||||
|
||||
export const sortChoices = sortBy<[string, ReactNode]>(([name]) => name);
|
||||
export const sortChoices = (array: [string, ReactNode][]) =>
|
||||
sortBy(array, ([name]) => name);
|
||||
|
||||
export type Feature<
|
||||
TReceiving,
|
||||
@@ -209,7 +210,7 @@ export const FeatureDropdownInput = (
|
||||
|
||||
return (
|
||||
<StandardizedDropdown
|
||||
choices={sortStrings(serverData.choices)}
|
||||
choices={sort(serverData.choices)}
|
||||
disabled={props.disabled}
|
||||
buttons={props.buttons}
|
||||
displayNames={displayNames}
|
||||
@@ -278,7 +279,7 @@ export const FeatureIconnedDropdownInput = (
|
||||
|
||||
return (
|
||||
<StandardizedDropdown
|
||||
choices={sortStrings(serverData.choices)}
|
||||
choices={sort(serverData.choices)}
|
||||
displayNames={displayNames}
|
||||
onSetValue={props.handleSetValue}
|
||||
value={props.value.value}
|
||||
|
||||
+2
-3
@@ -18,9 +18,8 @@ type SkinToneServerData = FeatureChoicedServerData & {
|
||||
to_hex: Record<string, HexValue>;
|
||||
};
|
||||
|
||||
const sortHexValues = sortBy<[string, HexValue]>(
|
||||
([_, hexValue]) => -hexValue.lightness,
|
||||
);
|
||||
const sortHexValues = (array: [string, HexValue][]) =>
|
||||
sortBy(array, ([_, hexValue]) => -hexValue.lightness);
|
||||
|
||||
export const skin_tone: Feature<string, string, SkinToneServerData> = {
|
||||
name: 'Skin tone',
|
||||
|
||||
+5
-2
@@ -22,10 +22,13 @@ export const ghost_accs: FeatureChoiced = {
|
||||
component: FeatureDropdownInput,
|
||||
};
|
||||
|
||||
const insertGhostForm = binaryInsertWith<{
|
||||
type GhostForm = {
|
||||
displayText: ReactNode;
|
||||
value: string;
|
||||
}>(({ value }) => value);
|
||||
};
|
||||
|
||||
const insertGhostForm = (collection: GhostForm[], value: GhostForm) =>
|
||||
binaryInsertWith(collection, value, ({ value }) => value);
|
||||
|
||||
const GhostFormInput = (
|
||||
props: FeatureValueProps<string, string, FeatureChoicedServerData>,
|
||||
|
||||
@@ -23,10 +23,10 @@ export const Radio = (props) => {
|
||||
const tunedChannel = RADIO_CHANNELS.find(
|
||||
(channel) => channel.freq === frequency,
|
||||
);
|
||||
const channels = map((value, key) => ({
|
||||
const channels = map(data.channels, (value, key) => ({
|
||||
name: key,
|
||||
status: !!value,
|
||||
}))(data.channels);
|
||||
}));
|
||||
// Calculate window height
|
||||
let height = 106;
|
||||
if (subspace) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { sortStrings } from 'common/collections';
|
||||
import { sort } from 'common/collections';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { useBackend, useLocalState } from '../../backend';
|
||||
@@ -22,9 +22,9 @@ export const MessageWriteTab = (props) => {
|
||||
information_consoles = [],
|
||||
} = data;
|
||||
|
||||
const sorted_assistance = sortStrings(assistance_consoles);
|
||||
const sorted_supply = sortStrings(supply_consoles);
|
||||
const sorted_information = sortStrings(information_consoles);
|
||||
const sorted_assistance = sort(assistance_consoles);
|
||||
const sorted_supply = sort(supply_consoles);
|
||||
const sorted_information = sort(information_consoles);
|
||||
|
||||
const resetMessage = () => {
|
||||
setMessageText('');
|
||||
|
||||
@@ -17,8 +17,9 @@ export const Restock = (props) => {
|
||||
|
||||
export const RestockTracker = (props) => {
|
||||
const { data } = useBackend();
|
||||
const vending_list = sortBy((vend) => vend.percentage)(
|
||||
const vending_list = sortBy(
|
||||
data.vending_list ?? [],
|
||||
(vend) => vend.percentage,
|
||||
);
|
||||
return (
|
||||
<Section fill title="Vendor Stocking Status">
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { filter, sortBy } from 'common/collections';
|
||||
import { flow } from 'common/fp';
|
||||
import { useState } from 'react';
|
||||
import { useBackend, useLocalState } from 'tgui/backend';
|
||||
import {
|
||||
@@ -29,10 +28,10 @@ export const SecurityRecordTabs = (props) => {
|
||||
|
||||
const [search, setSearch] = useState('');
|
||||
|
||||
const sorted: SecurityRecord[] = flow([
|
||||
filter((record: SecurityRecord) => isRecordMatch(record, search)),
|
||||
sortBy((record: SecurityRecord) => record.name),
|
||||
])(records);
|
||||
const sorted = sortBy(
|
||||
filter(records, (record) => isRecordMatch(record, search)),
|
||||
(record) => record.name,
|
||||
);
|
||||
|
||||
return (
|
||||
<Stack fill vertical>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { sortBy } from 'common/collections';
|
||||
import { flow } from 'common/fp';
|
||||
import { classes } from 'common/react';
|
||||
import { createSearch } from 'common/string';
|
||||
import { useState } from 'react';
|
||||
@@ -67,9 +66,10 @@ export const SeedExtractor = (props) => {
|
||||
const search = createSearch(searchText, (item: SeedData) => item.name);
|
||||
const seeds_filtered =
|
||||
searchText.length > 0 ? data.seeds.filter(search) : data.seeds;
|
||||
const seeds = flow([
|
||||
sortBy((item: SeedData) => item[sortField as keyof SeedData]),
|
||||
])(seeds_filtered || []);
|
||||
const seeds = sortBy(
|
||||
seeds_filtered || [],
|
||||
(item: SeedData) => item[sortField as keyof SeedData],
|
||||
);
|
||||
sortField !== 'name' && seeds.reverse();
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { filter, map, sortBy, uniq } from 'common/collections';
|
||||
import { flow } from 'common/fp';
|
||||
import { createSearch } from 'common/string';
|
||||
import { useState } from 'react';
|
||||
|
||||
@@ -30,10 +29,10 @@ export const SelectEquipment = (props) => {
|
||||
|
||||
const isFavorited = (entry) => favorites?.includes(entry.path);
|
||||
|
||||
const outfits = map((entry) => ({
|
||||
const outfits = map([...data.outfits, ...data.custom_outfits], (entry) => ({
|
||||
...entry,
|
||||
favorite: isFavorited(entry),
|
||||
}))([...data.outfits, ...data.custom_outfits]);
|
||||
}));
|
||||
|
||||
// even if no custom outfits were sent, we still want to make sure there's
|
||||
// at least a 'Custom' tab so the button to create a new one pops up
|
||||
@@ -49,15 +48,15 @@ export const SelectEquipment = (props) => {
|
||||
(entry) => entry.name + entry.path,
|
||||
);
|
||||
|
||||
const visibleOutfits = flow([
|
||||
filter((entry) => entry.category === tab),
|
||||
filter(searchFilter),
|
||||
sortBy(
|
||||
(entry) => !entry.favorite,
|
||||
(entry) => !entry.priority,
|
||||
(entry) => entry.name,
|
||||
const visibleOutfits = sortBy(
|
||||
filter(
|
||||
filter(outfits, (entry) => entry.category === tab),
|
||||
searchFilter,
|
||||
),
|
||||
])(outfits);
|
||||
(entry) => !entry.favorite,
|
||||
(entry) => !entry.priority,
|
||||
(entry) => entry.name,
|
||||
);
|
||||
|
||||
const getOutfitEntry = (current_outfit) =>
|
||||
outfits.find((outfit) => getOutfitKey(outfit) === current_outfit);
|
||||
|
||||
@@ -104,7 +104,7 @@ export const ShuttleManipulatorTemplates = (props) => {
|
||||
<Flex>
|
||||
<Flex.Item>
|
||||
<Tabs vertical>
|
||||
{map((template, templateId) => (
|
||||
{map(templateObject, (template, templateId) => (
|
||||
<Tabs.Tab
|
||||
key={templateId}
|
||||
selected={selectedTemplateId === templateId}
|
||||
@@ -112,7 +112,7 @@ export const ShuttleManipulatorTemplates = (props) => {
|
||||
>
|
||||
{template.port_id}
|
||||
</Tabs.Tab>
|
||||
))(templateObject)}
|
||||
))}
|
||||
</Tabs>
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={1} basis={0}>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { filter, map, reduce, sortBy } from 'common/collections';
|
||||
import { flow } from 'common/fp';
|
||||
import { clamp } from 'common/math';
|
||||
import { createSearch } from 'common/string';
|
||||
import { useState } from 'react';
|
||||
@@ -49,8 +47,7 @@ type RecipeBoxProps = {
|
||||
};
|
||||
|
||||
// RecipeList converted via Object.entries() for filterRecipeList
|
||||
type RecipeListEntry = [string, RecipeList | Recipe];
|
||||
type RecipeListFilterableEntry = [string, RecipeList | Recipe | undefined];
|
||||
type RecipeListFilterableEntry = [string, RecipeList | Recipe];
|
||||
|
||||
/**
|
||||
* Type guard for recipe vs recipe list
|
||||
@@ -70,30 +67,29 @@ function isRecipeList(value: Recipe | RecipeList): value is RecipeList {
|
||||
const filterRecipeList = (
|
||||
list: RecipeList,
|
||||
keyFilter: (key: string) => boolean,
|
||||
) => {
|
||||
const filteredList: RecipeList = flow([
|
||||
map((entry: RecipeListEntry): RecipeListFilterableEntry => {
|
||||
const [key, recipe] = entry;
|
||||
): RecipeList | undefined => {
|
||||
const filteredList = Object.fromEntries(
|
||||
Object.entries(list)
|
||||
.flatMap((entry): RecipeListFilterableEntry[] => {
|
||||
const [key, recipe] = entry;
|
||||
|
||||
if (isRecipeList(recipe)) {
|
||||
// If category name matches, return the whole thing.
|
||||
if (keyFilter(key)) {
|
||||
return entry;
|
||||
return [entry];
|
||||
}
|
||||
|
||||
// otherwise, filter sub-entries.
|
||||
return [key, filterRecipeList(recipe, keyFilter)];
|
||||
}
|
||||
if (isRecipeList(recipe)) {
|
||||
// otherwise, filter sub-entries.
|
||||
const subEntries = filterRecipeList(recipe, keyFilter);
|
||||
if (subEntries !== undefined) {
|
||||
return [[key, subEntries]];
|
||||
}
|
||||
}
|
||||
|
||||
return keyFilter(key) ? entry : [key, undefined];
|
||||
}),
|
||||
filter((entry: RecipeListFilterableEntry) => entry[1] !== undefined),
|
||||
sortBy((entry: RecipeListEntry) => entry[0].toLowerCase()),
|
||||
reduce((obj: RecipeList, entry: RecipeListEntry) => {
|
||||
obj[entry[0]] = entry[1];
|
||||
return obj;
|
||||
}, {}),
|
||||
])(Object.entries(list));
|
||||
return [];
|
||||
})
|
||||
.sort(([a], [b]) => (a < b ? -1 : a !== b ? 1 : 0)),
|
||||
);
|
||||
|
||||
return Object.keys(filteredList).length ? filteredList : undefined;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { sortBy } from 'common/collections';
|
||||
import { flow } from 'common/fp';
|
||||
|
||||
import { useBackend } from '../backend';
|
||||
import { Button, Section, Stack } from '../components';
|
||||
@@ -30,8 +29,9 @@ export const StationAlertConsoleContent = (props) => {
|
||||
Camera: 5,
|
||||
};
|
||||
|
||||
const sortedAlarms = flow([sortBy((alarm) => sortingKey[alarm.name])])(
|
||||
const sortedAlarms = sortBy(
|
||||
data.alarms || [],
|
||||
(alarm) => sortingKey[alarm.name],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { filterMap } from 'common/collections';
|
||||
import { filter, map } from 'common/collections';
|
||||
import { exhaustiveCheck } from 'common/exhaustive';
|
||||
import { BooleanLike } from 'common/react';
|
||||
import { useState } from 'react';
|
||||
@@ -110,15 +110,9 @@ const FutureStationTraitsPage = (props) => {
|
||||
icon="times"
|
||||
onClick={() => {
|
||||
act('setup_future_traits', {
|
||||
station_traits: filterMap(
|
||||
future_station_traits,
|
||||
(otherTrait) => {
|
||||
if (otherTrait.path === trait.path) {
|
||||
return undefined;
|
||||
} else {
|
||||
return otherTrait.path;
|
||||
}
|
||||
},
|
||||
station_traits: filter(
|
||||
map(future_station_traits, (t) => t.path),
|
||||
(p) => p !== trait.path,
|
||||
),
|
||||
});
|
||||
}}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { filter, sortBy } from 'common/collections';
|
||||
import { flow } from 'common/fp';
|
||||
import { toFixed } from 'common/math';
|
||||
import { BooleanLike } from 'common/react';
|
||||
import { ReactNode, useState } from 'react';
|
||||
@@ -123,10 +122,14 @@ export const SupermatterContent = (props: SupermatterProps) => {
|
||||
gas_metadata,
|
||||
} = props;
|
||||
const [allGasActive, setAllGasActive] = useState(false);
|
||||
const gas_composition: [gas_path: string, amount: number][] = flow([
|
||||
!allGasActive && filter(([gas_path, amount]) => amount !== 0),
|
||||
sortBy(([gas_path, amount]) => -amount),
|
||||
])(Object.entries(props.gas_composition));
|
||||
let gas_composition = Object.entries(props.gas_composition);
|
||||
if (!allGasActive) {
|
||||
gas_composition = filter(
|
||||
gas_composition,
|
||||
([gas_path, amount]) => amount !== 0,
|
||||
);
|
||||
}
|
||||
gas_composition = sortBy(gas_composition, ([gas_path, amount]) => -amount);
|
||||
|
||||
return (
|
||||
<Stack height="100%">
|
||||
|
||||
@@ -19,7 +19,8 @@ type SurgeryInitiatorData = {
|
||||
target_name: string;
|
||||
};
|
||||
|
||||
const sortSurgeries = sortBy((surgery: Surgery) => surgery.name);
|
||||
const sortSurgeries = (array: Surgery[]) =>
|
||||
sortBy(array, (surgery) => surgery.name);
|
||||
|
||||
type SurgeryInitiatorInnerState = {
|
||||
selectedSurgeryIndex: number;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { map, sortBy } from 'common/collections';
|
||||
import { sortBy } from 'common/collections';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
@@ -41,9 +41,9 @@ const selectRemappedStaticData = (data) => {
|
||||
...node,
|
||||
id: remapId(id),
|
||||
costs,
|
||||
prereq_ids: map(remapId)(node.prereq_ids || []),
|
||||
design_ids: map(remapId)(node.design_ids || []),
|
||||
unlock_ids: map(remapId)(node.unlock_ids || []),
|
||||
prereq_ids: map(node.prereq_ids || [], remapId),
|
||||
design_ids: map(node.design_ids || [], remapId),
|
||||
unlock_ids: map(node.unlock_ids || [], remapId),
|
||||
required_experiments: node.required_experiments || [],
|
||||
discount_experiments: node.discount_experiments || [],
|
||||
};
|
||||
@@ -251,10 +251,11 @@ const TechwebOverview = (props) => {
|
||||
);
|
||||
});
|
||||
} else {
|
||||
displayedNodes = sortBy((x) => node_cache[x.id].name)(
|
||||
displayedNodes = sortBy(
|
||||
tabIndex < 2
|
||||
? nodes.filter((x) => x.tier === tabIndex)
|
||||
: nodes.filter((x) => x.tier >= tabIndex),
|
||||
(x) => node_cache[x.id].name,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Window } from '../layouts';
|
||||
const JOB_REPORT_MENU_FAIL_REASON_TRACKING_DISABLED = 1;
|
||||
const JOB_REPORT_MENU_FAIL_REASON_NO_RECORDS = 2;
|
||||
|
||||
const sortByPlaytime = sortBy(([_, playtime]) => -playtime);
|
||||
const sortByPlaytime = (array) => sortBy(array, ([_, playtime]) => -playtime);
|
||||
|
||||
const PlaytimeSection = (props) => {
|
||||
const { playtimes } = props;
|
||||
|
||||
@@ -63,7 +63,7 @@ export const WarrantConsole = (props) => {
|
||||
const RecordList = (props) => {
|
||||
const { act, data } = useBackend<Data>();
|
||||
const { records = [] } = data;
|
||||
const sorted = sortBy((record: WarrantRecord) => record.crew_name)(records);
|
||||
const sorted = sortBy(records, (record) => record.crew_name);
|
||||
|
||||
const [selectedRecord, setSelectedRecord] = useLocalState<
|
||||
WarrantRecord | undefined
|
||||
|
||||
@@ -72,8 +72,9 @@ export function AccessConfig(props: ConfigProps) {
|
||||
accesses.find((access) => access.name === selectedAccessName) ||
|
||||
accesses[0];
|
||||
|
||||
const selectedAccessEntries = sortBy((entry: Area) => entry.desc)(
|
||||
const selectedAccessEntries = sortBy(
|
||||
selectedAccess?.accesses || [],
|
||||
(entry: Area) => entry.desc,
|
||||
);
|
||||
|
||||
function checkAccessIcon(accesses: Area[]) {
|
||||
|
||||
@@ -248,8 +248,9 @@ const RegionAccessList = (props) => {
|
||||
const selectedAccess = accesses.find(
|
||||
(access) => access.name === selectedAccessName,
|
||||
);
|
||||
const selectedAccessEntries = sortBy((entry) => entry.desc)(
|
||||
const selectedAccessEntries = sortBy(
|
||||
selectedAccess?.accesses || [],
|
||||
(entry) => entry.desc,
|
||||
);
|
||||
|
||||
const allWildcards = Object.keys(wildcardSlots);
|
||||
|
||||
Reference in New Issue
Block a user