Initial
This commit is contained in:
92
resources/app/common/primitives/array.mjs
Normal file
92
resources/app/common/primitives/array.mjs
Normal file
@@ -0,0 +1,92 @@
|
||||
import {getType, objectsEqual} from "../utils/helpers.mjs";
|
||||
|
||||
/**
|
||||
* Flatten nested arrays by concatenating their contents
|
||||
* @returns {any[]} An array containing the concatenated inner values
|
||||
*/
|
||||
export function deepFlatten() {
|
||||
return this.reduce((acc, val) => Array.isArray(val) ? acc.concat(val.deepFlatten()) : acc.concat(val), []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test element-wise equality of the values of this array against the values of another array
|
||||
* @param {any[]} other Some other array against which to test equality
|
||||
* @returns {boolean} Are the two arrays element-wise equal?
|
||||
*/
|
||||
export function equals(other) {
|
||||
if ( !(other instanceof Array) || (other.length !== this.length) ) return false;
|
||||
return this.every((v0, i) => {
|
||||
const v1 = other[i];
|
||||
const t0 = getType(v0);
|
||||
const t1 = getType(v1);
|
||||
if ( t0 !== t1 ) return false;
|
||||
if ( v0?.equals instanceof Function ) return v0.equals(v1);
|
||||
if ( t0 === "Object" ) return objectsEqual(v0, v1);
|
||||
return v0 === v1;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Partition an original array into two children array based on a logical test
|
||||
* Elements which test as false go into the first result while elements testing as true appear in the second
|
||||
* @param rule {Function}
|
||||
* @returns {Array} An Array of length two whose elements are the partitioned pieces of the original
|
||||
*/
|
||||
export function partition(rule) {
|
||||
return this.reduce((acc, val) => {
|
||||
let test = rule(val);
|
||||
acc[Number(test)].push(val);
|
||||
return acc;
|
||||
}, [[], []]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Join an Array using a string separator, first filtering out any parts which return a false-y value
|
||||
* @param {string} sep The separator string
|
||||
* @returns {string} The joined string, filtered of any false values
|
||||
*/
|
||||
export function filterJoin(sep) {
|
||||
return this.filter(p => !!p).join(sep);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an element within the Array and remove it from the array
|
||||
* @param {Function} find A function to use as input to findIndex
|
||||
* @param {*} [replace] A replacement for the spliced element
|
||||
* @returns {*|null} The replacement element, the removed element, or null if no element was found.
|
||||
*/
|
||||
export function findSplice(find, replace) {
|
||||
const idx = this.findIndex(find);
|
||||
if ( idx === -1 ) return null;
|
||||
if ( replace !== undefined ) {
|
||||
this.splice(idx, 1, replace);
|
||||
return replace;
|
||||
} else {
|
||||
const item = this[idx];
|
||||
this.splice(idx, 1);
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and initialize an array of length n with integers from 0 to n-1
|
||||
* @memberof Array
|
||||
* @param {number} n The desired array length
|
||||
* @param {number} [min=0] A desired minimum number from which the created array starts
|
||||
* @returns {number[]} An array of integers from min to min+n
|
||||
*/
|
||||
export function fromRange(n, min=0) {
|
||||
return Array.from({length: n}, (v, i) => i + min);
|
||||
}
|
||||
|
||||
// Define primitives on the Array prototype
|
||||
Object.defineProperties(Array.prototype, {
|
||||
deepFlatten: {value: deepFlatten},
|
||||
equals: {value: equals},
|
||||
filterJoin: {value: filterJoin},
|
||||
findSplice: {value: findSplice},
|
||||
partition: {value: partition}
|
||||
});
|
||||
Object.defineProperties(Array,{
|
||||
fromRange: {value: fromRange}
|
||||
});
|
||||
35
resources/app/common/primitives/date.mjs
Normal file
35
resources/app/common/primitives/date.mjs
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Test whether a Date instance is valid.
|
||||
* A valid date returns a number for its timestamp, and NaN otherwise.
|
||||
* NaN is never equal to itself.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isValid() {
|
||||
return this.getTime() === this.getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a standard YYYY-MM-DD string for the Date instance.
|
||||
* @returns {string} The date in YYYY-MM-DD format
|
||||
*/
|
||||
export function toDateInputString() {
|
||||
const yyyy = this.getFullYear();
|
||||
const mm = (this.getMonth() + 1).paddedString(2);
|
||||
const dd = this.getDate().paddedString(2);
|
||||
return `${yyyy}-${mm}-${dd}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a standard H:M:S.Z string for the Date instance.
|
||||
* @returns {string} The time in H:M:S format
|
||||
*/
|
||||
export function toTimeInputString() {
|
||||
return this.toTimeString().split(" ")[0];
|
||||
}
|
||||
|
||||
// Define primitives on the Date prototype
|
||||
Object.defineProperties(Date.prototype, {
|
||||
isValid: {value: isValid},
|
||||
toDateInputString: {value: toDateInputString},
|
||||
toTimeInputString: {value: toTimeInputString}
|
||||
});
|
||||
165
resources/app/common/primitives/math.mjs
Normal file
165
resources/app/common/primitives/math.mjs
Normal file
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
* √3
|
||||
* @type {number}
|
||||
*/
|
||||
export const SQRT3 = 1.7320508075688772;
|
||||
|
||||
/**
|
||||
* √⅓
|
||||
* @type {number}
|
||||
*/
|
||||
export const SQRT1_3 = 0.5773502691896257;
|
||||
|
||||
/**
|
||||
* Bound a number between some minimum and maximum value, inclusively.
|
||||
* @param {number} num The current value
|
||||
* @param {number} min The minimum allowed value
|
||||
* @param {number} max The maximum allowed value
|
||||
* @return {number} The clamped number
|
||||
* @memberof Math
|
||||
*/
|
||||
export function clamp(num, min, max) {
|
||||
return Math.min(Math.max(num, min), max);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since v12
|
||||
* @ignore
|
||||
*/
|
||||
export function clamped(num, min, max) {
|
||||
const msg = "Math.clamped is deprecated in favor of Math.clamp.";
|
||||
foundry.utils.logCompatibilityWarning(msg, {since: 12, until: 14, once: true});
|
||||
return clamp(num, min, max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Linear interpolation function
|
||||
* @param {number} a An initial value when weight is 0.
|
||||
* @param {number} b A terminal value when weight is 1.
|
||||
* @param {number} w A weight between 0 and 1.
|
||||
* @return {number} The interpolated value between a and b with weight w.
|
||||
*/
|
||||
export function mix(a, b, w) {
|
||||
return a * (1 - w) + b * w;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform an angle in degrees to be bounded within the domain [0, 360)
|
||||
* @param {number} degrees An angle in degrees
|
||||
* @returns {number} The same angle on the range [0, 360)
|
||||
*/
|
||||
export function normalizeDegrees(degrees, base) {
|
||||
const d = degrees % 360;
|
||||
if ( base !== undefined ) {
|
||||
const msg = "Math.normalizeDegrees(degrees, base) is deprecated.";
|
||||
foundry.utils.logCompatibilityWarning(msg, {since: 12, until: 14, once: true});
|
||||
if ( base === 360 ) return d <= 0 ? d + 360 : d;
|
||||
}
|
||||
return d < 0 ? d + 360 : d;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform an angle in radians to be bounded within the domain [-PI, PI]
|
||||
* @param {number} radians An angle in degrees
|
||||
* @return {number} The same angle on the range [-PI, PI]
|
||||
*/
|
||||
export function normalizeRadians(radians) {
|
||||
const pi = Math.PI;
|
||||
const pi2 = pi * 2;
|
||||
return radians - (pi2 * Math.floor((radians + pi) / pi2));
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since v12
|
||||
* @ignore
|
||||
*/
|
||||
export function roundDecimals(number, places) {
|
||||
const msg = "Math.roundDecimals is deprecated.";
|
||||
foundry.utils.logCompatibilityWarning(msg, {since: 12, until: 14, once: true});
|
||||
places = Math.max(Math.trunc(places), 0);
|
||||
let scl = Math.pow(10, places);
|
||||
return Math.round(number * scl) / scl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform an angle in radians to a number in degrees
|
||||
* @param {number} angle An angle in radians
|
||||
* @return {number} An angle in degrees
|
||||
*/
|
||||
export function toDegrees(angle) {
|
||||
return angle * (180 / Math.PI);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform an angle in degrees to an angle in radians
|
||||
* @param {number} angle An angle in degrees
|
||||
* @return {number} An angle in radians
|
||||
*/
|
||||
export function toRadians(angle) {
|
||||
return angle * (Math.PI / 180);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the oscillation between `a` and `b` at time `t`.
|
||||
* @param {number} a The minimium value of the oscillation
|
||||
* @param {number} b The maximum value of the oscillation
|
||||
* @param {number} t The time
|
||||
* @param {number} [p=1] The period (must be nonzero)
|
||||
* @param {(x: number) => number} [f=Math.cos] The periodic function (its period must be 2π)
|
||||
* @returns {number} `((b - a) * (f(2π * t / p) + 1) / 2) + a`
|
||||
*/
|
||||
export function oscillation(a, b, t, p=1, f=Math.cos) {
|
||||
return ((b - a) * (f((2 * Math.PI * t) / p) + 1) / 2) + a;
|
||||
}
|
||||
|
||||
// Define properties on the Math environment
|
||||
Object.defineProperties(Math, {
|
||||
SQRT3: {value: SQRT3},
|
||||
SQRT1_3: {value: SQRT1_3},
|
||||
clamp: {
|
||||
value: clamp,
|
||||
configurable: true,
|
||||
writable: true
|
||||
},
|
||||
clamped: {
|
||||
value: clamped,
|
||||
configurable: true,
|
||||
writable: true
|
||||
},
|
||||
mix: {
|
||||
value: mix,
|
||||
configurable: true,
|
||||
writable: true
|
||||
},
|
||||
normalizeDegrees: {
|
||||
value: normalizeDegrees,
|
||||
configurable: true,
|
||||
writable: true
|
||||
},
|
||||
normalizeRadians: {
|
||||
value: normalizeRadians,
|
||||
configurable: true,
|
||||
writable: true
|
||||
},
|
||||
roundDecimals: {
|
||||
value: roundDecimals,
|
||||
configurable: true,
|
||||
writable: true
|
||||
},
|
||||
toDegrees: {
|
||||
value: toDegrees,
|
||||
configurable: true,
|
||||
writable: true
|
||||
},
|
||||
toRadians: {
|
||||
value: toRadians,
|
||||
configurable: true,
|
||||
writable: true
|
||||
},
|
||||
oscillation: {
|
||||
value: oscillation,
|
||||
configurable: true,
|
||||
writable: true
|
||||
}
|
||||
});
|
||||
|
||||
10
resources/app/common/primitives/module.mjs
Normal file
10
resources/app/common/primitives/module.mjs
Normal file
@@ -0,0 +1,10 @@
|
||||
/** @module primitives */
|
||||
|
||||
export * as Array from "./array.mjs";
|
||||
export * as Date from "./date.mjs";
|
||||
export * as Math from "./math.mjs";
|
||||
export * as Number from "./number.mjs";
|
||||
export * as Set from "./set.mjs";
|
||||
export * as String from "./string.mjs";
|
||||
export * as RegExp from "./regexp.mjs";
|
||||
export * as URL from "./url.mjs";
|
||||
128
resources/app/common/primitives/number.mjs
Normal file
128
resources/app/common/primitives/number.mjs
Normal file
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* Test for near-equivalence of two numbers within some permitted epsilon
|
||||
* @param {number} n Some other number
|
||||
* @param {number} e Some permitted epsilon, by default 1e-8
|
||||
* @returns {boolean} Are the numbers almost equal?
|
||||
*/
|
||||
export function almostEqual(n, e=1e-8) {
|
||||
return Math.abs(this - n) < e;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a number to an ordinal string representation. i.e.
|
||||
* 1 => 1st
|
||||
* 2 => 2nd
|
||||
* 3 => 3rd
|
||||
* @returns {string}
|
||||
*/
|
||||
export function ordinalString() {
|
||||
const s = ["th","st","nd","rd"];
|
||||
const v = this % 100;
|
||||
return this + (s[(v-20)%10]||s[v]||s[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string front-padded by zeroes to reach a certain number of numeral characters
|
||||
* @param {number} digits The number of characters desired
|
||||
* @returns {string} The zero-padded number
|
||||
*/
|
||||
export function paddedString(digits) {
|
||||
return this.toString().padStart(digits, "0");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string prefaced by the sign of the number (+) or (-)
|
||||
* @returns {string} The signed number as a string
|
||||
*/
|
||||
export function signedString() {
|
||||
return (( this < 0 ) ? "" : "+") + this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Round a number to the closest number which is a multiple of the provided interval.
|
||||
* This is a convenience function intended to humanize issues of floating point precision.
|
||||
* The interval is treated as a standard string representation to determine the amount of decimal truncation applied.
|
||||
* @param {number} interval The interval to round the number to the nearest multiple of
|
||||
* @param {string} [method=round] The rounding method in: round, ceil, floor
|
||||
* @returns {number} The rounded number
|
||||
*
|
||||
* @example Round a number to the nearest step interval
|
||||
* ```js
|
||||
* let n = 17.18;
|
||||
* n.toNearest(5); // 15
|
||||
* n.toNearest(10); // 20
|
||||
* n.toNearest(10, "floor"); // 10
|
||||
* n.toNearest(10, "ceil"); // 20
|
||||
* n.toNearest(0.25); // 17.25
|
||||
* ```
|
||||
*/
|
||||
export function toNearest(interval=1, method="round") {
|
||||
if ( interval < 0 ) throw new Error(`Number#toNearest interval must be positive`);
|
||||
const float = Math[method](this / interval) * interval;
|
||||
const trunc = Number.isInteger(interval) ? 0 : String(interval).length - 2;
|
||||
return Number(float.toFixed(trunc));
|
||||
}
|
||||
|
||||
/**
|
||||
* A faster numeric between check which avoids type coercion to the Number object.
|
||||
* Since this avoids coercion, if non-numbers are passed in unpredictable results will occur. Use with caution.
|
||||
* @param {number} a The lower-bound
|
||||
* @param {number} b The upper-bound
|
||||
* @param {boolean} inclusive Include the bounding values as a true result?
|
||||
* @return {boolean} Is the number between the two bounds?
|
||||
*/
|
||||
export function between(a, b, inclusive=true) {
|
||||
const min = Math.min(a, b);
|
||||
const max = Math.max(a, b);
|
||||
return inclusive ? (this >= min) && (this <= max) : (this > min) && (this < max);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Number#between
|
||||
* @ignore
|
||||
*/
|
||||
Number.between = function(num, a, b, inclusive=true) {
|
||||
let min = Math.min(a, b);
|
||||
let max = Math.max(a, b);
|
||||
return inclusive ? (num >= min) && (num <= max) : (num > min) && (num < max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether a value is numeric.
|
||||
* This is the highest performing algorithm currently available, per https://jsperf.com/isnan-vs-typeof/5
|
||||
* @memberof Number
|
||||
* @param {*} n A value to test
|
||||
* @return {boolean} Is it a number?
|
||||
*/
|
||||
export function isNumeric(n) {
|
||||
if ( n instanceof Array ) return false;
|
||||
else if ( [null, ""].includes(n) ) return false;
|
||||
return +n === +n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to create a number from a user-provided string.
|
||||
* @memberof Number
|
||||
* @param {string|number} n The value to convert; typically a string, but may already be a number.
|
||||
* @return {number} The number that the string represents, or NaN if no number could be determined.
|
||||
*/
|
||||
export function fromString(n) {
|
||||
if ( typeof n === "number" ) return n;
|
||||
if ( (typeof n !== "string") || !n.length ) return NaN;
|
||||
n = n.replace(/\s+/g, "");
|
||||
return Number(n);
|
||||
}
|
||||
|
||||
// Define properties on the Number environment
|
||||
Object.defineProperties(Number.prototype, {
|
||||
almostEqual: {value: almostEqual},
|
||||
between: {value: between},
|
||||
ordinalString: {value: ordinalString},
|
||||
paddedString: {value: paddedString},
|
||||
signedString: {value: signedString},
|
||||
toNearest: {value: toNearest}
|
||||
});
|
||||
Object.defineProperties(Number, {
|
||||
isNumeric: {value: isNumeric},
|
||||
fromString: {value: fromString}
|
||||
});
|
||||
13
resources/app/common/primitives/regexp.mjs
Normal file
13
resources/app/common/primitives/regexp.mjs
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Escape a given input string, prefacing special characters with backslashes for use in a regular expression
|
||||
* @param {string} string The un-escaped input string
|
||||
* @returns {string} The escaped string, suitable for use in regular expression
|
||||
*/
|
||||
export function escape(string) {
|
||||
return string.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
||||
}
|
||||
|
||||
// Define properties on the RegExp environment
|
||||
Object.defineProperties(RegExp, {
|
||||
escape: {value: escape}
|
||||
});
|
||||
232
resources/app/common/primitives/set.mjs
Normal file
232
resources/app/common/primitives/set.mjs
Normal file
@@ -0,0 +1,232 @@
|
||||
/**
|
||||
* Return the difference of two sets.
|
||||
* @param {Set} other Some other set to compare against
|
||||
* @returns {Set} The difference defined as objects in this which are not present in other
|
||||
*/
|
||||
export function difference(other) {
|
||||
if ( !(other instanceof Set) ) throw new Error("Some other Set instance must be provided.");
|
||||
const difference = new Set();
|
||||
for ( const element of this ) {
|
||||
if ( !other.has(element) ) difference.add(element);
|
||||
}
|
||||
return difference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the symmetric difference of two sets.
|
||||
* @param {Set} other Another set.
|
||||
* @returns {Set} The set of elements that exist in this or other, but not both.
|
||||
*/
|
||||
export function symmetricDifference(other) {
|
||||
if ( !(other instanceof Set) ) throw new Error("Some other Set instance must be provided.");
|
||||
const difference = new Set(this);
|
||||
for ( const element of other ) {
|
||||
if ( difference.has(element) ) difference.delete(element);
|
||||
else difference.add(element);
|
||||
}
|
||||
return difference
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether this set is equal to some other set.
|
||||
* Sets are equal if they share the same members, independent of order
|
||||
* @param {Set} other Some other set to compare against
|
||||
* @returns {boolean} Are the sets equal?
|
||||
*/
|
||||
export function equals(other) {
|
||||
if ( !(other instanceof Set ) ) return false;
|
||||
if ( other.size !== this.size ) return false;
|
||||
for ( let element of this ) {
|
||||
if ( !other.has(element) ) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the first value from the set.
|
||||
* @returns {*} The first element in the set, or undefined
|
||||
*/
|
||||
export function first() {
|
||||
return this.values().next().value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the intersection of two sets.
|
||||
* @param {Set} other Some other set to compare against
|
||||
* @returns {Set} The intersection of both sets
|
||||
*/
|
||||
export function intersection(other) {
|
||||
const n = new Set();
|
||||
for ( let element of this ) {
|
||||
if ( other.has(element) ) n.add(element);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether this set has an intersection with another set.
|
||||
* @param {Set} other Another set to compare against
|
||||
* @returns {boolean} Do the sets intersect?
|
||||
*/
|
||||
export function intersects(other) {
|
||||
for ( let element of this ) {
|
||||
if ( other.has(element) ) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the union of two sets.
|
||||
* @param {Set} other The other set.
|
||||
* @returns {Set}
|
||||
*/
|
||||
export function union(other) {
|
||||
if ( !(other instanceof Set) ) throw new Error("Some other Set instance must be provided.");
|
||||
const union = new Set(this);
|
||||
for ( const element of other ) union.add(element);
|
||||
return union;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether this set is a subset of some other set.
|
||||
* A set is a subset if all its members are also present in the other set.
|
||||
* @param {Set} other Some other set that may be a subset of this one
|
||||
* @returns {boolean} Is the other set a subset of this one?
|
||||
*/
|
||||
export function isSubset(other) {
|
||||
if ( !(other instanceof Set ) ) return false;
|
||||
if ( other.size < this.size ) return false;
|
||||
for ( let element of this ) {
|
||||
if ( !other.has(element) ) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a set to a JSON object by mapping its contents to an array
|
||||
* @returns {Array} The set elements as an array.
|
||||
*/
|
||||
export function toObject() {
|
||||
return Array.from(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether every element in this Set satisfies a certain test criterion.
|
||||
* @see Array#every
|
||||
* @param {function(*,number,Set): boolean} test The test criterion to apply. Positional arguments are the value,
|
||||
* the index of iteration, and the set being tested.
|
||||
* @returns {boolean} Does every element in the set satisfy the test criterion?
|
||||
*/
|
||||
export function every(test) {
|
||||
let i = 0;
|
||||
for ( const v of this ) {
|
||||
if ( !test(v, i, this) ) return false;
|
||||
i++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter this set to create a subset of elements which satisfy a certain test criterion.
|
||||
* @see Array#filter
|
||||
* @param {function(*,number,Set): boolean} test The test criterion to apply. Positional arguments are the value,
|
||||
* the index of iteration, and the set being filtered.
|
||||
* @returns {Set} A new Set containing only elements which satisfy the test criterion.
|
||||
*/
|
||||
export function filter(test) {
|
||||
const filtered = new Set();
|
||||
let i = 0;
|
||||
for ( const v of this ) {
|
||||
if ( test(v, i, this) ) filtered.add(v);
|
||||
i++;
|
||||
}
|
||||
return filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the first element in this set which satisfies a certain test criterion.
|
||||
* @see Array#find
|
||||
* @param {function(*,number,Set): boolean} test The test criterion to apply. Positional arguments are the value,
|
||||
* the index of iteration, and the set being searched.
|
||||
* @returns {*|undefined} The first element in the set which satisfies the test criterion, or undefined.
|
||||
*/
|
||||
export function find(test) {
|
||||
let i = 0;
|
||||
for ( const v of this ) {
|
||||
if ( test(v, i, this) ) return v;
|
||||
i++;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Set where every element is modified by a provided transformation function.
|
||||
* @see Array#map
|
||||
* @param {function(*,number,Set): boolean} transform The transformation function to apply.Positional arguments are
|
||||
* the value, the index of iteration, and the set being transformed.
|
||||
* @returns {Set} A new Set of equal size containing transformed elements.
|
||||
*/
|
||||
export function map(transform) {
|
||||
const mapped = new Set();
|
||||
let i = 0;
|
||||
for ( const v of this ) {
|
||||
mapped.add(transform(v, i, this));
|
||||
i++;
|
||||
}
|
||||
if ( mapped.size !== this.size ) {
|
||||
throw new Error("The Set#map operation illegally modified the size of the set");
|
||||
}
|
||||
return mapped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Set with elements that are filtered and transformed by a provided reducer function.
|
||||
* @see Array#reduce
|
||||
* @param {function(*,*,number,Set): *} reducer A reducer function applied to each value. Positional
|
||||
* arguments are the accumulator, the value, the index of iteration, and the set being reduced.
|
||||
* @param {*} accumulator The initial value of the returned accumulator.
|
||||
* @returns {*} The final value of the accumulator.
|
||||
*/
|
||||
export function reduce(reducer, accumulator) {
|
||||
let i = 0;
|
||||
for ( const v of this ) {
|
||||
accumulator = reducer(accumulator, v, i, this);
|
||||
i++;
|
||||
}
|
||||
return accumulator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether any element in this Set satisfies a certain test criterion.
|
||||
* @see Array#some
|
||||
* @param {function(*,number,Set): boolean} test The test criterion to apply. Positional arguments are the value,
|
||||
* the index of iteration, and the set being tested.
|
||||
* @returns {boolean} Does any element in the set satisfy the test criterion?
|
||||
*/
|
||||
export function some(test) {
|
||||
let i = 0;
|
||||
for ( const v of this ) {
|
||||
if ( test(v, i, this) ) return true;
|
||||
i++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Assign primitives to Set prototype
|
||||
Object.defineProperties(Set.prototype, {
|
||||
difference: {value: difference},
|
||||
symmetricDifference: {value: symmetricDifference},
|
||||
equals: {value: equals},
|
||||
every: {value: every},
|
||||
filter: {value: filter},
|
||||
find: {value: find},
|
||||
first: {value: first},
|
||||
intersection: {value: intersection},
|
||||
intersects: {value: intersects},
|
||||
union: {value: union},
|
||||
isSubset: {value: isSubset},
|
||||
map: {value: map},
|
||||
reduce: {value: reduce},
|
||||
some: {value: some},
|
||||
toObject: {value: toObject}
|
||||
});
|
||||
82
resources/app/common/primitives/string.mjs
Normal file
82
resources/app/common/primitives/string.mjs
Normal file
File diff suppressed because one or more lines are too long
16
resources/app/common/primitives/url.mjs
Normal file
16
resources/app/common/primitives/url.mjs
Normal file
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Attempt to parse a URL without throwing an error.
|
||||
* @param {string} url The string to parse.
|
||||
* @returns {URL|null} The parsed URL if successful, otherwise null.
|
||||
*/
|
||||
export function parseSafe(url) {
|
||||
try {
|
||||
return new URL(url);
|
||||
} catch (err) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Define properties on the URL environment
|
||||
Object.defineProperties(URL, {
|
||||
parseSafe: {value: parseSafe}
|
||||
});
|
||||
Reference in New Issue
Block a user