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'
|
2024-08-22 19:13:27 -04:00
|
|
|
import { isDesktop } from './isDesktop'
|
2024-04-03 19:38:16 +11:00
|
|
|
|
|
|
|
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
|
|
|
|
2024-08-22 19:13:27 -04:00
|
|
|
// TODO: Remove the empty platform type.
|
|
|
|
export type Platform = 'macos' | 'windows' | 'linux' | ''
|
|
|
|
|
|
|
|
export function platform(): Platform {
|
|
|
|
if (isDesktop()) {
|
|
|
|
const platform = window.electron.platform ?? ''
|
|
|
|
// https://nodejs.org/api/process.html#processplatform
|
|
|
|
switch (platform) {
|
|
|
|
case 'darwin':
|
|
|
|
return 'macos'
|
|
|
|
case 'win32':
|
|
|
|
return 'windows'
|
|
|
|
// We don't currently care to distinguish between these.
|
|
|
|
case 'android':
|
|
|
|
case 'freebsd':
|
|
|
|
case 'linux':
|
|
|
|
case 'openbsd':
|
|
|
|
case 'sunos':
|
|
|
|
return 'linux'
|
|
|
|
default:
|
|
|
|
console.error('Unknown platform:', platform)
|
|
|
|
return ''
|
|
|
|
}
|
|
|
|
}
|
2024-08-23 16:20:22 -04:00
|
|
|
|
|
|
|
// navigator.platform is deprecated, but many browsers still support it, and
|
|
|
|
// it's more accurate than userAgent and userAgentData in Playwright.
|
|
|
|
if (
|
|
|
|
navigator.platform?.indexOf('Mac') === 0 ||
|
|
|
|
navigator.platform === 'iPhone'
|
|
|
|
) {
|
|
|
|
return 'macos'
|
|
|
|
}
|
|
|
|
if (navigator.platform === 'Win32') {
|
|
|
|
return 'windows'
|
|
|
|
}
|
|
|
|
|
|
|
|
// Chrome only, but more accurate than userAgent.
|
|
|
|
let userAgentDataPlatform: unknown
|
|
|
|
if (
|
|
|
|
'userAgentData' in navigator &&
|
|
|
|
navigator.userAgentData &&
|
|
|
|
typeof navigator.userAgentData === 'object' &&
|
|
|
|
'platform' in navigator.userAgentData
|
|
|
|
) {
|
|
|
|
userAgentDataPlatform = navigator.userAgentData.platform
|
|
|
|
if (userAgentDataPlatform === 'macOS') return 'macos'
|
|
|
|
if (userAgentDataPlatform === 'Windows') return 'windows'
|
|
|
|
}
|
|
|
|
|
2024-08-22 19:13:27 -04:00
|
|
|
if (navigator.userAgent.indexOf('Mac') !== -1) {
|
|
|
|
return 'macos'
|
|
|
|
} else if (navigator.userAgent.indexOf('Win') !== -1) {
|
|
|
|
return 'windows'
|
|
|
|
} else if (navigator.userAgent.indexOf('Linux') !== -1) {
|
|
|
|
return 'linux'
|
|
|
|
}
|
2024-08-23 16:20:22 -04:00
|
|
|
console.error(
|
|
|
|
'Unknown platform userAgent:',
|
|
|
|
navigator.platform,
|
|
|
|
userAgentDataPlatform,
|
|
|
|
navigator.userAgent
|
|
|
|
)
|
2024-08-22 19:13:27 -04:00
|
|
|
return ''
|
|
|
|
}
|
|
|
|
|
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)
|
|
|
|
}
|