2023-09-29 11:11:01 -07:00
|
|
|
import { SourceRange } from '../lang/wasm'
|
2022-11-26 05:13:07 +11:00
|
|
|
|
2024-04-03 19:38:16 +11:00
|
|
|
import { v4 } from 'uuid'
|
|
|
|
|
|
|
|
export const uuidv4 = v4
|
|
|
|
|
2024-08-22 16:08:49 -04:00
|
|
|
/**
|
|
|
|
* A safer type guard for arrays since the built-in Array.isArray() asserts `any[]`.
|
|
|
|
*/
|
|
|
|
export function isArray(val: any): val is unknown[] {
|
|
|
|
return Array.isArray(val)
|
|
|
|
}
|
|
|
|
|
2023-04-03 16:05:25 +10:00
|
|
|
export function isOverlap(a: SourceRange, b: SourceRange) {
|
2023-02-12 10:56:45 +11:00
|
|
|
const [startingRange, secondRange] = a[0] < b[0] ? [a, b] : [b, a]
|
|
|
|
const [lastOfFirst, firstOfSecond] = [startingRange[1], secondRange[0]]
|
|
|
|
return lastOfFirst >= firstOfSecond
|
|
|
|
}
|
|
|
|
|
|
|
|
export function roundOff(num: number, places: number = 2): number {
|
|
|
|
const x = Math.pow(10, places)
|
|
|
|
return Math.round(num * x) / x
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getLength(a: [number, number], b: [number, number]): number {
|
|
|
|
const x = b[0] - a[0]
|
|
|
|
const y = b[1] - a[1]
|
|
|
|
return Math.sqrt(x * x + y * y)
|
|
|
|
}
|
|
|
|
|
2024-05-24 20:54:42 +10:00
|
|
|
/**
|
|
|
|
* Calculates the angle in degrees between two points in a 2D space.
|
|
|
|
* The angle is normalized to the range [-180, 180].
|
|
|
|
*
|
|
|
|
* @param a The first point as a tuple [x, y].
|
|
|
|
* @param b The second point as a tuple [x, y].
|
|
|
|
* @returns The normalized angle in degrees between point a and point b.
|
|
|
|
*/
|
2023-02-12 10:56:45 +11:00
|
|
|
export function getAngle(a: [number, number], b: [number, number]): number {
|
|
|
|
const x = b[0] - a[0]
|
|
|
|
const y = b[1] - a[1]
|
2023-04-03 20:40:58 +10:00
|
|
|
return normaliseAngle((Math.atan2(y, x) * 180) / Math.PI)
|
|
|
|
}
|
|
|
|
|
2024-05-24 20:54:42 +10:00
|
|
|
/**
|
|
|
|
* Normalizes an angle to the range [-180, 180].
|
|
|
|
*
|
|
|
|
* This function takes an angle in degrees and normalizes it so that the result is always within the range of -180 to 180 degrees. This is useful for ensuring consistent angle measurements where the direction (positive or negative) is significant.
|
|
|
|
*
|
|
|
|
* @param angle The angle in degrees to be normalized.
|
|
|
|
* @returns The normalized angle in the range [-180, 180].
|
|
|
|
*/
|
2023-04-03 20:40:58 +10:00
|
|
|
export function normaliseAngle(angle: number): number {
|
|
|
|
const result = ((angle % 360) + 360) % 360
|
2023-04-02 17:20:11 +10:00
|
|
|
return result > 180 ? result - 360 : result
|
2022-11-26 08:34:23 +11:00
|
|
|
}
|
2023-06-22 16:43:33 +10:00
|
|
|
|
2023-08-02 15:41:59 +10:00
|
|
|
export function throttle<T>(
|
|
|
|
func: (args: T) => any,
|
2023-06-22 16:43:33 +10:00
|
|
|
wait: number
|
2023-08-02 15:41:59 +10:00
|
|
|
): (args: T) => any {
|
2023-06-22 16:43:33 +10:00
|
|
|
let timeout: ReturnType<typeof setTimeout> | null
|
2023-08-02 15:41:59 +10:00
|
|
|
let latestArgs: T
|
2023-06-22 16:43:33 +10:00
|
|
|
let latestTimestamp: number
|
|
|
|
|
|
|
|
function later() {
|
|
|
|
timeout = null
|
2023-08-02 15:41:59 +10:00
|
|
|
func(latestArgs)
|
2023-06-22 16:43:33 +10:00
|
|
|
}
|
|
|
|
|
2023-08-02 15:41:59 +10:00
|
|
|
function throttled(args: T) {
|
2023-06-22 16:43:33 +10:00
|
|
|
const currentTimestamp = Date.now()
|
|
|
|
latestArgs = args
|
|
|
|
|
|
|
|
if (!latestTimestamp || currentTimestamp - latestTimestamp >= wait) {
|
|
|
|
latestTimestamp = currentTimestamp
|
2023-08-02 15:41:59 +10:00
|
|
|
func(latestArgs)
|
2023-06-22 16:43:33 +10:00
|
|
|
} else if (!timeout) {
|
|
|
|
timeout = setTimeout(later, wait - (currentTimestamp - latestTimestamp))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return throttled
|
|
|
|
}
|
2023-08-09 20:49:10 +10:00
|
|
|
|
2023-09-08 17:50:37 +10:00
|
|
|
// takes a function and executes it after the wait time, if the function is called again before the wait time is up, the timer is reset
|
2023-09-15 04:35:48 -07:00
|
|
|
export function deferExecution<T>(func: (args: T) => any, wait: number) {
|
2023-09-08 17:50:37 +10:00
|
|
|
let timeout: ReturnType<typeof setTimeout> | null
|
|
|
|
let latestArgs: T
|
|
|
|
|
|
|
|
function later() {
|
|
|
|
timeout = null
|
|
|
|
func(latestArgs)
|
|
|
|
}
|
|
|
|
|
2023-09-15 04:35:48 -07:00
|
|
|
function deferred(args: T) {
|
2023-09-08 17:50:37 +10:00
|
|
|
latestArgs = args
|
|
|
|
if (timeout) {
|
|
|
|
clearTimeout(timeout)
|
|
|
|
}
|
|
|
|
timeout = setTimeout(later, wait)
|
|
|
|
}
|
|
|
|
|
2023-09-15 04:35:48 -07:00
|
|
|
return deferred
|
2023-09-08 17:50:37 +10:00
|
|
|
}
|
|
|
|
|
2023-08-09 20:49:10 +10:00
|
|
|
export function getNormalisedCoordinates({
|
|
|
|
clientX,
|
|
|
|
clientY,
|
|
|
|
streamWidth,
|
|
|
|
streamHeight,
|
|
|
|
el,
|
|
|
|
}: {
|
|
|
|
clientX: number
|
|
|
|
clientY: number
|
|
|
|
streamWidth: number
|
|
|
|
streamHeight: number
|
|
|
|
el: HTMLElement
|
|
|
|
}) {
|
|
|
|
const { left, top, width, height } = el?.getBoundingClientRect()
|
|
|
|
const browserX = clientX - left
|
|
|
|
const browserY = clientY - top
|
|
|
|
return {
|
|
|
|
x: Math.round((browserX / width) * streamWidth),
|
|
|
|
y: Math.round((browserY / height) * streamHeight),
|
|
|
|
}
|
|
|
|
}
|
2024-02-11 12:59:00 +11:00
|
|
|
|
|
|
|
export function isReducedMotion(): boolean {
|
|
|
|
return (
|
|
|
|
typeof window !== 'undefined' &&
|
|
|
|
window.matchMedia &&
|
|
|
|
// TODO/Note I (Kurt) think '(prefers-reduced-motion: reduce)' and '(prefers-reduced-motion)' are equivalent, but not 100% sure
|
|
|
|
window.matchMedia('(prefers-reduced-motion)').matches
|
|
|
|
)
|
|
|
|
}
|
2024-06-03 15:37:23 +10:00
|
|
|
|
|
|
|
export function XOR(bool1: boolean, bool2: boolean): boolean {
|
|
|
|
return (bool1 || bool2) && !(bool1 && bool2)
|
|
|
|
}
|