@synnaxlabs/x 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@synnaxlabs/x",
3
3
  "private": false,
4
- "version": "0.7.0",
4
+ "version": "0.8.0",
5
5
  "type": "module",
6
6
  "description": "Common Utilities for Synnax Labs",
7
7
  "repository": "https://github.com/synnaxlabs/synnax/tree/main/x/go",
@@ -13,3 +13,4 @@ export * from "@/deep/key";
13
13
  export * from "@/deep/merge";
14
14
  export * from "@/deep/partial";
15
15
  export * from "@/deep/equal";
16
+ export * from "@/deep/memo";
@@ -0,0 +1,15 @@
1
+ import { equal } from "@/deep/equal"
2
+
3
+
4
+ export const memo = <F extends (...args: any[]) => any>(func: F): F => {
5
+ let prevArgs: Parameters<F> = undefined as any
6
+ let prevResult: ReturnType<F> = undefined as any
7
+ const v = ((...args: Parameters<F>) => {
8
+ if (equal(prevArgs, args)) return prevResult
9
+ const result = func(...args)
10
+ prevArgs = args
11
+ prevResult = result
12
+ return result
13
+ })
14
+ return v as F;
15
+ }
@@ -184,6 +184,8 @@ export const dim = (
184
184
  return signed ? dim : Math.abs(dim);
185
185
  };
186
186
 
187
+
188
+
187
189
  /** @returns the pont corresponding to the given corner of the box. */
188
190
  export const xyLoc = (b: Crude, l: location.XY): xy.XY => {
189
191
  const b_ = construct(b);
@@ -62,3 +62,8 @@ export const min = (crude: Crude[]): Dimensions => ({
62
62
  width: Math.min(...crude.map((c) => construct(c).width)),
63
63
  height: Math.min(...crude.map((c) => construct(c).height)),
64
64
  });
65
+
66
+ export const scale = (ca: Crude, factor: number): Dimensions => {
67
+ const a = construct(ca);
68
+ return { width: a.width * factor, height: a.height * factor };
69
+ }
@@ -32,16 +32,16 @@ export const construct = (c: Crude): Direction => {
32
32
  else return "x";
33
33
  };
34
34
 
35
- export const swap = (direction: Direction): Direction =>
36
- direction === "x" ? "y" : "x";
35
+ export const swap = (direction: CrudeDirection): Direction =>
36
+ construct(direction) === "x" ? "y" : "x";
37
37
 
38
- export const dimension = (direction: Direction): Dimension =>
39
- direction === "x" ? "width" : "height";
38
+ export const dimension = (direction: CrudeDirection): Dimension =>
39
+ construct(direction) === "x" ? "width" : "height";
40
40
 
41
- export const location = (direction: Direction): Location =>
42
- direction === "x" ? "left" : "top";
41
+ export const location = (direction: CrudeDirection): Location =>
42
+ construct(direction) === "x" ? "left" : "top";
43
43
 
44
44
  export const isDirection = (c: unknown): c is Direction => crude.safeParse(c).success;
45
45
 
46
- export const signedDimension = (direction: Direction): SignedDimension =>
47
- direction === "x" ? "signedWidth" : "signedHeight";
46
+ export const signedDimension = (direction: CrudeDirection): SignedDimension =>
47
+ construct(direction) === "x" ? "signedWidth" : "signedHeight";
@@ -52,6 +52,14 @@ const SWAPPED: Record<Location, Location> = {
52
52
  center: "center",
53
53
  };
54
54
 
55
+ const ROTATE_90: Record<Location, Location> = {
56
+ top: "left",
57
+ right: "top",
58
+ bottom: "right",
59
+ left: "bottom",
60
+ center: "center",
61
+ };
62
+
55
63
  export const crude = crudeLocation;
56
64
 
57
65
  export type Crude = CrudeLocation;
@@ -65,6 +73,8 @@ export const construct = (cl: Crude): Location => {
65
73
 
66
74
  export const swap = (cl: Crude): Location => SWAPPED[construct(cl)];
67
75
 
76
+ export const rotate90 = (cl: Crude): Location => ROTATE_90[construct(cl)];
77
+
68
78
  export const direction = (cl: Crude): Direction => {
69
79
  const l = construct(cl);
70
80
  if (l === "top" || l === "bottom") return "y";
@@ -76,6 +86,7 @@ export const corner = z.object({ x: xLocation, y: yLocation });
76
86
 
77
87
  export type XY = z.infer<typeof xy>;
78
88
  export type CornerXY = z.infer<typeof corner>;
89
+ export type CornerXYString = "topLeft" | "topRight" | "bottomLeft" | "bottomRight";
79
90
 
80
91
  export const TOP_LEFT: CornerXY = { x: "left", y: "top" };
81
92
  export const TOP_RIGHT: CornerXY = { x: "right", y: "top" };
@@ -46,6 +46,14 @@ test("translate", () => {
46
46
  expect(p.x).toEqual(6);
47
47
  expect(p.y).toEqual(7);
48
48
  });
49
+
50
+ test("translate multiple", () => {
51
+ let p = xy.construct([1, 2]);
52
+ p = xy.translate(p, [5, 5], [2, 2]);
53
+ expect(p.x).toEqual(8);
54
+ expect(p.y).toEqual(9);
55
+ });
56
+
49
57
  describe("equals", () => {
50
58
  const TESTS: Array<[xy.Crude, xy.Crude, boolean]> = [
51
59
  [[1, 1], { x: 1, y: 1 }, true],
package/src/spatial/xy.ts CHANGED
@@ -25,6 +25,7 @@ export { clientXY, xy, type ClientXY as Client, type XY };
25
25
 
26
26
  /** A crude representation of a {@link XY} coordinate as a zod schema. */
27
27
  export const crudeZ = z.union([
28
+ z.number(),
28
29
  xy,
29
30
  numberCouple,
30
31
  dimensions,
@@ -42,7 +43,7 @@ export type Crude = z.infer<typeof crudeZ>;
42
43
  * @param y - If x is a number, the y coordinate. If x is a number and this argument is
43
44
  * not given, the y coordinate is assumed to be the same as the x coordinate.
44
45
  */
45
- export const construct = (x: Crude | number, y?: number): XY => {
46
+ export const construct = (x: Crude, y?: number): XY => {
46
47
  if (typeof x === "number") return { x, y: y ?? x };
47
48
  if (Array.isArray(x)) return { x: x[0], y: x[1] };
48
49
  if ("signedWidth" in x) return { x: x.signedWidth, y: x.signedHeight };
@@ -63,11 +64,12 @@ export const INFINITY = { x: Infinity, y: Infinity };
63
64
  /** An x and y coordinate of NaN */
64
65
  export const NAN = { x: NaN, y: NaN };
65
66
 
66
- /** @returns true if the two XY coordinates are semntically equal. */
67
- export const equals = (a: Crude, b: Crude): boolean => {
67
+ /** @returns true if the two XY coordinates are semantically equal. */
68
+ export const equals = (a: Crude, b: Crude, threshold: number = 0): boolean => {
68
69
  const a_ = construct(a);
69
70
  const b_ = construct(b);
70
- return a_.x === b_.x && a_.y === b_.y;
71
+ if (threshold === 0) return a_.x === b_.x && a_.y === b_.y;
72
+ return Math.abs(a_.x - b_.x) <= threshold && Math.abs(a_.y - b_.y) <= threshold;
71
73
  };
72
74
 
73
75
  /** Is zero is true if the XY coordinate has a semantic x and y value of zero. */
@@ -94,15 +96,28 @@ export const translateY = (c: Crude, y: number): XY => {
94
96
  return { x: p.x, y: p.y + y };
95
97
  };
96
98
 
99
+ type TranslateOverloadOne = (a: Crude, b: Crude, ...cb: Crude[]) => XY;
100
+ type TranslateOverloadTwo = (a: Crude, direction: Direction, value: number) => XY;
101
+
97
102
  /**
98
103
  * @returns the given coordinate translated by an arbitrary number of translation
99
104
  * coordinates.
100
105
  */
101
- export const translate = (a: Crude, b: Crude, ...cb: Crude[]): XY =>
102
- [a, b, ...cb].reduce((p: XY, c) => {
103
- const xy = construct(c);
106
+ export const translate: TranslateOverloadOne & TranslateOverloadTwo = (
107
+ a,
108
+ b,
109
+ v,
110
+ ...cb
111
+ ): XY => {
112
+ if (typeof b === "string" && typeof v === "number") {
113
+ if (b === "x") return translateX(a, v);
114
+ return translateY(a, v);
115
+ }
116
+ return [a, b, v ?? ZERO, ...cb].reduce((p: XY, c) => {
117
+ const xy = construct(c as Crude);
104
118
  return { x: p.x + xy.x, y: p.y + xy.y };
105
119
  }, ZERO);
120
+ };
106
121
 
107
122
  /**
108
123
  * @returns the given coordinate the given direction set to the given value.
@@ -7,8 +7,9 @@
7
7
  // License, use of this software will be governed by the Apache License, Version 2.0,
8
8
  // included in the file licenses/APL.txt.
9
9
 
10
- import { type z } from "zod";
10
+ import { date, type z } from "zod";
11
11
 
12
+ import { compare } from "@/compare";
12
13
  import { bounds } from "@/spatial";
13
14
  import { type GLBufferController, type GLBufferUsage } from "@/telem/gl";
14
15
  import {
@@ -21,7 +22,6 @@ import {
21
22
  type TimeStamp,
22
23
  type CrudeDataType,
23
24
  } from "@/telem/telem";
24
- import { compare } from "@/compare";
25
25
 
26
26
  export type SampleValue = number | bigint;
27
27
 
@@ -204,6 +204,22 @@ export class Series {
204
204
  return new TextDecoder().decode(this.buffer).split("\n").slice(0, -1);
205
205
  }
206
206
 
207
+ toUUIDs(): string[] {
208
+ if (!this.dataType.equals(DataType.UUID))
209
+ throw new Error("cannot convert non-uuid series to uuids");
210
+ const den = DataType.UUID.density.valueOf();
211
+ const r = Array(this.length);
212
+
213
+ for (let i = 0; i < this.length; i++) {
214
+ const v = this.buffer.slice(i * den, (i + 1) * den);
215
+ const id = Array.from(new Uint8Array(v), (b) => b.toString(16).padStart(2, "0"))
216
+ .join("")
217
+ .replace(/(.{8})(.{4})(.{4})(.{4})(.{12})/, "$1-$2-$3-$4-$5");
218
+ r[i] = id;
219
+ }
220
+ return r;
221
+ }
222
+
207
223
  parseJSON<Z extends z.ZodTypeAny>(schema: Z): Array<z.output<Z>> {
208
224
  if (!this.dataType.equals(DataType.JSON))
209
225
  throw new Error("cannot convert non-string series to strings");
@@ -217,7 +233,7 @@ export class Series {
217
233
  /** @returns the time range of this array. */
218
234
  get timeRange(): TimeRange {
219
235
  validateFieldNotNull("timeRange", this._timeRange);
220
- return this._timeRange as TimeRange;
236
+ return this._timeRange!;
221
237
  }
222
238
 
223
239
  /** @returns the capacity of the underlying buffer in bytes. */
@@ -328,6 +328,11 @@ export class TimeStamp extends Number implements Stringer {
328
328
  return remainder(this, divisor);
329
329
  }
330
330
 
331
+ /** @returns true if the day portion TimeStamp is today, false otherwise. */
332
+ get isToday(): boolean {
333
+ return this.truncate(TimeSpan.DAY).equals(TimeStamp.now().truncate(TimeSpan.DAY));
334
+ }
335
+
331
336
  truncate(span: TimeSpan | TimeStamp): TimeStamp {
332
337
  return this.sub(this.remainder(span));
333
338
  }