Remove several functions from collections.js which have ES5 equivalents (#82417)

This commit is contained in:
Arthri
2024-04-18 05:18:20 -07:00
committed by GitHub
parent 4eb943c0d3
commit 12bdaa11c4
69 changed files with 590 additions and 578 deletions
+121 -118
View File
@@ -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 => {
-24
View File
@@ -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));
};
-48
View File
@@ -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));
};
+51
View File
@@ -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);
};
+1 -1
View File
@@ -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];
+9 -8
View File
@@ -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;
};
+10 -4
View File
@@ -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);
+19 -19
View File
@@ -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));
+11 -10
View File
@@ -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) => {
+7 -7
View File
@@ -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
+2 -1
View File
@@ -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}>
+25 -19
View File
@@ -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 -2
View File
@@ -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,
);
+17 -10
View File
@@ -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">
+1 -1
View File
@@ -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;
+13 -10
View File
@@ -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}>
+15 -11
View File
@@ -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}
@@ -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',
@@ -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>,
+2 -2
View File
@@ -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}>
+18 -22
View File
@@ -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;
+6 -5
View File
@@ -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);