Compare commits
4 Commits
kcl-57
...
jtran/prom
Author | SHA1 | Date | |
---|---|---|---|
5ab814d153 | |||
fe83cd94ca | |||
5178a72c52 | |||
f4e801351c |
18
.eslintrc
18
.eslintrc
@ -26,11 +26,17 @@
|
|||||||
"@typescript-eslint/no-duplicate-enum-values": "error",
|
"@typescript-eslint/no-duplicate-enum-values": "error",
|
||||||
"@typescript-eslint/no-duplicate-type-constituents": "error",
|
"@typescript-eslint/no-duplicate-type-constituents": "error",
|
||||||
"@typescript-eslint/no-empty-object-type": "error",
|
"@typescript-eslint/no-empty-object-type": "error",
|
||||||
|
"@typescript-eslint/no-extra-non-null-assertion": "error",
|
||||||
"@typescript-eslint/no-floating-promises": "error",
|
"@typescript-eslint/no-floating-promises": "error",
|
||||||
|
"@typescript-eslint/no-for-in-array": "error",
|
||||||
"no-implied-eval": "off", // This is wrong; use the @typescript-eslint one instead.
|
"no-implied-eval": "off", // This is wrong; use the @typescript-eslint one instead.
|
||||||
"@typescript-eslint/no-implied-eval": "error",
|
"@typescript-eslint/no-implied-eval": "error",
|
||||||
|
"@typescript-eslint/no-misused-new": "error",
|
||||||
"@typescript-eslint/no-misused-promises": "error",
|
"@typescript-eslint/no-misused-promises": "error",
|
||||||
"@typescript-eslint/no-namespace": "error",
|
"@typescript-eslint/no-namespace": "error",
|
||||||
|
"@typescript-eslint/no-non-null-asserted-optional-chain": "error",
|
||||||
|
"@typescript-eslint/no-redundant-type-constituents": "error",
|
||||||
|
"@typescript-eslint/no-this-alias": "warn",
|
||||||
"@typescript-eslint/no-unnecessary-type-assertion": "error",
|
"@typescript-eslint/no-unnecessary-type-assertion": "error",
|
||||||
"@typescript-eslint/no-unnecessary-type-constraint": "error",
|
"@typescript-eslint/no-unnecessary-type-constraint": "error",
|
||||||
"no-unused-vars": "off", // This is wrong; use the @typescript-eslint one instead.
|
"no-unused-vars": "off", // This is wrong; use the @typescript-eslint one instead.
|
||||||
@ -41,7 +47,13 @@
|
|||||||
"vars": "all",
|
"vars": "all",
|
||||||
"args": "none"
|
"args": "none"
|
||||||
}],
|
}],
|
||||||
|
"@typescript-eslint/no-unsafe-unary-minus": "error",
|
||||||
|
"@typescript-eslint/no-wrapper-object-types": "error",
|
||||||
|
"no-throw-literal": "off", // Use @typescript-eslint/only-throw-error instead.
|
||||||
|
"@typescript-eslint/only-throw-error": "error",
|
||||||
"@typescript-eslint/prefer-as-const": "warn",
|
"@typescript-eslint/prefer-as-const": "warn",
|
||||||
|
"@typescript-eslint/prefer-namespace-keyword": "error",
|
||||||
|
"@typescript-eslint/restrict-plus-operands": "error",
|
||||||
"jsx-a11y/click-events-have-key-events": "off",
|
"jsx-a11y/click-events-have-key-events": "off",
|
||||||
"jsx-a11y/no-autofocus": "off",
|
"jsx-a11y/no-autofocus": "off",
|
||||||
"jsx-a11y/no-noninteractive-element-interactions": "off",
|
"jsx-a11y/no-noninteractive-element-interactions": "off",
|
||||||
@ -50,7 +62,7 @@
|
|||||||
{
|
{
|
||||||
"name": "isNaN",
|
"name": "isNaN",
|
||||||
"message": "Use Number.isNaN() instead."
|
"message": "Use Number.isNaN() instead."
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
"no-restricted-syntax": [
|
"no-restricted-syntax": [
|
||||||
"error",
|
"error",
|
||||||
@ -64,7 +76,7 @@
|
|||||||
"never"
|
"never"
|
||||||
],
|
],
|
||||||
"react-hooks/exhaustive-deps": "off",
|
"react-hooks/exhaustive-deps": "off",
|
||||||
"suggest-no-throw/suggest-no-throw": "warn",
|
"suggest-no-throw/suggest-no-throw": "error"
|
||||||
},
|
},
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
@ -84,7 +96,7 @@
|
|||||||
"plugin:testing-library/react"
|
"plugin:testing-library/react"
|
||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
"suggest-no-throw/suggest-no-throw": "off",
|
"suggest-no-throw/suggest-no-throw": "off"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -124,6 +124,7 @@ export class ElectronZoo {
|
|||||||
|
|
||||||
// We need to expose this in order for some tests that require folder
|
// We need to expose this in order for some tests that require folder
|
||||||
// creation and some code below.
|
// creation and some code below.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||||
const that = this
|
const that = this
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
|
@ -37,11 +37,11 @@ export class IntoServer
|
|||||||
implements AsyncGenerator<Uint8Array, never, void>
|
implements AsyncGenerator<Uint8Array, never, void>
|
||||||
{
|
{
|
||||||
private worker: Worker | null = null
|
private worker: Worker | null = null
|
||||||
private type_: String | null = null
|
private type_: string | null = null
|
||||||
|
|
||||||
private trace: boolean = false
|
private trace: boolean = false
|
||||||
|
|
||||||
constructor(type_?: String, worker?: Worker, trace?: boolean) {
|
constructor(type_?: string, worker?: Worker, trace?: boolean) {
|
||||||
super()
|
super()
|
||||||
if (worker && type_) {
|
if (worker && type_) {
|
||||||
this.worker = worker
|
this.worker = worker
|
||||||
|
@ -261,7 +261,7 @@ export class SceneInfra {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
hoveredObject: null | any = null
|
hoveredObject: null | Object3D<Object3DEventMap> = null
|
||||||
raycaster = new Raycaster()
|
raycaster = new Raycaster()
|
||||||
planeRaycaster = new Raycaster()
|
planeRaycaster = new Raycaster()
|
||||||
currentMouseVector = new Vector2()
|
currentMouseVector = new Vector2()
|
||||||
@ -487,11 +487,13 @@ export class SceneInfra {
|
|||||||
if (this.hoveredObject !== firstIntersectObject) {
|
if (this.hoveredObject !== firstIntersectObject) {
|
||||||
const hoveredObj = this.hoveredObject
|
const hoveredObj = this.hoveredObject
|
||||||
this.hoveredObject = null
|
this.hoveredObject = null
|
||||||
|
if (hoveredObj) {
|
||||||
await this.onMouseLeave({
|
await this.onMouseLeave({
|
||||||
selected: hoveredObj,
|
selected: hoveredObj,
|
||||||
mouseEvent: mouseEvent,
|
mouseEvent: mouseEvent,
|
||||||
intersectionPoint,
|
intersectionPoint,
|
||||||
})
|
})
|
||||||
|
}
|
||||||
this.hoveredObject = firstIntersectObject
|
this.hoveredObject = firstIntersectObject
|
||||||
await this.onMouseEnter({
|
await this.onMouseEnter({
|
||||||
selected: this.hoveredObject,
|
selected: this.hoveredObject,
|
||||||
|
@ -175,8 +175,11 @@ export const FileMachineProvider = ({
|
|||||||
commandBarActor.send({ type: 'Close' })
|
commandBarActor.send({ type: 'Close' })
|
||||||
navigate(
|
navigate(
|
||||||
`..${PATHS.FILE}/${encodeURIComponent(
|
`..${PATHS.FILE}/${encodeURIComponent(
|
||||||
|
// TODO: Should this be context.selectedDirectory.path?
|
||||||
|
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
|
||||||
context.selectedDirectory +
|
context.selectedDirectory +
|
||||||
window.electron.path.sep +
|
window.electron.path.sep +
|
||||||
|
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
|
||||||
event.output.name
|
event.output.name
|
||||||
)}`
|
)}`
|
||||||
)
|
)
|
||||||
|
@ -591,6 +591,7 @@ class EngineConnection extends EventTarget {
|
|||||||
* did not establish.
|
* did not establish.
|
||||||
*/
|
*/
|
||||||
connect(reconnecting?: boolean): Promise<void> {
|
connect(reconnecting?: boolean): Promise<void> {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||||
const that = this
|
const that = this
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
if (this.isConnecting() || this.isReady()) {
|
if (this.isConnecting() || this.isReady()) {
|
||||||
|
@ -274,11 +274,17 @@ export const parse = (code: string | Error): ParseResult | Error => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse and throw an exception if there are any errors (probably not suitable for use outside of testing).
|
/**
|
||||||
export const assertParse = (code: string): Node<Program> => {
|
* Parse and throw an exception if there are any errors (probably not suitable for use outside of testing).
|
||||||
|
*/
|
||||||
|
export function assertParse(code: string): Node<Program> {
|
||||||
const result = parse(code)
|
const result = parse(code)
|
||||||
// eslint-disable-next-line suggest-no-throw/suggest-no-throw
|
// eslint-disable-next-line suggest-no-throw/suggest-no-throw
|
||||||
if (err(result) || !resultIsOk(result)) throw result
|
if (err(result)) throw result
|
||||||
|
if (!resultIsOk(result)) {
|
||||||
|
// eslint-disable-next-line suggest-no-throw/suggest-no-throw
|
||||||
|
throw new Error('parse result contains errors', { cause: result })
|
||||||
|
}
|
||||||
return result.program
|
return result.program
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -565,8 +571,8 @@ export function base64Decode(base64: string): ArrayBuffer | Error {
|
|||||||
const decoded = base64_decode(base64)
|
const decoded = base64_decode(base64)
|
||||||
return new Uint8Array(decoded).buffer
|
return new Uint8Array(decoded).buffer
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Caught error decoding base64 string: ' + e)
|
console.error('Caught error decoding base64 string', e)
|
||||||
return new Error('Caught error decoding base64 string: ' + e)
|
return new Error('Caught error decoding base64 string', { cause: e })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
97
src/lib/settle.ts
Normal file
97
src/lib/settle.ts
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import { debounce } from './utils'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A registry for tracking background work and reacting once all work has
|
||||||
|
* settled.
|
||||||
|
*/
|
||||||
|
export class PromiseRegistry {
|
||||||
|
outstanding: Array<TrackedPromise<unknown>>
|
||||||
|
settleCallbacks: Array<() => void>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This reduces overhead when there are many promises all settling around the
|
||||||
|
* same time by trading some latency for when the overall settling is
|
||||||
|
* detected.
|
||||||
|
*/
|
||||||
|
private debouncedCleanUp: () => void
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.outstanding = []
|
||||||
|
this.settleCallbacks = []
|
||||||
|
this.debouncedCleanUp = debounce(this.attemptCleanUp.bind(this), 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
track<T>(promise: Promise<T>, onSettle?: () => void) {
|
||||||
|
// Since built-in Promises don't have a way to synchronously check if
|
||||||
|
// they're settled, it cannot start out settled.
|
||||||
|
this.outstanding.push(new TrackedPromise(promise, this.debouncedCleanUp))
|
||||||
|
if (onSettle) {
|
||||||
|
this.settleCallbacks.push(onSettle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a promise that resolves when all promises have settled.
|
||||||
|
*/
|
||||||
|
waitForSettle(): Promise<void> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.addSettleCallback(resolve)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a callback to be called when all promises have settled.
|
||||||
|
*
|
||||||
|
* @see waitForSettle for a Promise-based interface.
|
||||||
|
*/
|
||||||
|
addSettleCallback(onSettle: () => void) {
|
||||||
|
if (this.isSettled()) {
|
||||||
|
// Already settled, so schedule the callback.
|
||||||
|
setTimeout(onSettle, 0)
|
||||||
|
} else {
|
||||||
|
this.settleCallbacks.push(onSettle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isSettled(): boolean {
|
||||||
|
return this.outstanding.every((p) => p.settled)
|
||||||
|
}
|
||||||
|
|
||||||
|
private attemptCleanUp() {
|
||||||
|
if (this.outstanding.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Garbage collect.
|
||||||
|
const unsettled = this.outstanding.filter((p) => !p.settled)
|
||||||
|
this.outstanding = unsettled
|
||||||
|
// Transition to settled. We could move this into TrackedPromise.
|
||||||
|
// It's a trade-off between reducing latency and reducing overhead.
|
||||||
|
if (unsettled.length === 0) {
|
||||||
|
for (const cb of this.settleCallbacks) {
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
this.settleCallbacks = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Native Promises don't have a way to synchronously detect if they're settled.
|
||||||
|
*/
|
||||||
|
class TrackedPromise<T> {
|
||||||
|
settled: boolean
|
||||||
|
inner: Promise<T>
|
||||||
|
|
||||||
|
constructor(promise: Promise<T>, onSettle: () => void) {
|
||||||
|
this.settled = false
|
||||||
|
this.inner = promise.finally(() => {
|
||||||
|
this.settled = true
|
||||||
|
onSettle()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Singleton regsitry for the whole app.
|
||||||
|
*/
|
||||||
|
export const AppPromises = new PromiseRegistry()
|
@ -88,6 +88,29 @@ export function normaliseAngle(angle: number): number {
|
|||||||
return result > 180 ? result - 360 : result
|
return result > 180 ? result - 360 : result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a function that will delay the execution of the given function each
|
||||||
|
* time it's called until the wait time has passed.
|
||||||
|
*/
|
||||||
|
export function debounce<
|
||||||
|
F extends (...args: any[]) => void,
|
||||||
|
P extends Parameters<F>
|
||||||
|
>(func: F, wait: number): (...args: P) => void {
|
||||||
|
let timeout: ReturnType<typeof setTimeout> | null = null
|
||||||
|
|
||||||
|
function debounced(...args: P) {
|
||||||
|
if (timeout) {
|
||||||
|
clearTimeout(timeout)
|
||||||
|
}
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
timeout = null
|
||||||
|
func(args)
|
||||||
|
}, wait)
|
||||||
|
}
|
||||||
|
|
||||||
|
return debounced
|
||||||
|
}
|
||||||
|
|
||||||
export function throttle<T>(
|
export function throttle<T>(
|
||||||
func: (args: T) => any,
|
func: (args: T) => any,
|
||||||
wait: number
|
wait: number
|
||||||
|
@ -11,7 +11,7 @@ import type { WebContentSendPayload } from './menu/channels'
|
|||||||
|
|
||||||
const typeSafeIpcRendererOn = (
|
const typeSafeIpcRendererOn = (
|
||||||
channel: Channel,
|
channel: Channel,
|
||||||
listener: (event: IpcRendererEvent, ...args: any[]) => Promise<void> | any
|
listener: (event: IpcRendererEvent, ...args: any[]) => void
|
||||||
) => ipcRenderer.on(channel, listener)
|
) => ipcRenderer.on(channel, listener)
|
||||||
|
|
||||||
const resizeWindow = (width: number, height: number) =>
|
const resizeWindow = (width: number, height: number) =>
|
||||||
@ -163,7 +163,7 @@ const listMachines = async (
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const getMachineApiIp = async (): Promise<String | null> =>
|
const getMachineApiIp = async (): Promise<string | null> =>
|
||||||
ipcRenderer.invoke('find_machine_api')
|
ipcRenderer.invoke('find_machine_api')
|
||||||
|
|
||||||
const getArgvParsed = () => {
|
const getArgvParsed = () => {
|
||||||
|
Reference in New Issue
Block a user