From edd7d88f0310bbb5de9e7c589f02b23251fad12c Mon Sep 17 00:00:00 2001 From: Valentin Vago <65971+zeropaper@users.noreply.github.com> Date: Fri, 24 Mar 2023 20:21:27 +0100 Subject: [PATCH] chore: fix linting --- .eslintrc.js | 13 +- __mocks__/vscode.ts | 22 +- jest.config.ts | 4 +- scripts-dts/assetTools.ts | 12 +- scripts-dts/canvas.ts | 177 ++-- scripts-dts/mathTools.ts | 135 +-- scripts-dts/miscTools.ts | 32 +- scripts-dts/scriptRunner.ts | 10 +- scripts-dts/threejs.ts | 14 +- scripts-dts/worker.ts | 8 +- src/capture/index.ts | 114 +-- src/display/Display.ts | 143 ++- src/display/Display.worker.ts | 136 +-- src/display/DisplayWorker.test.ts | 4 +- src/display/DisplayWorker.ts | 182 ++-- src/display/displayState.ts | 34 +- src/display/index.ts | 20 +- src/extension/VFExtension.ts | 208 ++-- src/extension/VFPanel.ts | 170 ++-- src/extension/WebServer.ts | 313 +++--- src/extension/asyncReadFile.ts | 20 +- src/extension/commands/createLayer.ts | 4 +- src/extension/commands/index.ts | 30 +- src/extension/commands/openControls.ts | 10 +- src/extension/commands/openEditor.ts | 90 +- src/extension/commands/removeLayer.ts | 4 +- src/extension/commands/resetData.ts | 10 +- .../commands/scaffoldProject.test.ts | 26 +- src/extension/commands/scaffoldProject.ts | 169 ++-- src/extension/commands/setBPM.ts | 10 +- src/extension/commands/setStageSize.ts | 12 +- src/extension/commands/toggleLayer.ts | 14 +- src/extension/configuration.ts | 14 +- src/extension/extension.ts | 14 +- src/extension/getNonce.ts | 10 +- src/extension/getWebviewOptions.ts | 10 +- src/extension/getWorkspaceFolder.ts | 14 +- src/extension/readLayerScripts.ts | 14 +- src/extension/readScripts.ts | 24 +- src/extension/readWorkspaceRC.test.ts | 6 +- src/extension/readWorkspaceRC.ts | 20 +- src/extension/scriptUri.ts | 14 +- src/extension/store.ts | 102 +- src/extension/textDocumentScriptInfo.ts | 32 +- src/extension/workspaceFileExists.test.ts | 33 +- src/extension/workspaceFileExists.ts | 22 +- src/layers/Canvas2D/Canvas2DLayer.spec.ts | 112 +- src/layers/Canvas2D/Canvas2DLayer.ts | 28 +- src/layers/Canvas2D/canvasTools.ts | 957 +++++++++--------- src/layers/Layer.spec.ts | 112 +- src/layers/Layer.ts | 53 +- .../ThreeJS/ThreeJSLayer.skippedspec.ts | 112 +- src/layers/ThreeJS/ThreeJSLayer.ts | 86 +- src/types.ts | 140 ++- src/utils/ScriptRunner.spec.ts | 278 ++--- src/utils/ScriptRunner.ts | 206 ++-- src/utils/Scriptable.ts | 126 ++- src/utils/VFS.ts | 22 +- src/utils/assetTools.ts | 42 +- src/utils/blob2DataURI.ts | 14 +- src/utils/blobURI2DataURI.ts | 10 +- src/utils/com.spec.ts | 148 +-- src/utils/com.ts | 172 ++-- src/utils/dataURI2Blob.ts | 16 +- src/utils/deprecate.ts | 16 +- src/utils/mathTools.ts | 104 +- src/utils/miscTools.ts | 92 +- src/webviews/ComContext.tsx | 66 +- src/webviews/components/AppInfo.tsx | 26 +- src/webviews/components/Audio.tsx | 52 +- src/webviews/components/ControlDisplay.tsx | 22 +- src/webviews/components/Display.tsx | 10 +- src/webviews/components/DisplaysList.tsx | 22 +- src/webviews/components/LayersList.tsx | 35 +- src/webviews/components/StoreControl.tsx | 14 +- src/webviews/index.tsx | 56 +- src/webviews/store.ts | 80 +- src/webviews/vscode.ts | 6 +- 78 files changed, 2836 insertions(+), 2878 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 7523d733..47258a47 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -16,7 +16,18 @@ module.exports = { }, plugins: [ 'react' - ] + ], + rules: { + 'promise/param-names': 'warn', + '@typescript-eslint/explicit-function-return-type': 'warn', + '@typescript-eslint/strict-boolean-expressions': 'warn', + '@typescript-eslint/no-floating-promises': 'warn', + '@typescript-eslint/no-misused-promises': 'warn', + '@typescript-eslint/no-loss-of-precision': 'warn', + '@typescript-eslint/consistent-type-assertions': 'warn', + '@typescript-eslint/no-dynamic-delete': 'warn', + '@typescript-eslint/prefer-nullish-coalescing': 'warn' + } // rules: { // 'max-len': 'warn', // 'no-console': ['warn', { allow: ['warn', 'error'] }], diff --git a/__mocks__/vscode.ts b/__mocks__/vscode.ts index 6dcb0308..7cfe7e34 100644 --- a/__mocks__/vscode.ts +++ b/__mocks__/vscode.ts @@ -1,13 +1,13 @@ -let workspacePath = '/absolute/fictive'; +let workspacePath = '/absolute/fictive' const workspace = { - workspaceFolders: ['fictive-a', 'fictive-b'], -}; + workspaceFolders: ['fictive-a', 'fictive-b'] +} // eslint-disable-next-line @typescript-eslint/naming-convention -export function __setWorkspace(absPath: string, info: { worksapceFolders: string[] }) { - workspace.workspaceFolders = info.worksapceFolders; - workspacePath = absPath; +export function __setWorkspace (absPath: string, info: { worksapceFolders: string[] }) { + workspace.workspaceFolders = info.worksapceFolders + workspacePath = absPath } export const Uri = { @@ -15,9 +15,9 @@ export const Uri = { joinPath: jest.fn((_, filepath: string) => { // console.warn('[vscode mock] Uri.joinPath', workspacePath, filepath); return { - fsPath: `${workspacePath}/${filepath}`, - }; - }), -}; + fsPath: `${workspacePath}/${filepath}` + } + }) +} -export { workspace }; +export { workspace } diff --git a/jest.config.ts b/jest.config.ts index ec13d444..e4e74328 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -96,7 +96,7 @@ export default { // A preset that is used as a base for Jest's configuration // preset: undefined, - preset: 'ts-jest', + preset: 'ts-jest' // Run tests from one or more projects // projects: undefined, @@ -195,4 +195,4 @@ export default { // Whether to use watchman for file crawling // watchman: true, -}; +} diff --git a/scripts-dts/assetTools.ts b/scripts-dts/assetTools.ts index 716bb223..b83a2f40 100644 --- a/scripts-dts/assetTools.ts +++ b/scripts-dts/assetTools.ts @@ -1,10 +1,10 @@ -import type * as assetTools from '../src/utils/assetTools'; +import type * as assetTools from '../src/utils/assetTools' declare global { - const clearFetchedAssets: typeof assetTools.clearFetchedAssets; - const loadImage: typeof assetTools.loadImage; - const loadVideo: typeof assetTools.loadVideo; - const asset: typeof assetTools.asset; + const clearFetchedAssets: typeof assetTools.clearFetchedAssets + const loadImage: typeof assetTools.loadImage + const loadVideo: typeof assetTools.loadVideo + const asset: typeof assetTools.asset } -export { }; +export { } diff --git a/scripts-dts/canvas.ts b/scripts-dts/canvas.ts index 36ee9485..c08409f6 100644 --- a/scripts-dts/canvas.ts +++ b/scripts-dts/canvas.ts @@ -1,94 +1,95 @@ -import type { Canvas2DAPI } from '../src/layers/Canvas2D/canvasTools'; +import type { Canvas2DAPI } from '../src/layers/Canvas2D/canvasTools' -import './scriptRunner'; -import './mathTools'; -import './miscTools'; -import './assetTools'; +import './scriptRunner' +import './mathTools' +import './miscTools' +import './assetTools' declare global { - const width: Canvas2DAPI['width']; - const height: Canvas2DAPI['height']; - const vmin: Canvas2DAPI['vmin']; - const vmax: Canvas2DAPI['vmax']; - const vh: Canvas2DAPI['vh']; - const vw: Canvas2DAPI['vw']; - const textLines: Canvas2DAPI['textLines']; - const mirror: Canvas2DAPI['mirror']; - const mediaType: Canvas2DAPI['mediaType']; - const clear: Canvas2DAPI['clear']; - const copy: Canvas2DAPI['copy']; - const pasteImage: Canvas2DAPI['pasteImage']; - const pasteContain: Canvas2DAPI['pasteContain']; - const pasteCover: Canvas2DAPI['pasteCover']; - const fontSize: Canvas2DAPI['fontSize']; - const fontFamily: Canvas2DAPI['fontFamily']; - const fontWeight: Canvas2DAPI['fontWeight']; - const plot: Canvas2DAPI['plot']; - const circle: Canvas2DAPI['circle']; - const polygon: Canvas2DAPI['polygon']; - const grid: Canvas2DAPI['grid']; - const centeredGrid: Canvas2DAPI['centeredGrid']; + const width: Canvas2DAPI['width'] + const height: Canvas2DAPI['height'] + const vmin: Canvas2DAPI['vmin'] + const vmax: Canvas2DAPI['vmax'] + const vh: Canvas2DAPI['vh'] + const vw: Canvas2DAPI['vw'] + const textLines: Canvas2DAPI['textLines'] + const mirror: Canvas2DAPI['mirror'] + const mediaType: Canvas2DAPI['mediaType'] + const clear: Canvas2DAPI['clear'] + const copy: Canvas2DAPI['copy'] + const pasteImage: Canvas2DAPI['pasteImage'] + const pasteContain: Canvas2DAPI['pasteContain'] + const pasteCover: Canvas2DAPI['pasteCover'] + const fontSize: Canvas2DAPI['fontSize'] + const fontFamily: Canvas2DAPI['fontFamily'] + const fontWeight: Canvas2DAPI['fontWeight'] + const plot: Canvas2DAPI['plot'] + const circle: Canvas2DAPI['circle'] + const polygon: Canvas2DAPI['polygon'] + const grid: Canvas2DAPI['grid'] + const centeredGrid: Canvas2DAPI['centeredGrid'] - const clip: Canvas2DAPI['clip']; - const createImageData: Canvas2DAPI['createImageData']; - const createLinearGradient: Canvas2DAPI['createLinearGradient']; - const createPattern: Canvas2DAPI['createPattern']; - const createRadialGradient: Canvas2DAPI['createRadialGradient']; - const drawImage: Canvas2DAPI['drawImage']; - const fill: Canvas2DAPI['fill']; - const fillText: Canvas2DAPI['fillText']; - const getImageData: Canvas2DAPI['getImageData']; - const getLineDash: Canvas2DAPI['getLineDash']; - const getTransform: Canvas2DAPI['getTransform']; - const isPointInPath: Canvas2DAPI['isPointInPath']; - const isPointInStroke: Canvas2DAPI['isPointInStroke']; - const measureText: Canvas2DAPI['measureText']; - const putImageData: Canvas2DAPI['putImageData']; - const scale: Canvas2DAPI['scale']; - const setLineDash: Canvas2DAPI['setLineDash']; - const setTransform: Canvas2DAPI['setTransform']; - const stroke: Canvas2DAPI['stroke']; - const strokeText: Canvas2DAPI['strokeText']; - const transform: Canvas2DAPI['transform']; - const translate: Canvas2DAPI['translate']; - const arc: Canvas2DAPI['arc']; - const arcTo: Canvas2DAPI['arcTo']; - const beginPath: Canvas2DAPI['beginPath']; - const bezierCurveTo: Canvas2DAPI['bezierCurveTo']; - const clearRect: Canvas2DAPI['clearRect']; - const closePath: Canvas2DAPI['closePath']; - const ellipse: Canvas2DAPI['ellipse']; - const fillRect: Canvas2DAPI['fillRect']; - const lineTo: Canvas2DAPI['lineTo']; - // @ts-ignore - const moveTo: Canvas2DAPI['moveTo']; - const quadraticCurveTo: Canvas2DAPI['quadraticCurveTo']; - const rect: Canvas2DAPI['rect']; - const resetTransform: Canvas2DAPI['resetTransform']; - const restore: Canvas2DAPI['restore']; - const rotate: Canvas2DAPI['rotate']; - const save: Canvas2DAPI['save']; - const strokeRect: Canvas2DAPI['strokeRect']; - const globalAlpha: Canvas2DAPI['globalAlpha']; - const globalCompositeOperation: Canvas2DAPI['globalCompositeOperation']; - const filter: Canvas2DAPI['filter']; - const imageSmoothingEnabled: Canvas2DAPI['imageSmoothingEnabled']; - const imageSmoothingQuality: Canvas2DAPI['imageSmoothingQuality']; - const strokeStyle: Canvas2DAPI['strokeStyle']; - const fillStyle: Canvas2DAPI['fillStyle']; - const shadowOffsetX: Canvas2DAPI['shadowOffsetX']; - const shadowOffsetY: Canvas2DAPI['shadowOffsetY']; - const shadowBlur: Canvas2DAPI['shadowBlur']; - const shadowColor: Canvas2DAPI['shadowColor']; - const lineWidth: Canvas2DAPI['lineWidth']; - const lineCap: Canvas2DAPI['lineCap']; - const lineJoin: Canvas2DAPI['lineJoin']; - const miterLimit: Canvas2DAPI['miterLimit']; - const lineDashOffset: Canvas2DAPI['lineDashOffset']; - const font: Canvas2DAPI['font']; - const textAlign: Canvas2DAPI['textAlign']; - const textBaseline: Canvas2DAPI['textBaseline']; - const direction: Canvas2DAPI['direction']; + const clip: Canvas2DAPI['clip'] + const createImageData: Canvas2DAPI['createImageData'] + const createLinearGradient: Canvas2DAPI['createLinearGradient'] + const createPattern: Canvas2DAPI['createPattern'] + const createRadialGradient: Canvas2DAPI['createRadialGradient'] + const drawImage: Canvas2DAPI['drawImage'] + const fill: Canvas2DAPI['fill'] + const fillText: Canvas2DAPI['fillText'] + const getImageData: Canvas2DAPI['getImageData'] + const getLineDash: Canvas2DAPI['getLineDash'] + const getTransform: Canvas2DAPI['getTransform'] + const isPointInPath: Canvas2DAPI['isPointInPath'] + const isPointInStroke: Canvas2DAPI['isPointInStroke'] + const measureText: Canvas2DAPI['measureText'] + const putImageData: Canvas2DAPI['putImageData'] + const scale: Canvas2DAPI['scale'] + const setLineDash: Canvas2DAPI['setLineDash'] + const setTransform: Canvas2DAPI['setTransform'] + const stroke: Canvas2DAPI['stroke'] + const strokeText: Canvas2DAPI['strokeText'] + const transform: Canvas2DAPI['transform'] + const translate: Canvas2DAPI['translate'] + const arc: Canvas2DAPI['arc'] + const arcTo: Canvas2DAPI['arcTo'] + const beginPath: Canvas2DAPI['beginPath'] + const bezierCurveTo: Canvas2DAPI['bezierCurveTo'] + const clearRect: Canvas2DAPI['clearRect'] + const closePath: Canvas2DAPI['closePath'] + const ellipse: Canvas2DAPI['ellipse'] + const fillRect: Canvas2DAPI['fillRect'] + const lineTo: Canvas2DAPI['lineTo'] + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + const moveTo: Canvas2DAPI['moveTo'] + const quadraticCurveTo: Canvas2DAPI['quadraticCurveTo'] + const rect: Canvas2DAPI['rect'] + const resetTransform: Canvas2DAPI['resetTransform'] + const restore: Canvas2DAPI['restore'] + const rotate: Canvas2DAPI['rotate'] + const save: Canvas2DAPI['save'] + const strokeRect: Canvas2DAPI['strokeRect'] + const globalAlpha: Canvas2DAPI['globalAlpha'] + const globalCompositeOperation: Canvas2DAPI['globalCompositeOperation'] + const filter: Canvas2DAPI['filter'] + const imageSmoothingEnabled: Canvas2DAPI['imageSmoothingEnabled'] + const imageSmoothingQuality: Canvas2DAPI['imageSmoothingQuality'] + const strokeStyle: Canvas2DAPI['strokeStyle'] + const fillStyle: Canvas2DAPI['fillStyle'] + const shadowOffsetX: Canvas2DAPI['shadowOffsetX'] + const shadowOffsetY: Canvas2DAPI['shadowOffsetY'] + const shadowBlur: Canvas2DAPI['shadowBlur'] + const shadowColor: Canvas2DAPI['shadowColor'] + const lineWidth: Canvas2DAPI['lineWidth'] + const lineCap: Canvas2DAPI['lineCap'] + const lineJoin: Canvas2DAPI['lineJoin'] + const miterLimit: Canvas2DAPI['miterLimit'] + const lineDashOffset: Canvas2DAPI['lineDashOffset'] + const font: Canvas2DAPI['font'] + const textAlign: Canvas2DAPI['textAlign'] + const textBaseline: Canvas2DAPI['textBaseline'] + const direction: Canvas2DAPI['direction'] } -export { }; +export { } diff --git a/scripts-dts/mathTools.ts b/scripts-dts/mathTools.ts index 10c27a13..6ce315aa 100644 --- a/scripts-dts/mathTools.ts +++ b/scripts-dts/mathTools.ts @@ -1,72 +1,73 @@ -import type * as mathTools from '../src/utils/mathTools'; +import type * as mathTools from '../src/utils/mathTools' declare global { - const abs: typeof Math.abs; - const acos: typeof Math.acos; - const acosh: typeof Math.acosh; - const asin: typeof Math.asin; - const asinh: typeof Math.asinh; - const atan: typeof Math.atan; - const atanh: typeof Math.atanh; - const atan2: typeof Math.atan2; - const ceil: typeof Math.ceil; - const cbrt: typeof Math.cbrt; - const expm1: typeof Math.expm1; - const clz32: typeof Math.clz32; - const cos: typeof Math.cos; - const cosh: typeof Math.cosh; - const exp: typeof Math.exp; - const floor: typeof Math.floor; - const fround: typeof Math.fround; - const hypot: typeof Math.hypot; - const imul: typeof Math.imul; - const log: typeof Math.log; - const log1p: typeof Math.log1p; - const log2: typeof Math.log2; - const log10: typeof Math.log10; - const max: typeof Math.max; - const min: typeof Math.min; - const pow: typeof Math.pow; - const random: typeof Math.random; - const round: typeof Math.round; - const sign: typeof Math.sign; - const sin: typeof Math.sin; - const sinh: typeof Math.sinh; - const sqrt: typeof Math.sqrt; - const tan: typeof Math.tan; - const tanh: typeof Math.tanh; - const trunc: typeof Math.trunc; - const E: typeof Math.E; - const LN10: typeof Math.LN10; - const LN2: typeof Math.LN2; - const LOG10E: typeof Math.LOG10E; - const LOG2E: typeof Math.LOG2E; - const PI: typeof Math.PI; - const SQRT1_2: typeof Math.SQRT1_2; - const SQRT2: typeof Math.SQRT2; + const abs: typeof Math.abs + const acos: typeof Math.acos + const acosh: typeof Math.acosh + const asin: typeof Math.asin + const asinh: typeof Math.asinh + const atan: typeof Math.atan + const atanh: typeof Math.atanh + const atan2: typeof Math.atan2 + const ceil: typeof Math.ceil + const cbrt: typeof Math.cbrt + const expm1: typeof Math.expm1 + const clz32: typeof Math.clz32 + const cos: typeof Math.cos + const cosh: typeof Math.cosh + const exp: typeof Math.exp + const floor: typeof Math.floor + const fround: typeof Math.fround + const hypot: typeof Math.hypot + const imul: typeof Math.imul + const log: typeof Math.log + const log1p: typeof Math.log1p + const log2: typeof Math.log2 + const log10: typeof Math.log10 + const max: typeof Math.max + const min: typeof Math.min + const pow: typeof Math.pow + const random: typeof Math.random + const round: typeof Math.round + const sign: typeof Math.sign + const sin: typeof Math.sin + const sinh: typeof Math.sinh + const sqrt: typeof Math.sqrt + const tan: typeof Math.tan + const tanh: typeof Math.tanh + const trunc: typeof Math.trunc + const E: typeof Math.E + const LN10: typeof Math.LN10 + const LN2: typeof Math.LN2 + const LOG10E: typeof Math.LOG10E + const LOG2E: typeof Math.LOG2E + const PI: typeof Math.PI + const SQRT1_2: typeof Math.SQRT1_2 + const SQRT2: typeof Math.SQRT2 - const PI2: typeof mathTools.PI2; - const GR: typeof mathTools.GR; - const sDiv: typeof mathTools.sDiv; - const arrayMax: typeof mathTools.arrayMax; - const arrayMin: typeof mathTools.arrayMin; - const arraySum: typeof mathTools.arraySum; - const arrayDiff: typeof mathTools.arrayDiff; - const arrayAvg: typeof mathTools.arrayAvg; - const arrayMirror: typeof mathTools.arrayMirror; - const arrayDownsample: typeof mathTools.arrayDownsample; - const arraySmooth: typeof mathTools.arraySmooth; - const deg2rad: typeof mathTools.deg2rad; - const rad2deg: typeof mathTools.rad2deg; - const cap: typeof mathTools.cap; - const between: typeof mathTools.between; - const beatPrct: typeof mathTools.beatPrct; - const beat: typeof mathTools.beat; - // @ts-ignore - const orientation: typeof mathTools.orientation; - const objOrientation: typeof mathTools.objOrientation; - const containBox: typeof mathTools.containBox; - const coverBox: typeof mathTools.coverBox; + const PI2: typeof mathTools.PI2 + const GR: typeof mathTools.GR + const sDiv: typeof mathTools.sDiv + const arrayMax: typeof mathTools.arrayMax + const arrayMin: typeof mathTools.arrayMin + const arraySum: typeof mathTools.arraySum + const arrayDiff: typeof mathTools.arrayDiff + const arrayAvg: typeof mathTools.arrayAvg + const arrayMirror: typeof mathTools.arrayMirror + const arrayDownsample: typeof mathTools.arrayDownsample + const arraySmooth: typeof mathTools.arraySmooth + const deg2rad: typeof mathTools.deg2rad + const rad2deg: typeof mathTools.rad2deg + const cap: typeof mathTools.cap + const between: typeof mathTools.between + const beatPrct: typeof mathTools.beatPrct + const beat: typeof mathTools.beat + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + const orientation: typeof mathTools.orientation + const objOrientation: typeof mathTools.objOrientation + const containBox: typeof mathTools.containBox + const coverBox: typeof mathTools.coverBox } -export { }; +export { } diff --git a/scripts-dts/miscTools.ts b/scripts-dts/miscTools.ts index 15050958..01518471 100644 --- a/scripts-dts/miscTools.ts +++ b/scripts-dts/miscTools.ts @@ -1,20 +1,20 @@ -import type * as miscTools from '../src/utils/miscTools'; +import type * as miscTools from '../src/utils/miscTools' declare global { - const noop: typeof miscTools.noop; - const rgba: typeof miscTools.rgba; - const hsla: typeof miscTools.hsla; - const repeat: typeof miscTools.repeat; - const assetDataURI: typeof miscTools.assetDataURI; - const isFunction: typeof miscTools.isFunction; - const toggled: typeof miscTools.toggled; - const prevToggle: typeof miscTools.prevToggle; - const toggle: typeof miscTools.toggle; - const inOut: typeof miscTools.inOut; - const steps: typeof miscTools.steps; - const prevStepVals: typeof miscTools.prevStepVals; - const stepper: typeof miscTools.stepper; - const merge: typeof miscTools.merge; + const noop: typeof miscTools.noop + const rgba: typeof miscTools.rgba + const hsla: typeof miscTools.hsla + const repeat: typeof miscTools.repeat + const assetDataURI: typeof miscTools.assetDataURI + const isFunction: typeof miscTools.isFunction + const toggled: typeof miscTools.toggled + const prevToggle: typeof miscTools.prevToggle + const toggle: typeof miscTools.toggle + const inOut: typeof miscTools.inOut + const steps: typeof miscTools.steps + const prevStepVals: typeof miscTools.prevStepVals + const stepper: typeof miscTools.stepper + const merge: typeof miscTools.merge } -export { }; +export { } diff --git a/scripts-dts/scriptRunner.ts b/scripts-dts/scriptRunner.ts index bb0cdb89..7c1f9de9 100644 --- a/scripts-dts/scriptRunner.ts +++ b/scripts-dts/scriptRunner.ts @@ -1,8 +1,8 @@ -import type { ReadInterface, Cache } from '../src/utils/Scriptable'; -import type { ScriptLog } from '../src/utils/ScriptRunner'; +import type { ReadInterface, Cache } from '../src/utils/Scriptable' +import type { ScriptLog } from '../src/utils/ScriptRunner' declare global { - const read: ReadInterface; - const cache: Cache; - const scriptLog: ScriptLog; + const read: ReadInterface + const cache: Cache + const scriptLog: ScriptLog } diff --git a/scripts-dts/threejs.ts b/scripts-dts/threejs.ts index 6963c891..388d1a32 100644 --- a/scripts-dts/threejs.ts +++ b/scripts-dts/threejs.ts @@ -1,13 +1,13 @@ // eslint-disable-next-line @typescript-eslint/no-unused-vars -import type * as THREE from 'three'; +import type * as THREE from 'three' -import './scriptRunner'; -import './mathTools'; -import './miscTools'; -import './assetTools'; +import './scriptRunner' +import './mathTools' +import './miscTools' +import './assetTools' declare global { - const THREE: typeof THREE; + const THREE: typeof THREE } -export { }; +export { } diff --git a/scripts-dts/worker.ts b/scripts-dts/worker.ts index 9673b183..9589b8b2 100644 --- a/scripts-dts/worker.ts +++ b/scripts-dts/worker.ts @@ -1,5 +1,5 @@ -import './scriptRunner'; -import './mathTools'; -import './miscTools'; +import './scriptRunner' +import './mathTools' +import './miscTools' -export { }; +export { } diff --git a/src/capture/index.ts b/src/capture/index.ts index ae1d3f70..81ba5d73 100644 --- a/src/capture/index.ts +++ b/src/capture/index.ts @@ -1,77 +1,77 @@ -import { io } from 'socket.io-client'; +import { io } from 'socket.io-client' -import { autoBind, ComEventData } from '../utils/com'; +import { autoBind, type ComEventData } from '../utils/com' -const socket = io(); +const socket = io() // eslint-disable-next-line @typescript-eslint/no-unused-vars const { post, listener } = autoBind({ postMessage: (message: ComEventData) => { - socket.emit('message', message); - }, -}, 'capture-socket', {}); + socket.emit('message', message) + } +}, 'capture-socket', {}) -socket.on('message', (message: ComEventData) => listener({ data: message })); +socket.on('message', (message: ComEventData) => { listener({ data: message }) }) -socket.emit('registercapture'); +socket.emit('registercapture') -const canvasCtx = document.getElementsByTagName('canvas')[0]?.getContext('2d'); +const canvasCtx = document.getElementsByTagName('canvas')[0]?.getContext('2d') -let audioCtx: AudioContext; +let audioCtx: AudioContext const audioConfig = { minDecibels: -180, maxDecibels: 120, smoothingTimeConstant: 0.85, - fftSize: 1024, -}; + fftSize: 1024 +} // eslint-disable-next-line max-len -function drawValues(values: Uint8Array | Float32Array, color:string, transform: (val: number) => number) { - if (!canvasCtx || !values || !values.length) return; +function drawValues (values: Uint8Array | Float32Array, color: string, transform: (val: number) => number) { + if ((canvasCtx == null) || !values || !values.length) return - const { canvas: { width: w } } = canvasCtx; - const wi = (w / values.length); + const { canvas: { width: w } } = canvasCtx + const wi = (w / values.length) - canvasCtx.strokeStyle = color; - canvasCtx.beginPath(); + canvasCtx.strokeStyle = color + canvasCtx.beginPath() values.forEach((val: number, i: number) => { - const vh = transform(val); + const vh = transform(val) if (i === 0) { - canvasCtx.moveTo(0, vh); - return; + canvasCtx.moveTo(0, vh) + return } - canvasCtx.lineTo(wi * i, vh); - }); - canvasCtx.stroke(); + canvasCtx.lineTo(wi * i, vh) + }) + canvasCtx.stroke() } -let analyser:AnalyserNode; -let freqArray: Uint8Array; -let timeDomainArray: Uint8Array; -const timeDomainFloat = new Float32Array(audioConfig.fftSize); -function render() { - if (canvasCtx && analyser) { - analyser.getByteFrequencyData(freqArray); - analyser.getByteTimeDomainData(timeDomainArray); - analyser.getFloatTimeDomainData(timeDomainFloat); +let analyser: AnalyserNode +let freqArray: Uint8Array +let timeDomainArray: Uint8Array +const timeDomainFloat = new Float32Array(audioConfig.fftSize) +function render () { + if ((canvasCtx != null) && analyser) { + analyser.getByteFrequencyData(freqArray) + analyser.getByteTimeDomainData(timeDomainArray) + analyser.getFloatTimeDomainData(timeDomainFloat) // post('audioupdate', { socket.emit('audioupdate', { frequency: Array.from(freqArray), volume: Array.from(timeDomainArray), - volumeFloat: Array.from(timeDomainFloat), - }); + volumeFloat: Array.from(timeDomainFloat) + }) - const { canvas: { width: w, height: h } } = canvasCtx; - const hh = h * 0.5; - canvasCtx.clearRect(0, 0, w, h); + const { canvas: { width: w, height: h } } = canvasCtx + const hh = h * 0.5 + canvasCtx.clearRect(0, 0, w, h) - drawValues(freqArray, 'green', (val: number) => ((val / 255) * h)); - drawValues(timeDomainFloat, 'orange', (val: number) => hh + ((val * hh))); - drawValues(timeDomainArray, 'red', (val: number) => hh + (((val - 127) / hh) * h)); + drawValues(freqArray, 'green', (val: number) => ((val / 255) * h)) + drawValues(timeDomainFloat, 'orange', (val: number) => hh + ((val * hh))) + drawValues(timeDomainArray, 'red', (val: number) => hh + (((val - 127) / hh) * h)) } - requestAnimationFrame(render); + requestAnimationFrame(render) } // function render() { @@ -100,28 +100,28 @@ function render() { navigator.mediaDevices.getUserMedia({ video: false, - audio: true, + audio: true }).then((stream: MediaStream) => { - console.log('getUserMedia success', stream); + console.log('getUserMedia success', stream) - audioCtx = new AudioContext(); - analyser = audioCtx.createAnalyser(); + audioCtx = new AudioContext() + analyser = audioCtx.createAnalyser() try { - analyser.minDecibels = audioConfig.minDecibels; - analyser.maxDecibels = audioConfig.maxDecibels; - analyser.smoothingTimeConstant = audioConfig.smoothingTimeConstant; - analyser.fftSize = audioConfig.fftSize; + analyser.minDecibels = audioConfig.minDecibels + analyser.maxDecibels = audioConfig.maxDecibels + analyser.smoothingTimeConstant = audioConfig.smoothingTimeConstant + analyser.fftSize = audioConfig.fftSize } catch (err) { // eslint-disable-next-line no-console - console.warn(err); + console.warn(err) } - freqArray = new Uint8Array(analyser.frequencyBinCount); - timeDomainArray = new Uint8Array(analyser.frequencyBinCount); + freqArray = new Uint8Array(analyser.frequencyBinCount) + timeDomainArray = new Uint8Array(analyser.frequencyBinCount) - const source = audioCtx.createMediaStreamSource(stream); - source.connect(analyser); - render(); + const source = audioCtx.createMediaStreamSource(stream) + source.connect(analyser) + render() // -------------------------------------------------------------------- @@ -133,4 +133,4 @@ navigator.mediaDevices.getUserMedia({ // source.connect(processor); // processor.connect(audioCtx.destination); // render(); -}).catch((e: any) => console.error(e)); +}).catch((e: any) => { console.error(e) }) diff --git a/src/display/Display.ts b/src/display/Display.ts index f1da4851..33e18b9f 100644 --- a/src/display/Display.ts +++ b/src/display/Display.ts @@ -1,125 +1,124 @@ -import { AppState } from '../types'; +/* eslint-disable @typescript-eslint/ban-ts-comment */ +import { type AppState } from '../types' import { autoBind, - ComActionHandlers, - ComMessageEventListener, - ChannelPost, -} from '../utils/com'; -import type Canvas2DLayer from '../layers/Canvas2D/Canvas2DLayer'; -import type ThreeJSLayer from '../layers/ThreeJS/ThreeJSLayer'; + type ComActionHandlers, + type ComMessageEventListener, + type ChannelPost +} from '../utils/com' +import type Canvas2DLayer from '../layers/Canvas2D/Canvas2DLayer' +import type ThreeJSLayer from '../layers/ThreeJS/ThreeJSLayer' interface OffscreenCanvas extends HTMLCanvasElement { } export interface DisplayOptions { - id?: string; - canvas?: HTMLCanvasElement; + id?: string + canvas?: HTMLCanvasElement } export interface DisplayState extends Omit, 'displays'> { - id: string; - readonly control: boolean; - width: number; - height: number; - layers?: Array; + id: string + readonly control: boolean + width: number + height: number + layers?: Array } -const handlers: ComActionHandlers = {}; +const handlers: ComActionHandlers = {} export default class Display { static ensureCanvas = (id: string = 'canvas'): HTMLCanvasElement => { - let el = document.querySelector(`body>#${id}`) as HTMLCanvasElement; + let el = document.querySelector(`body>#${id}`) as HTMLCanvasElement if (!el) { - el = document.createElement('canvas'); - el.id = id; - document.body.appendChild(el); - document.body.style.margin = '0'; - document.body.style.padding = '0'; - document.body.style.overflow = 'hidden'; + el = document.createElement('canvas') + el.id = id + document.body.appendChild(el) + document.body.style.margin = '0' + document.body.style.padding = '0' + document.body.style.overflow = 'hidden' } - const { style: parentStyle } = el.parentElement as HTMLElement; - parentStyle.position = 'relative'; - parentStyle.background = 'black'; - return el; - }; + const { style: parentStyle } = el.parentElement as HTMLElement + parentStyle.position = 'relative' + parentStyle.background = 'black' + return el + } static checkSupport = () => { - // @ts-ignore - if (typeof OffscreenCanvas === 'undefined') return false; + if (typeof OffscreenCanvas === 'undefined') return false try { - const el = document.createElement('canvas'); - // @ts-ignore - el.transferControlToOffscreen(); + const el = document.createElement('canvas') + el.transferControlToOffscreen() } catch (e) { - return false; + return false } - return true; - }; + return true + } - constructor(options?: DisplayOptions) { + constructor (options?: DisplayOptions) { const { id, - canvas = Display.ensureCanvas(), - } = options || {}; - this.#id = id || `display${(Math.random() * 10000).toFixed()}`; + canvas = Display.ensureCanvas() + } = (options != null) || {} + this.#id = id || `display${(Math.random() * 10000).toFixed()}` - canvas.width = canvas.parentElement?.clientWidth || canvas.width; - canvas.height = canvas.parentElement?.clientHeight || canvas.height; - this.#canvas = canvas; + canvas.width = canvas.parentElement?.clientWidth || canvas.width + canvas.height = canvas.parentElement?.clientHeight || canvas.height + this.#canvas = canvas - this.#worker = new Worker(`/Display.worker.js#${this.#id}`); + this.#worker = new Worker(`/Display.worker.js#${this.#id}`) const { post, - listener, - } = autoBind(this.#worker, `display-${id}-browser`, handlers); - this.#post = post; - this.#listener = listener; - this.#worker.addEventListener('message', this.#listener); - - // @ts-ignore - this.#offscreen = canvas.transferControlToOffscreen(); - // @ts-ignore + listener + } = autoBind(this.#worker, `display-${this.#id}-browser`, handlers) + this.#post = post + this.#listener = listener + this.#worker.addEventListener('message', this.#listener) + + // @ts-expect-error + this.#offscreen = canvas.transferControlToOffscreen() this.#worker.postMessage({ type: 'offscreencanvas', - payload: { canvas: this.#offscreen }, - }, [this.#offscreen]); - requestAnimationFrame(() => this.resize()); + payload: { canvas: this.#offscreen } + // @ts-expect-error + }, [this.#offscreen]) + requestAnimationFrame(() => this.resize()) } - #offscreen: OffscreenCanvas; + #offscreen: OffscreenCanvas - #worker: Worker; + #worker: Worker - #post: ChannelPost; + #post: ChannelPost - #id: string; + #id: string - #canvas: HTMLCanvasElement; + #canvas: HTMLCanvasElement - #listener: ComMessageEventListener; + #listener: ComMessageEventListener - get canvas() { - return this.#canvas; + get canvas () { + return this.#canvas } - get state(): Partial { + get state (): Partial { return { id: this.#id, width: this.#canvas.width, - height: this.#canvas.height, - }; + height: this.#canvas.height + } } - get post(): ChannelPost { - return this.#post; + get post (): ChannelPost { + return this.#post } resize = () => requestAnimationFrame(() => { - const { canvas } = this; + const { canvas } = this this.post('resize', { width: canvas.parentElement?.clientWidth || this.#canvas.width, - height: canvas.parentElement?.clientHeight || this.#canvas.height, - }); - }); + height: canvas.parentElement?.clientHeight || this.#canvas.height + }) + }) } diff --git a/src/display/Display.worker.ts b/src/display/Display.worker.ts index cdff38c8..b390bad1 100644 --- a/src/display/Display.worker.ts +++ b/src/display/Display.worker.ts @@ -1,25 +1,25 @@ /* eslint-env worker */ -import { debounce } from 'lodash'; +import { debounce } from 'lodash' -import VFWorker, { OffscreenCanvas } from './DisplayWorker'; +import VFWorker, { type OffscreenCanvas } from './DisplayWorker' import type { ScriptingData, ScriptInfo, - AppState, -} from '../types'; + AppState +} from '../types' import { - ComActionHandlers, -} from '../utils/com'; -import Canvas2DLayer from '../layers/Canvas2D/Canvas2DLayer'; -import ThreeJSLayer from '../layers/ThreeJS/ThreeJSLayer'; + type ComActionHandlers +} from '../utils/com' +import Canvas2DLayer from '../layers/Canvas2D/Canvas2DLayer' +import ThreeJSLayer from '../layers/ThreeJS/ThreeJSLayer' -type LayerTypes = Canvas2DLayer | ThreeJSLayer; +type LayerTypes = Canvas2DLayer | ThreeJSLayer interface WebWorker extends Worker { - location: Location; + location: Location } // scripting @@ -29,133 +29,133 @@ const data: ScriptingData = { now: 0, deltaNow: 0, frequency: [], - volume: [], -}; + volume: [] +} // eslint-disable-next-line no-restricted-globals -const worker: WebWorker = self as any; +const worker: WebWorker = self as any const read = (key: string, defaultVal?: any) => - (typeof data[key] !== 'undefined' ? data[key] : defaultVal); + (typeof data[key] !== 'undefined' ? data[key] : defaultVal) -const idFromWorkerHash = worker.location.hash.replace('#', ''); -if (!idFromWorkerHash) throw new Error('[worker] worker is not ready'); +const idFromWorkerHash = worker.location.hash.replace('#', '') +if (!idFromWorkerHash) throw new Error('[worker] worker is not ready') const socketHandlers = (vfWorker: VFWorker): ComActionHandlers => ({ scriptchange: async (payload: ScriptInfo & { - script: string; + script: string }) => { const { id, type, role, - script, - } = payload; + script + } = payload if (type === 'worker') { - vfWorker.scriptable[role].code = script; + vfWorker.scriptable[role].code = script if (role === 'setup') { // data = { ...data, ...((await scriptable.execSetup()) || {}) }; - Object.assign(data, (await vfWorker.scriptable.execSetup() || {})); + Object.assign(data, (await vfWorker.scriptable.execSetup() || {})) } } else { - vfWorker.workerCom.post('scriptchange', payload); + vfWorker.workerCom.post('scriptchange', payload) if (type === 'layer') { - const found = vfWorker.findStateLayer(id); - if (found) { - found[role].code = script; + const found = vfWorker.findStateLayer(id) + if (found != null) { + found[role].code = script if (role === 'setup') { - found.execSetup(); + found.execSetup() } } else { - console.error('scriptchange layer not found', id); + console.error('scriptchange layer not found', id) } } } }, updatestate: debounce((update: Partial) => { - const { scriptable, state } = vfWorker; + const { scriptable, state } = vfWorker vfWorker.state = { ...state, ...update, - layers: update + layers: ((update .layers?.map((options) => { - const found = vfWorker.findStateLayer(options.id); - if (found) { - found.active = !!options.active; - return found; + const found = vfWorker.findStateLayer(options.id) + if (found != null) { + found.active = !!options.active + return found } switch (options.type) { case 'canvas2d': case 'canvas': - return new Canvas2DLayer({ ...options, read }); + return new Canvas2DLayer({ ...options, read }) case 'threejs': - return new ThreeJSLayer({ ...options, read }); + return new ThreeJSLayer({ ...options, read }) default: - return null; + return null } }) .filter(Boolean) - .map((layer) => vfWorker.resizeLayer(layer as LayerTypes)) - || state.layers, - }; + .map((layer) => vfWorker.resizeLayer(layer as LayerTypes))) != null) || + state.layers + } if ( - typeof update.worker?.setup !== 'undefined' - && update.worker.setup !== scriptable.setup.code + typeof update.worker?.setup !== 'undefined' && + update.worker.setup !== scriptable.setup.code ) { - scriptable.setup.code = update.worker.setup || scriptable.setup.code; - state.worker.setup = scriptable.setup.code; - scriptable.execSetup(); + scriptable.setup.code = update.worker.setup || scriptable.setup.code + state.worker.setup = scriptable.setup.code + scriptable.execSetup() } if ( - typeof update.worker?.animation !== 'undefined' - && update.worker.animation !== scriptable.animation.code + typeof update.worker?.animation !== 'undefined' && + update.worker.animation !== scriptable.animation.code ) { - scriptable.animation.code = update.worker.animation - || scriptable.animation.code; - state.worker.animation = scriptable.animation.code; + scriptable.animation.code = update.worker.animation || + scriptable.animation.code + state.worker.animation = scriptable.animation.code } }, 32), updatedata: (payload: typeof data) => { - Object.assign(data, payload); + Object.assign(data, payload) // workerCom.post('updatedata', data); - }, -}); + } +}) const messageHandlers = (vfWorker: VFWorker): ComActionHandlers => ({ offscreencanvas: ({ canvas: onscreen }: { canvas: OffscreenCanvas }) => { // eslint-disable-next-line no-param-reassign - vfWorker.onScreenCanvas = onscreen; + vfWorker.onScreenCanvas = onscreen // TODO: use autoBind - vfWorker.registerDisplay(); + vfWorker.registerDisplay() }, resize: ({ width, - height, - }: { width: number; height: number; }) => { - const { canvas, socketCom, state } = vfWorker; + height + }: { width: number, height: number }) => { + const { canvas, socketCom, state } = vfWorker vfWorker.state = { ...state, width: width || state.width, - height: height || state.height, - }; - canvas.width = state.width; - canvas.height = state.height; - state.layers?.forEach((l) => vfWorker.resizeLayer(l)); + height: height || state.height + } + canvas.width = state.width + canvas.height = state.height + state.layers?.forEach((l) => vfWorker.resizeLayer(l)) if (!state.control) { socketCom.post('resizedisplay', { id: idFromWorkerHash, width: state.width, - height: state.height, - }); + height: state.height + }) } - }, -}); + } +}) -const displayWorker = new VFWorker(worker, socketHandlers, messageHandlers); +const displayWorker = new VFWorker(worker, socketHandlers, messageHandlers) // eslint-disable-next-line no-console -console.info('displayWorker', displayWorker); +console.info('displayWorker', displayWorker) diff --git a/src/display/DisplayWorker.test.ts b/src/display/DisplayWorker.test.ts index 193b22f8..ac8f692b 100644 --- a/src/display/DisplayWorker.test.ts +++ b/src/display/DisplayWorker.test.ts @@ -1,3 +1,3 @@ describe('DisplayWorker', () => { - it.todo('is comprehensively tested (haha)'); -}); + it.todo('is comprehensively tested (haha)') +}) diff --git a/src/display/DisplayWorker.ts b/src/display/DisplayWorker.ts index 46cffde9..dfd6a438 100644 --- a/src/display/DisplayWorker.ts +++ b/src/display/DisplayWorker.ts @@ -1,29 +1,29 @@ /* eslint-env worker */ -import { io } from 'socket.io-client'; +import { io } from 'socket.io-client' -import { Socket } from 'dgram'; -import type { ComEventData } from '../utils/com'; -import type { ScriptingData } from '../types'; +import { type Socket } from 'dgram' +import { autoBind } from '../utils/com' +import type { + ComEventData, + ChannelBindings, + ComActionHandlers +} from '../utils/com' +import type { ScriptingData } from '../types' -import type { DisplayState } from './Display'; +import type { DisplayState } from './Display' -import { - autoBind, - ChannelBindings, - ComActionHandlers, -} from '../utils/com'; -import Scriptable, { ScriptableOptions } from '../utils/Scriptable'; -import * as mathTools from '../utils/mathTools'; -import Canvas2DLayer from '../layers/Canvas2D/Canvas2DLayer'; -import ThreeJSLayer from '../layers/ThreeJS/ThreeJSLayer'; -import canvasTools, { Canvas2DAPI } from '../layers/Canvas2D/canvasTools'; +import Scriptable, { type ScriptableOptions } from '../utils/Scriptable' +import * as mathTools from '../utils/mathTools' +import type Canvas2DLayer from '../layers/Canvas2D/Canvas2DLayer' +import type ThreeJSLayer from '../layers/ThreeJS/ThreeJSLayer' +import canvasTools, { type Canvas2DAPI } from '../layers/Canvas2D/canvasTools' export interface OffscreenCanvas extends HTMLCanvasElement { } interface OffscreenCanvasRenderingContext2D extends CanvasRenderingContext2D { } interface WebWorker extends Worker { - location: Location; + location: Location } // scripting @@ -31,42 +31,41 @@ interface WebWorker extends Worker { const defaultStage = { width: 600, height: 400, - autoScale: true, -}; + autoScale: true +} const data: ScriptingData = { iterationCount: 0, now: 0, deltaNow: 0, frequency: [], - volume: [], -}; + volume: [] +} // eslint-disable-next-line no-restricted-globals -const worker: WebWorker = self as any; +const worker: WebWorker = self as any const read = (/* Worker read */ key: string, defaultVal?: any) => - (typeof data[key] !== 'undefined' ? data[key] : defaultVal); -const makeErrorHandler = (type: string) => (event: any) => - console.warn('[worker]', type, event); + (typeof data[key] !== 'undefined' ? data[key] : defaultVal) +const makeErrorHandler = (type: string) => (event: any): any => { console.warn('[worker]', type, event) } const scriptableOptions: ScriptableOptions = { id: 'worker', api: { ...mathTools, read }, read, onCompilationError: makeErrorHandler('compilation'), - onExecutionError: makeErrorHandler('execution'), -}; + onExecutionError: makeErrorHandler('execution') +} -const idFromWorkerHash = worker.location.hash.replace('#', ''); -if (!idFromWorkerHash) throw new Error('[worker] worker is not ready'); +const idFromWorkerHash = worker.location.hash.replace('#', '') +if (!idFromWorkerHash) throw new Error('[worker] worker is not ready') export default class VFWorker { - constructor( + constructor ( workerSelf: WebWorker, socketHandlers: (instance: VFWorker) => ComActionHandlers, - messageHandlers: (instance: VFWorker) => ComActionHandlers, + messageHandlers: (instance: VFWorker) => ComActionHandlers ) { - this.#worker = workerSelf; + this.#worker = workerSelf this.state = { bpm: { count: 120, start: Date.now() }, @@ -79,115 +78,116 @@ export default class VFWorker { stage: { ...defaultStage }, worker: { setup: '', - animation: '', - }, - }; + animation: '' + } + } - this.scriptable = new Scriptable(scriptableOptions); + this.scriptable = new Scriptable(scriptableOptions) + // eslint-disable-next-line @typescript-eslint/ban-ts-comment, @typescript-eslint/prefer-ts-expect-error // @ts-ignore - this.canvas = new OffscreenCanvas(this.state.width, this.state.height); + this.canvas = new OffscreenCanvas(this.state.width, this.state.height) this.#context = this.canvas - .getContext('2d') as OffscreenCanvasRenderingContext2D; + .getContext('2d') as OffscreenCanvasRenderingContext2D - this.#tools = canvasTools(this.#context); + this.#tools = canvasTools(this.#context) - this.#socket = io() as unknown as Socket; + this.#socket = io() as unknown as Socket // eslint-disable-next-line prefer-const this.socketCom = autoBind({ postMessage: (message: any) => { - this.#socket.emit('message', message); - }, - }, `display-${idFromWorkerHash}-socket`, socketHandlers(this)); + this.#socket.emit('message', message) + } + }, `display-${idFromWorkerHash}-socket`, socketHandlers(this)) this.#socket.on('message', - (message: ComEventData) => this.socketCom.listener({ data: message })); + (message: ComEventData) => { this.socketCom.listener({ data: message }) }) this.#socket.on('reconnect', (attempt: number) => { - console.info('[worker] reconnect', attempt); - this.registerDisplay(); - }); + console.info('[worker] reconnect', attempt) + this.registerDisplay() + }) this.workerCom = autoBind(this.#worker, `display-${idFromWorkerHash}-worker`, - messageHandlers(this)); - worker.addEventListener('message', this.workerCom.listener); + messageHandlers(this)) + worker.addEventListener('message', this.workerCom.listener) try { this.scriptable.execSetup() - .then(() => this.render()) - .catch(() => console.error('Cannot run worker initial setup')); + .then(() => { this.render() }) + .catch(() => { console.error('Cannot run worker initial setup') }) } catch (e) { - console.error(e); + console.error(e) } } - #worker: WebWorker; + #worker: WebWorker - #socket: Socket; + #socket: Socket - socketCom: ChannelBindings; + socketCom: ChannelBindings - workerCom: ChannelBindings; + workerCom: ChannelBindings - scriptable: Scriptable; + scriptable: Scriptable - canvas: OffscreenCanvas; + canvas: OffscreenCanvas - onScreenCanvas: OffscreenCanvas | null = null; + onScreenCanvas: OffscreenCanvas | null = null - #context: OffscreenCanvasRenderingContext2D; + #context: OffscreenCanvasRenderingContext2D - #tools: Canvas2DAPI; + #tools: Canvas2DAPI - state: DisplayState; + state: DisplayState - registerDisplay() { - if (!this.onScreenCanvas) return; + registerDisplay () { + if (this.onScreenCanvas == null) return this.#socket.emit('registerdisplay', { id: idFromWorkerHash, width: this.onScreenCanvas.width, - height: this.onScreenCanvas.height, - }); + height: this.onScreenCanvas.height + }) } - resizeLayer(layer: Canvas2DLayer | ThreeJSLayer) { + resizeLayer (layer: Canvas2DLayer | ThreeJSLayer) { // eslint-disable-next-line no-param-reassign - layer.width = this.canvas.width; + layer.width = this.canvas.width // eslint-disable-next-line no-param-reassign - layer.height = this.canvas.height; + layer.height = this.canvas.height - layer.execSetup(); - return layer; + layer.execSetup() + return layer } - findStateLayer(id: string) { - return this.state.layers?.find((layer) => id === layer.id); + findStateLayer (id: string) { + return this.state.layers?.find((layer) => id === layer.id) } renderLayers = () => { - const { canvas } = this; - const context = this.#context; - if (!context) return; - context.clearRect(0, 0, canvas.width, canvas.height); + const { canvas } = this + const context = this.#context + if (!context) return + context.clearRect(0, 0, canvas.width, canvas.height) this.state.layers?.forEach((layer) => { - if (!layer.active) return; - layer.execAnimation(); - this.#tools.pasteContain(layer.canvas as any); - }); - }; + if (!layer.active) return + layer.execAnimation() + this.#tools.pasteContain(layer.canvas as any) + }) + } - render() { - Object.assign(data, this.scriptable.execAnimation() || {}); + render () { + Object.assign(data, this.scriptable.execAnimation() || {}) - if (this.#context && this.onScreenCanvas) { - this.renderLayers(); + if (this.#context && (this.onScreenCanvas != null)) { + this.renderLayers() - this.onScreenCanvas.height = this.canvas.height; - this.onScreenCanvas.width = this.canvas.width; + this.onScreenCanvas.height = this.canvas.height + this.onScreenCanvas.width = this.canvas.width const ctx = this.onScreenCanvas - .getContext('2d') as OffscreenCanvasRenderingContext2D; + .getContext('2d') as OffscreenCanvasRenderingContext2D ctx.drawImage( this.canvas, 0, @@ -197,10 +197,10 @@ export default class VFWorker { 0, 0, this.canvas.width, - this.canvas.height, - ); + this.canvas.height + ) } - requestAnimationFrame(() => this.render()); + requestAnimationFrame(() => { this.render() }) } } diff --git a/src/display/displayState.ts b/src/display/displayState.ts index a440388c..4ef29c04 100644 --- a/src/display/displayState.ts +++ b/src/display/displayState.ts @@ -1,30 +1,30 @@ -import type { Layer } from '../types'; +import type { Layer } from '../types' -export type DisplayState = { +export interface DisplayState { meta: { - displayId: string; - connected: boolean; - socketId?: string; - }; - data: object; + displayId: string + connected: boolean + socketId?: string + } + data: object worker: { - setup: string; - animate: string; - }; - layers: Layer[]; -}; + setup: string + animate: string + } + layers: Layer[] +} const defaultState: DisplayState = { meta: { displayId: `display${(Math.random() * 10000).toFixed()}`, - connected: false, + connected: false }, data: {}, worker: { setup: '', - animate: '', + animate: '' }, - layers: [], -}; + layers: [] +} -export default defaultState; +export default defaultState diff --git a/src/display/index.ts b/src/display/index.ts index 1920b26c..318981ba 100644 --- a/src/display/index.ts +++ b/src/display/index.ts @@ -1,24 +1,24 @@ -import Display from './Display'; +import Display from './Display' if (Display.checkSupport()) { const display = new Display({ - id: window.location.hash.slice(1), - }); + id: window.location.hash.slice(1) + }) window.addEventListener('resize', () => { - display.resize(); - }); + display.resize() + }) window.addEventListener('beforeunload', () => { - display.post('unregister'); - }); + display.post('unregister') + }) // when double clicking on the window its body goes fullscreen window.addEventListener('dblclick', () => { if (document.body.requestFullscreen) { - document.body.requestFullscreen(); + document.body.requestFullscreen() } - }); + }) } else { - document.body.innerHTML = '
Unsupported browser, please use Google Chrome.
'; + document.body.innerHTML = '
Unsupported browser, please use Google Chrome.
' } diff --git a/src/extension/VFExtension.ts b/src/extension/VFExtension.ts index 8b9d5f48..c1a94cd3 100644 --- a/src/extension/VFExtension.ts +++ b/src/extension/VFExtension.ts @@ -1,38 +1,38 @@ -import * as vscode from 'vscode'; +import * as vscode from 'vscode' import { - AnyAction, + type AnyAction // EmptyObject, // Store, -} from 'redux'; +} from 'redux' import { - AppState, - ScriptingData, + type AppState, + type ScriptingData // DisplayBase, // Layer, // StageInfo, -} from '../types'; -import VFPanel from './VFPanel'; -import VFServer from './WebServer'; -import commands from './commands'; -import store from './store'; -import readScripts from './readScripts'; -import readLayerScripts from './readLayerScripts'; -import textDocumentScriptInfo from './textDocumentScriptInfo'; -import readWorkspaceRC from './readWorkspaceRC'; -import configuration from './configuration'; +} from '../types' +import VFPanel from './VFPanel' +import VFServer from './WebServer' +import commands from './commands' +import store from './store' +import readScripts from './readScripts' +import readLayerScripts from './readLayerScripts' +import textDocumentScriptInfo from './textDocumentScriptInfo' +import readWorkspaceRC from './readWorkspaceRC' +import configuration from './configuration' export default class VFExtension { - constructor() { - this.#refreshInterval = null; - this.#store = store; - this.#webServer = new VFServer(() => this.state); + constructor () { + this.#refreshInterval = null + this.#store = store + this.#webServer = new VFServer(() => this.state) } - #refreshInterval: NodeJS.Timer | null; + #refreshInterval: NodeJS.Timer | null - #webServer: VFServer; + #webServer: VFServer - #store: typeof store; + #store: typeof store // #store: Store; #data: ScriptingData = { @@ -41,27 +41,27 @@ export default class VFExtension { now: 0, deltaNow: 0, frequency: [], - volume: [], - }; + volume: [] + } - resetData() { + resetData () { this.#data = { started: 0, iterationCount: 0, now: 0, deltaNow: 0, frequency: [], - volume: [], - }; + volume: [] + } } - #refreshData() { - const { state } = this; - const now = Date.now(); - const started = this.#data.started || now; - const bpm = state.bpm.count || this.#data.bpm || 120; - const timeSinceBPMSet = now - (state.bpm.start || started); - const oneMinute = 60000; + #refreshData () { + const { state } = this + const now = Date.now() + const started = this.#data.started || now + const bpm = state.bpm.count || this.#data.bpm || 120 + const timeSinceBPMSet = now - (state.bpm.start || started) + const oneMinute = 60000 this.#data = { ...this.#data, @@ -70,12 +70,12 @@ export default class VFExtension { started, iterationCount: this.#data.iterationCount + 1, now: now - started, - deltaNow: this.#data.now ? now - this.#data.now : 0, - }; + deltaNow: this.#data.now ? now - this.#data.now : 0 + } - const beatLength = oneMinute / bpm; - this.#data.beatPrct = (timeSinceBPMSet % beatLength) / beatLength; - this.#data.beatNum = Math.floor(this.#data.now / (oneMinute / bpm)); + const beatLength = oneMinute / bpm + this.#data.beatPrct = (timeSinceBPMSet % beatLength) / beatLength + this.#data.beatNum = Math.floor(this.#data.now / (oneMinute / bpm)) // if (this.#data.iterationCount % 100 === 0) { // console.info('[ext] this.#data refreshed', @@ -86,30 +86,30 @@ export default class VFExtension { // this.#data.beatNum); // } - this.#webServer.broadcastData(this.#data); + this.#webServer.broadcastData(this.#data) } - get state() { - return this.#store.getState() as AppState; + get state () { + return this.#store.getState() as AppState } - dispatch(action: AnyAction) { - return this.#store.dispatch(action); + dispatch (action: AnyAction) { + return this.#store.dispatch(action) } - updateState() { - const { state } = this; - this.#webServer.broadcastState(state); - VFPanel.currentPanel?.updateState(state); + updateState () { + const { state } = this + this.#webServer.broadcastState(state) + VFPanel.currentPanel?.updateState(state) } - async propagate() { - console.info('[ext] propagate'); + async propagate () { + console.info('[ext] propagate') try { - const fiharc = await readWorkspaceRC(); + const fiharc = await readWorkspaceRC() - const { state: current } = this; + const { state: current } = this this.dispatch({ type: 'replaceState', payload: { @@ -118,39 +118,39 @@ export default class VFExtension { id: fiharc.id || current.id, layers: await Promise.all(fiharc.layers .map(readLayerScripts('layer'))), - worker: await readScripts('worker', 'worker', 'worker'), - }, - }); + worker: await readScripts('worker', 'worker', 'worker') + } + }) } catch (err) { - console.warn('[ext] fiharc', (err as Error).message); + console.warn('[ext] fiharc', (err as Error).message) } } - makeDisposableStoreListener(): vscode.Disposable { + makeDisposableStoreListener (): vscode.Disposable { const unsubscribe = store.subscribe(() => { - this.updateState(); - }); + this.updateState() + }) return { - dispose: unsubscribe, - }; + dispose: unsubscribe + } } - async activate(context: vscode.ExtensionContext) { + async activate (context: vscode.ExtensionContext) { try { - const openControls = configuration('openControls'); + const openControls = configuration('openControls') if (openControls) { - vscode.commands.executeCommand('visualFiha.openControls'); + vscode.commands.executeCommand('visualFiha.openControls') } - console.info('[ext] start refreshing data'); - this.#refreshInterval = setInterval(() => this.#refreshData(), 8); + console.info('[ext] start refreshing data') + this.#refreshInterval = setInterval(() => { this.#refreshData() }, 8) - VFPanel.currentPanel?.updateDisplays(this.#webServer.displays); + VFPanel.currentPanel?.updateDisplays(this.#webServer.displays) - this.propagate(); + this.propagate() } catch (err) { - const msg = `Could not read fiha.json: "${(err as Error).message}"`; - vscode.window.showWarningMessage(msg); + const msg = `Could not read fiha.json: "${(err as Error).message}"` + vscode.window.showWarningMessage(msg) } context.subscriptions.push( @@ -159,81 +159,79 @@ export default class VFExtension { this.makeDisposableStoreListener(), this.#webServer.onDisplaysChange((displays) => { - const { state } = this; + const { state } = this this.dispatch({ type: 'setStage', payload: { ...state.stage, - ...this.#webServer.displaysMaxSize, - }, - }); + ...this.#webServer.displaysMaxSize + } + }) // this.#webServer.broadcastState(this.#runtimeState); - VFPanel.currentPanel?.updateDisplays(displays); + VFPanel.currentPanel?.updateDisplays(displays) }), this.#webServer.onSocketConnection((socket) => { socket.emit('message', { type: 'updatestate', - payload: this.state, - }); + payload: this.state + }) socket.on('audioupdate', (audio: { - frequency: number[]; - volume: number[]; + frequency: number[] + volume: number[] }) => { this.#data = { ...this.#data, - ...audio, - }; - }); + ...audio + } + }) }), ...Object.keys(commands) .map((name) => { - const fn = commands[name](context, this); + const fn = commands[name](context, this) return vscode.commands - .registerCommand(`visualFiha.${name}`, fn); + .registerCommand(`visualFiha.${name}`, fn) }), vscode.workspace.onDidChangeTextDocument((event) => { // if (!event.contentChanges.length) return; - const { document: doc } = event; + const { document: doc } = event if (doc.isUntitled || doc.isClosed || doc.languageId !== 'javascript') { - return; + return } - const info = textDocumentScriptInfo(doc); - const script = doc.getText(); - this.#webServer.broadcastScript(info, script); + const info = textDocumentScriptInfo(doc) + const script = doc.getText() + this.#webServer.broadcastScript(info, script) - const { state } = this; + const { state } = this const layerIndex = state.layers - .findIndex((layer) => layer.id === info.id); + .findIndex((layer) => layer.id === info.id) if (layerIndex < 0) { // TODO: check info.type - state.worker[info.role] = script; - return; + state.worker[info.role] = script + return } - state.layers[layerIndex][info.role] = script; + state.layers[layerIndex][info.role] = script VFPanel.currentPanel?.updateState({ - layers: state.layers, - }); + layers: state.layers + }) }), vscode.workspace.onDidSaveTextDocument((event) => { - if (!event.fileName.endsWith('fiha.json')) - return; - this.propagate(); - }), - ); + if (!event.fileName.endsWith('fiha.json')) { return } + this.propagate() + }) + ) } - deactivate() { - if (this.#refreshInterval) - clearInterval(this.#refreshInterval); - this.#webServer.deactivate(); + deactivate () { + if (this.#refreshInterval != null) { clearInterval(this.#refreshInterval) } + this.#webServer.deactivate() } } diff --git a/src/extension/VFPanel.ts b/src/extension/VFPanel.ts index 3a78128b..1798a3c1 100644 --- a/src/extension/VFPanel.ts +++ b/src/extension/VFPanel.ts @@ -1,18 +1,18 @@ /* eslint-disable no-underscore-dangle */ -import * as vscode from 'vscode'; -import * as fs from 'fs'; +import * as vscode from 'vscode' +import * as fs from 'fs' -import type { ComEventData } from '../utils/com'; -import { AppState } from '../types'; -import getNonce from './getNonce'; -import getWebviewOptions from './getWebviewOptions'; +import type { ComEventData } from '../utils/com' +import { type AppState } from '../types' +import getNonce from './getNonce' +import getWebviewOptions from './getWebviewOptions' -const isDebugMode = () => process.env.VSCODE_DEBUG_MODE === 'true'; +const isDebugMode = () => process.env.VSCODE_DEBUG_MODE === 'true' -function handleIncomingMessage({ +function handleIncomingMessage ({ type, payload, - meta, + meta }: ComEventData) { switch (type) { case 'setBPM': @@ -20,13 +20,13 @@ function handleIncomingMessage({ case 'toggleLayer': case 'createLayer': case 'removeLayer': - vscode.commands.executeCommand(`visualFiha.${type}`, payload, meta); - break; + vscode.commands.executeCommand(`visualFiha.${type}`, payload, meta) + break case 'alert': - vscode.window.showErrorMessage(payload); - break; + vscode.window.showErrorMessage(payload) + break default: - vscode.window.showErrorMessage(`Unknown command: ${type}`); + vscode.window.showErrorMessage(`Unknown command: ${type}`) } } @@ -37,30 +37,30 @@ export default class VFPanel { /** * Track the currently panel. Only allow a single panel to exist at a time. */ - public static currentPanel: VFPanel | undefined; + public static currentPanel: VFPanel | undefined - public static readonly viewType = 'visualFiha'; + public static readonly viewType = 'visualFiha' - private readonly _panel: vscode.WebviewPanel; + private readonly _panel: vscode.WebviewPanel - private readonly _context: vscode.ExtensionContext; + private readonly _context: vscode.ExtensionContext - private readonly _extensionUri: vscode.Uri; + private readonly _extensionUri: vscode.Uri - private _incomingMessage: vscode.EventEmitter; + private readonly _incomingMessage: vscode.EventEmitter - private _disposables: vscode.Disposable[] = []; + private readonly _disposables: vscode.Disposable[] = [] - public static createOrShow(context: vscode.ExtensionContext) { - const column = vscode.window.activeTextEditor + public static createOrShow (context: vscode.ExtensionContext) { + const column = (vscode.window.activeTextEditor != null) ? vscode.window.activeTextEditor.viewColumn - : undefined; + : undefined // console.info('[VFPanel] createOrShow', vscode.window); // If we already have a panel, show it. - if (VFPanel.currentPanel) { - VFPanel.currentPanel._panel.reveal(column); - return; + if (VFPanel.currentPanel != null) { + VFPanel.currentPanel._panel.reveal(column) + return } // Otherwise, create a new panel. @@ -68,114 +68,114 @@ export default class VFPanel { VFPanel.viewType, 'Visual Fiha', column || vscode.ViewColumn.One, - getWebviewOptions(context.extensionUri), - ); + getWebviewOptions(context.extensionUri) + ) - VFPanel.currentPanel = new VFPanel(panel, context); - VFPanel.currentPanel.onIncomingMessage(handleIncomingMessage); + VFPanel.currentPanel = new VFPanel(panel, context) + VFPanel.currentPanel.onIncomingMessage(handleIncomingMessage) } - public static revive(panel: vscode.WebviewPanel, context: vscode.ExtensionContext) { + public static revive (panel: vscode.WebviewPanel, context: vscode.ExtensionContext) { // console.info('[VFPanel] revive'); - VFPanel.currentPanel = new VFPanel(panel, context); - VFPanel.currentPanel.onIncomingMessage(handleIncomingMessage); + VFPanel.currentPanel = new VFPanel(panel, context) + VFPanel.currentPanel.onIncomingMessage(handleIncomingMessage) } - private constructor(panel: vscode.WebviewPanel, context: vscode.ExtensionContext) { - this._context = context; - this._panel = panel; - this._extensionUri = context.extensionUri; - this._incomingMessage = new vscode.EventEmitter(); + private constructor (panel: vscode.WebviewPanel, context: vscode.ExtensionContext) { + this._context = context + this._panel = panel + this._extensionUri = context.extensionUri + this._incomingMessage = new vscode.EventEmitter() // Set the webview's initial html content - this._update(); + this._update() // In development mode, listen for when the webview JS changes. if (isDebugMode()) { - const jsBundlePath = `${context.extensionPath}/out/webviews/index.js`; - const jsBundleWatcher = fs.watch(jsBundlePath, () => this._update()); + const jsBundlePath = `${context.extensionPath}/out/webviews/index.js` + const jsBundleWatcher = fs.watch(jsBundlePath, () => { this._update() }) this._disposables.push({ - dispose: () => jsBundleWatcher.close(), - }); - const cssPath = `${context.extensionPath}/media/main.css`; - const cssWatcher = fs.watch(cssPath, () => this._update()); + dispose: () => { jsBundleWatcher.close() } + }) + const cssPath = `${context.extensionPath}/media/main.css` + const cssWatcher = fs.watch(cssPath, () => { this._update() }) this._disposables.push({ - dispose: () => cssWatcher.close(), - }); + dispose: () => { cssWatcher.close() } + }) } // Listen for when the panel is disposed // This happens when the user closes the panel or when the panel is closed programmatically - this._panel.onDidDispose(() => this.dispose(), null, this._disposables); + this._panel.onDidDispose(() => { this.dispose() }, null, this._disposables) // Handle messages from the webview this._panel.webview.onDidReceiveMessage( - (evt) => this._incomingMessage.fire(evt), + (evt) => { this._incomingMessage.fire(evt) }, null, - this._disposables, - ); + this._disposables + ) } - public onIncomingMessage(listener: (evt: ComEventData) => void) { - return this._incomingMessage.event(listener);// , null, this._disposables); + public onIncomingMessage (listener: (evt: ComEventData) => void) { + return this._incomingMessage.event(listener)// , null, this._disposables); } - public postMessage(msg: ComEventData) { - this._panel.webview.postMessage(msg); + public postMessage (msg: ComEventData) { + this._panel.webview.postMessage(msg) } - public updateDisplays(displays: object) { - this._panel.webview.postMessage({ type: 'updatedisplays', payload: displays }); + public updateDisplays (displays: object) { + this._panel.webview.postMessage({ type: 'updatedisplays', payload: displays }) } - public updateState(update = {} as Partial) { - this._panel.webview.postMessage({ type: 'updatestate', payload: update }); + public updateState (update = {} as Partial) { + this._panel.webview.postMessage({ type: 'updatestate', payload: update }) } - public updateData(update = {}) { - this._panel.webview.postMessage({ type: 'updatedata', payload: update }); + public updateData (update = {}) { + this._panel.webview.postMessage({ type: 'updatedata', payload: update }) } - public dispose() { - VFPanel.currentPanel = undefined; + public dispose () { + VFPanel.currentPanel = undefined // Clean up our resources - this._panel.dispose(); + this._panel.dispose() - while (this._disposables.length) { - const x = this._disposables.pop(); - if (x) { - x.dispose(); + while (this._disposables.length > 0) { + const x = this._disposables.pop() + if (x != null) { + x.dispose() } } } - private _update() { - const { webview } = this._panel; - this._panel.webview.html = this._getHtmlForWebview(webview); + private _update () { + const { webview } = this._panel + this._panel.webview.html = this._getHtmlForWebview(webview) } - private _getHtmlForWebview(webview: vscode.Webview) { + private _getHtmlForWebview (webview: vscode.Webview) { // Local path to main script run in the webview - const scriptPathOnDisk = vscode.Uri.joinPath(this._extensionUri, 'out', 'webviews', 'index.js'); + const scriptPathOnDisk = vscode.Uri.joinPath(this._extensionUri, 'out', 'webviews', 'index.js') // And the uri we use to load this script in the webview - const scriptUri = webview.asWebviewUri(scriptPathOnDisk); + const scriptUri = webview.asWebviewUri(scriptPathOnDisk).toString() // Local path to css styles - const styleResetPath = vscode.Uri.joinPath(this._extensionUri, 'media', 'reset.css'); - const stylesPathVSCPath = vscode.Uri.joinPath(this._extensionUri, 'media', 'vscode.css'); - const stylesPathMainPath = vscode.Uri.joinPath(this._extensionUri, 'media', 'main.css'); - const iconPath = vscode.Uri.joinPath(this._extensionUri, 'media', 'favicon.png'); + const styleResetPath = vscode.Uri.joinPath(this._extensionUri, 'media', 'reset.css') + const stylesPathVSCPath = vscode.Uri.joinPath(this._extensionUri, 'media', 'vscode.css') + const stylesPathMainPath = vscode.Uri.joinPath(this._extensionUri, 'media', 'main.css') + const iconPath = vscode.Uri.joinPath(this._extensionUri, 'media', 'favicon.png') // Uri to load styles into webview - const stylesResetUri = webview.asWebviewUri(styleResetPath); - const stylesVSCUri = webview.asWebviewUri(stylesPathVSCPath); - const stylesMainUri = webview.asWebviewUri(stylesPathMainPath); - const iconUri = webview.asWebviewUri(iconPath); + const stylesResetUri = webview.asWebviewUri(styleResetPath).toString() + const stylesVSCUri = webview.asWebviewUri(stylesPathVSCPath).toString() + const stylesMainUri = webview.asWebviewUri(stylesPathMainPath).toString() + const iconUri = webview.asWebviewUri(iconPath).toString() // Use a nonce to only allow specific scripts to be run - const nonce = getNonce(); + const nonce = getNonce() return ` @@ -236,6 +236,6 @@ export default class VFPanel { - `; + ` } } diff --git a/src/extension/WebServer.ts b/src/extension/WebServer.ts index 5392749e..6baf0d25 100644 --- a/src/extension/WebServer.ts +++ b/src/extension/WebServer.ts @@ -1,23 +1,23 @@ -import * as vscode from 'vscode'; -import { readFile } from 'fs'; +import * as vscode from 'vscode' +import { readFile } from 'fs' import { createServer, - Server, - IncomingMessage, - ServerResponse, -} from 'http'; -import * as mime from 'mime'; -import { Server as SocketIOServer, Socket } from 'socket.io'; - -import type { ComEventData } from '../utils/com'; -import { AppState, DisplayBase } from '../types'; + type Server, + type IncomingMessage, + type ServerResponse +} from 'http' +import * as mime from 'mime' +import { Server as SocketIOServer, type Socket } from 'socket.io' + +import type { ComEventData } from '../utils/com' +import { type AppState, type DisplayBase } from '../types' // import { ChannelPost } from '../utils/com'; export type ServerDisplay = Omit & { - socket: Socket; -}; + socket: Socket +} -export const indexTemplate = ({ host, port, path }: { host: string; port: number; path: string; }) => ` +export const indexTemplate = ({ host, port, path }: { host: string, port: number, path: string }) => ` Visual Fiha @@ -28,281 +28,282 @@ export const indexTemplate = ({ host, port, path }: { host: string; port: number -`.trim(); +`.trim() export default class VFServer { - constructor(stateGetter: () => AppState, { + constructor (stateGetter: () => AppState, { host = 'localhost', - port = 9999, + port = 9999 }: { - host?: string; - port?: number; + host?: string + port?: number } = {}) { - this.#stateGetter = stateGetter; - this.#host = host || this.#host; - this.#port = port || this.#port; - this.#server = createServer(this.#handleHTTPRequest); + this.#stateGetter = stateGetter + this.#host = host || this.#host + this.#port = port || this.#port + this.#server = createServer(this.#handleHTTPRequest) - this.#io = new SocketIOServer(this.#server); - this.#io.on('connection', this.#handleIOConnection); + this.#io = new SocketIOServer(this.#server) + this.#io.on('connection', this.#handleIOConnection) } - #stateGetter: () => AppState; + #stateGetter: () => AppState - #context: vscode.ExtensionContext | null = null; + #context: vscode.ExtensionContext | null = null - #host = 'localhost'; + #host = 'localhost' - #port = 9999; + #port = 9999 - #server: Server; + #server: Server - #io: SocketIOServer; + #io: SocketIOServer - #displays: { [id: string]: ServerDisplay } = {}; + #displays: Record = {} - #displaysChange = new vscode.EventEmitter(); + #displaysChange = new vscode.EventEmitter() - #socketConnection = new vscode.EventEmitter(); + #socketConnection = new vscode.EventEmitter() #serveExtensionFile = (reqUrl: string, res: ServerResponse) => { - if (!this.#context) throw new Error('WebServer is missing extension context'); + if (this.#context == null) throw new Error('WebServer is missing extension context') const filepath = vscode.Uri - .joinPath(this.#context.extensionUri, reqUrl).fsPath; + .joinPath(this.#context.extensionUri, reqUrl).fsPath readFile(filepath, (err, content) => { - if (err) { - res.statusCode = 500; - res.end(err.message); - return; + if (err != null) { + res.statusCode = 500 + res.end(err.message) + return } - let type = mime.getType(filepath.split('.').pop() || '') || 'text/plain'; - if (filepath.endsWith('.gltf')) type = 'model/gltf+json'; - res.writeHead(200, { 'Content-Type': type }); - res.end(content); - }); - }; + let type = mime.getType(filepath.split('.').pop() || '') || 'text/plain' + if (filepath.endsWith('.gltf')) type = 'model/gltf+json' + res.writeHead(200, { 'Content-Type': type }) + res.end(content) + }) + } #serveAsset = (reqUrl: string, res: ServerResponse) => { - if (!this.#context) throw new Error('WebServer is missing extension context'); + if (this.#context == null) throw new Error('WebServer is missing extension context') - if (!vscode.workspace.workspaceFolders) return; - if (!vscode.workspace.workspaceFolders?.length) return; + if (vscode.workspace.workspaceFolders == null) return + if (!vscode.workspace.workspaceFolders?.length) return - const folder = vscode.workspace.workspaceFolders[0]; + const folder = vscode.workspace.workspaceFolders[0] const filepath = vscode.Uri - .joinPath(folder.uri, 'assets', reqUrl.replace('/assets/', '')).fsPath; + .joinPath(folder.uri, 'assets', reqUrl.replace('/assets/', '')).fsPath readFile(filepath, (err, content) => { - if (err) { - res.statusCode = 500; - res.end(err.message); - return; + if (err != null) { + res.statusCode = 500 + res.end(err.message) + return } - let type = mime.getType(filepath.split('.').pop() || '') || 'text/plain'; - if (filepath.endsWith('.gltf')) type = 'model/gltf+json'; - res.writeHead(200, { 'Content-Type': type }); - res.end(content); - }); - }; + let type = mime.getType(filepath.split('.').pop() || '') || 'text/plain' + if (filepath.endsWith('.gltf')) type = 'model/gltf+json' + res.writeHead(200, { 'Content-Type': type }) + res.end(content) + }) + } #handleHTTPRequest = (req: IncomingMessage, res: ServerResponse) => { try { + const { url = '' } = req if (req.method === 'GET') { if ([ '/display', '/display/', '/capture', - '/capture/', + '/capture/' ].includes(req.url || '')) { - res.writeHead(200, { 'Content-Type': 'text/html' }); + res.writeHead(200, { 'Content-Type': 'text/html' }) res.end(indexTemplate({ host: this.host, port: this.port, path: (req.url || '').endsWith('/') ? req.url as string - : `${req.url || ''}/`, - })); - return; + : `${req.url || ''}/` + })) + return } if ([ '/display/index.js', '/display/index.js.map', '/capture/index.js', - '/capture/index.js.map', + '/capture/index.js.map' ].includes(req.url || '')) { - this.#serveExtensionFile(`out/${req.url}`, res); - return; + this.#serveExtensionFile(`out/${url}`, res) + return } - if (['/Display.worker.js', '/Display.worker.js.map'].includes(req.url || '')) { - this.#serveExtensionFile(`out/display/${req.url}`, res); - return; + if (['/Display.worker.js', '/Display.worker.js.map'].includes(url)) { + this.#serveExtensionFile(`out/display/${url}`, res) + return } if (req.url === '/favicon.png') { - this.#serveExtensionFile('media/favicon.png', res); - return; + this.#serveExtensionFile('media/favicon.png', res) + return } if (req.url === '/reset.css') { - this.#serveExtensionFile('media/reset.css', res); - return; + this.#serveExtensionFile('media/reset.css', res) + return } if (req.url?.startsWith('/assets/')) { - this.#serveAsset(req.url, res); - return; + this.#serveAsset(req.url, res) + return } } - res.writeHead(404, { 'Content-Type': 'application/json' }); + res.writeHead(404, { 'Content-Type': 'application/json' }) res.end(JSON.stringify({ - message: 'Not Found', - })); + message: 'Not Found' + })) } catch (err) { - res.writeHead(500, { 'Content-Type': 'application/json' }); + res.writeHead(500, { 'Content-Type': 'application/json' }) res.end(JSON.stringify({ - message: (err as Error).message, - })); + message: (err as Error).message + })) } - }; + } #resizeDisplay = ({ id, width, - height, + height // stage: displayStage, }: DisplayBase) => { - const display = this.#displays[id]; - if (!display) return; + const display = this.#displays[id] + if (!display) return - display.width = width; - display.height = height; + display.width = width + display.height = height // display.stage = displayStage; - this.#displaysChange.fire(this.displays); - }; + this.#displaysChange.fire(this.displays) + } #registerDisplay = (socket: Socket, display: DisplayBase) => { - this.#displays[display.id] = { ...display, socket }; + this.#displays[display.id] = { ...display, socket } - this.#displaysChange.fire(this.displays); - }; + this.#displaysChange.fire(this.displays) + } #handleIOConnection = (socket: Socket) => { // TODO: register socket, use autoBind socket.on('registerdisplay', (display: DisplayBase) => { - this.#registerDisplay(socket, display); - }); + this.#registerDisplay(socket, display) + }) socket.on('disconnect', () => { const id = Object.keys(this.#displays).find((key) => { - const { socket: displaySocket } = this.#displays[key]; - return displaySocket.id === socket.id; - }); - if (!id) return; - const { [id]: dropped, ...displays } = this.#displays; - this.#displays = displays; - this.#displaysChange.fire(this.displays); - }); + const { socket: displaySocket } = this.#displays[key] + return displaySocket.id === socket.id + }) + if (!id) return + const { [id]: dropped, ...displays } = this.#displays + this.#displays = displays + this.#displaysChange.fire(this.displays) + }) socket.on('unregisterdisplay', ({ id }) => { - const { [id]: dropped, ...displays } = this.#displays; - this.#displays = displays; - this.#displaysChange.fire(this.displays); - }); + const { [id]: dropped, ...displays } = this.#displays + this.#displays = displays + this.#displaysChange.fire(this.displays) + }) // TODO: use autoBind socket.on('message', ({ type, - payload, + payload // meta, }: ComEventData) => { if (type === 'resizedisplay') { - this.#resizeDisplay(payload); + this.#resizeDisplay(payload) } - }); + }) - this.#socketConnection.fire(socket); - }; + this.#socketConnection.fire(socket) + } - get state(): AppState { - return this.#stateGetter(); + get state (): AppState { + return this.#stateGetter() } - get host() { - return this.#host; + get host () { + return this.#host } - get port() { - return this.#port; + get port () { + return this.#port } - get displays(): DisplayBase[] { + get displays (): DisplayBase[] { return Object.keys(this.#displays) .filter((id) => !id.startsWith('control')) .map((id) => { - const { socket, ...display } = this.#displays[id]; - return { ...display, id }; - }); + const { socket, ...display } = this.#displays[id] + return { ...display, id } + }) } - get displaysMaxSize(): { width: number; height: number; } { + get displaysMaxSize (): { width: number, height: number } { const size = { width: 600, - height: 400, - }; + height: 400 + } this.displays.filter((display) => !display.control).forEach((display) => { - size.width = Math.max(display.width || 0, size.width); - size.height = Math.max(display.height || 0, size.height); - }); - return size; + size.width = Math.max(display.width || 0, size.width) + size.height = Math.max(display.height || 0, size.height) + }) + return size } activate = (context: vscode.ExtensionContext): vscode.Disposable => { - console.info('[webServer] activation'); + console.info('[webServer] activation') - this.#context = context; - this.#server.listen(this.#port); + this.#context = context + this.#server.listen(this.#port) return { dispose: () => { - console.info('[webServer] dispose'); - this.deactivate(); - }, - }; - }; - - get onDisplaysChange() { - return this.#displaysChange.event; + console.info('[webServer] dispose') + this.deactivate() + } + } + } + + get onDisplaysChange () { + return this.#displaysChange.event } - get onSocketConnection() { - return this.#socketConnection.event; + get onSocketConnection () { + return this.#socketConnection.event } broadcast = (type: string, payload?: any) => { - this.#io.emit('message', { type, payload }); - }; + this.#io.emit('message', { type, payload }) + } - broadcastScript = (info: { [k: string]: any }, script: string) => { - this.broadcast('scriptchange', { ...info, script }); - }; + broadcastScript = (info: Record, script: string) => { + this.broadcast('scriptchange', { ...info, script }) + } broadcastData = (data: any) => { - this.broadcast('updatedata', data); - }; + this.broadcast('updatedata', data) + } broadcastState = ({ displays, ...data }: Partial) => { - this.broadcast('updatestate', data); - }; + this.broadcast('updatestate', data) + } deactivate = (cb?: (err?: Error) => void) => { - console.info('[webServer] deactivation'); + console.info('[webServer] deactivation') // TODO: disconnect IO clients - this.#server.close(cb); - }; + this.#server.close(cb) + } } diff --git a/src/extension/asyncReadFile.ts b/src/extension/asyncReadFile.ts index 2b771221..27111682 100644 --- a/src/extension/asyncReadFile.ts +++ b/src/extension/asyncReadFile.ts @@ -1,18 +1,18 @@ -import { readFile } from 'fs'; +import { readFile } from 'fs' -export default function asyncReadFile(fsPath: string): Promise { - return new Promise((res, rej) => { +export default async function asyncReadFile (fsPath: string): Promise { + return await new Promise((res, rej) => { try { readFile(fsPath, 'utf8', (err, content) => { - if (err) { - rej(err); - return; + if (err != null) { + rej(err) + return } - res(content); - }); + res(content) + }) } catch (err) { - rej(err); + rej(err) } - }); + }) } diff --git a/src/extension/commands/createLayer.ts b/src/extension/commands/createLayer.ts index 75ed4729..acb65ad0 100644 --- a/src/extension/commands/createLayer.ts +++ b/src/extension/commands/createLayer.ts @@ -1,3 +1,3 @@ -export default function createLayer() { - return () => { }; +export default function createLayer () { + return () => { } } diff --git a/src/extension/commands/index.ts b/src/extension/commands/index.ts index 0f008788..92e8a7f3 100644 --- a/src/extension/commands/index.ts +++ b/src/extension/commands/index.ts @@ -1,17 +1,15 @@ -import type { VFCommand } from '../../types'; -import openControls from './openControls'; -import openEditor from './openEditor'; -import setBPM from './setBPM'; -import createLayer from './createLayer'; -import removeLayer from './removeLayer'; -import toggleLayer from './toggleLayer'; -import setStageSize from './setStageSize'; -import resetData from './resetData'; -import scaffoldProject from './scaffoldProject'; +import type { VFCommand } from '../../types' +import openControls from './openControls' +import openEditor from './openEditor' +import setBPM from './setBPM' +import createLayer from './createLayer' +import removeLayer from './removeLayer' +import toggleLayer from './toggleLayer' +import setStageSize from './setStageSize' +import resetData from './resetData' +import scaffoldProject from './scaffoldProject' -export type Commands = { - [name: string]: VFCommand; -}; +export type Commands = Record const commands: Commands = { openControls, @@ -22,7 +20,7 @@ const commands: Commands = { toggleLayer, setStageSize, resetData, - scaffoldProject, -}; + scaffoldProject +} -export default commands; +export default commands diff --git a/src/extension/commands/openControls.ts b/src/extension/commands/openControls.ts index a3da4b81..5323d10c 100644 --- a/src/extension/commands/openControls.ts +++ b/src/extension/commands/openControls.ts @@ -1,9 +1,9 @@ -import * as vscode from 'vscode'; +import type * as vscode from 'vscode' -import VFPanel from '../VFPanel'; +import VFPanel from '../VFPanel' -export default function openControls(context: vscode.ExtensionContext) { +export default function openControls (context: vscode.ExtensionContext) { return () => { - VFPanel.createOrShow(context); - }; + VFPanel.createOrShow(context) + } } diff --git a/src/extension/commands/openEditor.ts b/src/extension/commands/openEditor.ts index e184b1c8..cca0b2a0 100644 --- a/src/extension/commands/openEditor.ts +++ b/src/extension/commands/openEditor.ts @@ -1,63 +1,63 @@ -import * as vscode from 'vscode'; -import * as fs from 'fs'; +import * as vscode from 'vscode' +import * as fs from 'fs' -import type { ComEventDataMeta } from '../../utils/com'; -import getWorkspaceFolder from '../getWorkspaceFolder'; -import VFPanel from '../VFPanel'; +import type { ComEventDataMeta } from '../../utils/com' +import getWorkspaceFolder from '../getWorkspaceFolder' +import VFPanel from '../VFPanel' export type OpenCommandOptions = string | { - relativePath: string; - viewColumn?: number; - preserveFocus?: boolean; - preview?: boolean; - selection?: vscode.Range; - createIfMissing?: boolean; -}; + relativePath: string + viewColumn?: number + preserveFocus?: boolean + preview?: boolean + selection?: vscode.Range + createIfMissing?: boolean +} -export default function openEditor() { +export default function openEditor () { return (options: OpenCommandOptions, meta?: ComEventDataMeta) => { const { commands: { - executeCommand, + executeCommand }, workspace: { - fs: wfs, + fs: wfs }, window: { - showErrorMessage, - }, - } = vscode; - const { uri } = getWorkspaceFolder(); + showErrorMessage + } + } = vscode + const { uri } = getWorkspaceFolder() const filepath = vscode.Uri.joinPath(uri, typeof options === 'string' ? options - : options.relativePath); + : options.relativePath) const { viewColumn = vscode.ViewColumn.Beside, preserveFocus = true, preview = false, - selection = undefined, + selection = undefined // createIfMissing = false, - } = typeof options === 'string' ? {} : options; + } = typeof options === 'string' ? {} : options const resolve = () => { - if (!meta?.operationId) return; + if (!meta?.operationId) return VFPanel.currentPanel?.postMessage({ type: 'com/reply', meta: { ...meta, originalType: 'open', - processed: Date.now(), - }, - }); - }; + processed: Date.now() + } + }) + } const reject = (error: any) => { - console.warn('[VFPanel] open error', error); + console.warn('[VFPanel] open error', error) if (!meta?.operationId) { - showErrorMessage(`Could not open: ${filepath}`); - return; + showErrorMessage(`Could not open: ${filepath.toString()}`) + return } VFPanel.currentPanel?.postMessage({ @@ -66,25 +66,27 @@ export default function openEditor() { ...meta, error, originalType: 'open', - processed: Date.now(), - }, - }); - }; + processed: Date.now() + } + }) + } - const create = () => new Promise((res, rej) => { - fs.writeFile(filepath.fsPath, '', 'utf8', (err) => { - if (err) return rej(err); - return res(); - }); - }); + const create = async () => { + await new Promise((res, rej) => { + fs.writeFile(filepath.fsPath, '', 'utf8', (err) => { + if (err != null) { rej(err); return } + res() + }) + }) + } const doOpen = () => executeCommand('vscode.open', filepath, { viewColumn, preserveFocus, preview, - selection, - }).then(resolve, reject); + selection + }).then(resolve, reject) wfs.stat(filepath) - .then(doOpen, () => create().then(doOpen).catch(reject)); - }; + .then(doOpen, async () => { await create().then(doOpen).catch(reject) }) + } } diff --git a/src/extension/commands/removeLayer.ts b/src/extension/commands/removeLayer.ts index 32b48f17..cd82de4b 100644 --- a/src/extension/commands/removeLayer.ts +++ b/src/extension/commands/removeLayer.ts @@ -1,3 +1,3 @@ -export default function removeLayer() { - return () => { }; +export default function removeLayer () { + return () => { } } diff --git a/src/extension/commands/resetData.ts b/src/extension/commands/resetData.ts index c4b979a4..64af421a 100644 --- a/src/extension/commands/resetData.ts +++ b/src/extension/commands/resetData.ts @@ -1,8 +1,8 @@ -import * as vscode from 'vscode'; +import * as vscode from 'vscode' -export default function resetData(context: vscode.ExtensionContext, extension: any) { +export default function resetData (context: vscode.ExtensionContext, extension: any) { return () => { - vscode.window.showWarningMessage('Reseting visuals data'); - extension.resetData(); - }; + vscode.window.showWarningMessage('Reseting visuals data') + extension.resetData() + } } diff --git a/src/extension/commands/scaffoldProject.test.ts b/src/extension/commands/scaffoldProject.test.ts index 1c45869d..995cc169 100644 --- a/src/extension/commands/scaffoldProject.test.ts +++ b/src/extension/commands/scaffoldProject.test.ts @@ -5,25 +5,25 @@ // } from './scaffoldProject'; describe('resolveValue', () => { - it.todo('do some stuff with the fs path and homedir'); -}); + it.todo('do some stuff with the fs path and homedir') +}) describe('promptName', () => { - it.todo('prompts user'); -}); + it.todo('prompts user') +}) describe('promptDirectory', () => { - it.todo('prompts user'); -}); + it.todo('prompts user') +}) describe('scaffoldProject', () => { describe('filepath argument', () => { - it.todo('must point to a writeable directory'); - }); + it.todo('must point to a writeable directory') + }) describe('created result', () => { - it.todo('contains a directories structure'); - it.todo('contains type claration files'); - it.todo('contains a fiha.json file'); - }); -}); + it.todo('contains a directories structure') + it.todo('contains type claration files') + it.todo('contains a fiha.json file') + }) +}) diff --git a/src/extension/commands/scaffoldProject.ts b/src/extension/commands/scaffoldProject.ts index ed0e46a1..76924c0a 100644 --- a/src/extension/commands/scaffoldProject.ts +++ b/src/extension/commands/scaffoldProject.ts @@ -1,58 +1,53 @@ -import * as vscode from 'vscode'; -import { stat } from 'fs/promises'; -import { resolve, join } from 'path'; -import { existsSync, outputFileSync, readFileSync, copySync } from 'fs-extra'; -import { homedir } from 'os'; -import configuration from '../configuration'; - -interface OnValue { - (value: T): any; -} +import * as vscode from 'vscode' +import { stat } from 'fs/promises' +import { resolve, join } from 'path' +import { existsSync, outputFileSync, readFileSync, copySync } from 'fs-extra' +import { homedir } from 'os' +import configuration from '../configuration' -interface OnAbort { - (reason?: any): void; -} +type OnValue = (value: T) => any -export function promptName(onValue: OnValue, onAbort?: OnAbort) { +type OnAbort = (reason?: any) => void + +export function promptName (onValue: OnValue, onAbort?: OnAbort) { vscode.window.showInputBox({ title: 'Visual Fiha project name', prompt: 'What name should be used for the project (/^[a-z0-9-_]+$/i) ?', validateInput: (value: string) => { - const exp = /^[a-z0-9-_]+$/i; - const found = value.match(exp); - if (found) return; + const exp = /^[a-z0-9-_]+$/i + const found = value.match(exp) + if (found != null) return - return 'The value contains invalid characters'; - }, + return 'The value contains invalid characters' + } }).then((value?: string) => { if (!value) { if (typeof onAbort === 'function') { - onAbort(new Error('No name provided')); + onAbort(new Error('No name provided')) } - return; + return } - return onValue(value); + return onValue(value) }, (err: any) => { - console.warn('[command] scaffoldProject promptName err', err); - if (typeof onAbort === 'function') onAbort(err); - }); + console.warn('[command] scaffoldProject promptName err', err) + if (typeof onAbort === 'function') onAbort(err) + }) } - -const exp = /^\~\//; -export function resolveValue(value: string) { - if (!value.match(exp)) { - return resolve(value); +const exp = /^~\// +export function resolveValue (value: string) { + if (value.match(exp) == null) { + return resolve(value) } - const hd = homedir(); - const final = join(hd, value.replace(exp, '')); - return final; + const hd = homedir() + const final = join(hd, value.replace(exp, '')) + return final } -export function promptDirectory(onValue: OnValue, onAbort?: OnAbort) { - const configPath = configuration('projectsPath') as string; +export function promptDirectory (onValue: OnValue, onAbort?: OnAbort) { + const configPath = configuration('projectsPath') as string vscode.window.showInputBox({ title: 'Visual Fiha project scaffolding', @@ -60,110 +55,110 @@ export function promptDirectory(onValue: OnValue, onAbort?: OnAbort) { placeHolder: configPath, validateInput: (value: string) => { try { - const resolved = resolveValue(value); - if (existsSync(resolved)) return; + const resolved = resolveValue(value) + if (existsSync(resolved)) return - return `That path "${resolved}" does not exists`; + return `That path "${resolved}" does not exists` } catch (err: unknown) { - return (err as Error).message; + return (err as Error).message } - }, + } }).then((value?: string) => { // esc pressed, abort... if (typeof value === 'undefined') { - if (typeof onAbort === 'function') onAbort(); - return; + if (typeof onAbort === 'function') onAbort() + return } // use default value if (!value) { - onValue(resolveValue(configPath)); - return; + onValue(resolveValue(configPath)) + return } // use value provided - onValue(resolveValue(value)); + onValue(resolveValue(value)) }, (err: any) => { - if (typeof onAbort === 'function') onAbort(err); - }); + if (typeof onAbort === 'function') onAbort(err) + }) } -export default function scaffoldProject(context: vscode.ExtensionContext, { - propagate, +export default function scaffoldProject (context: vscode.ExtensionContext, { + propagate }: { propagate: () => Promise }) { - const demoProjectPath = context.asAbsolutePath('out/demo-project'); + const demoProjectPath = context.asAbsolutePath('out/demo-project') - const [currentWorkspaceFolder] = vscode.workspace.workspaceFolders || []; - const wsUri = currentWorkspaceFolder ? currentWorkspaceFolder.uri : null; + const [currentWorkspaceFolder] = (vscode.workspace.workspaceFolders != null) || [] + const wsUri = currentWorkspaceFolder ? currentWorkspaceFolder.uri : null - function scaffold(projectId: string, projectPath: string) { + function scaffold (projectId: string, projectPath: string) { copySync(demoProjectPath, projectPath, { overwrite: false, - errorOnExist: false, - }); + errorOnExist: false + }) - const demoProjectJsonPath = join(demoProjectPath, 'fiha.json'); - const originalJson = JSON.parse(readFileSync(demoProjectJsonPath, 'utf8')); + const demoProjectJsonPath = join(demoProjectPath, 'fiha.json') + const originalJson = JSON.parse(readFileSync(demoProjectJsonPath, 'utf8')) const content = JSON.stringify({ ...originalJson, - id: projectId, - }, null, 2); - outputFileSync(join(projectPath, 'fiha.json'), content, 'utf8'); + id: projectId + }, null, 2) + outputFileSync(join(projectPath, 'fiha.json'), content, 'utf8') - return projectPath; + return projectPath } - return () => new Promise((res, rej) => { - function onAbort(reason?: any) { + return async () => await new Promise((res, rej) => { + function onAbort (reason?: any) { const err = reason instanceof Error ? reason - : new Error(reason?.message || reason); + : new Error(reason?.message || reason) console.warn( - '[command] project scaffolding aborted', err.stack); - rej(reason); + '[command] project scaffolding aborted', err.stack) + rej(reason) } - function proceed(projectId: string, projectPath: string) { + function proceed (projectId: string, projectPath: string) { try { - scaffold(projectId, projectPath); + scaffold(projectId, projectPath) if (projectPath === wsUri?.fsPath) { - propagate().then(res).catch(rej); - return; + propagate().then(res).catch(rej) + return } - const uri = vscode.Uri.parse(projectPath); + const uri = vscode.Uri.parse(projectPath) vscode.commands.executeCommand('vscode.openFolder', uri, { - forceNewWindow: false, + forceNewWindow: false }) - .then(() => res(projectPath), onAbort); + .then(() => { res(projectPath) }, onAbort) } catch (err: any) { - console.error((err as Error).message); - onAbort(err); + console.error((err as Error).message) + onAbort(err) } } - function promptInfo() { + function promptInfo () { promptName((projectId: string) => { promptDirectory((wantedProjectPath: string) => { - proceed(projectId, join(wantedProjectPath, projectId)); - }, onAbort); - }, onAbort); + proceed(projectId, join(wantedProjectPath, projectId)) + }, onAbort) + }, onAbort) } - if (!wsUri) { - promptInfo(); - return; + if (wsUri == null) { + promptInfo() + return } stat(vscode.Uri.joinPath(wsUri, 'fiha.json').fsPath) .then(() => { - promptInfo(); + promptInfo() }) .catch(() => { promptName((projectId) => { - proceed(projectId, wsUri.fsPath); - }); - }); - }); + proceed(projectId, wsUri.fsPath) + }) + }) + }) } diff --git a/src/extension/commands/setBPM.ts b/src/extension/commands/setBPM.ts index 900e4b59..53644616 100644 --- a/src/extension/commands/setBPM.ts +++ b/src/extension/commands/setBPM.ts @@ -1,9 +1,9 @@ -import type { ComEventDataMeta } from '../../utils/com'; -import store from '../store'; +import type { ComEventDataMeta } from '../../utils/com' +import store from '../store' -export default function setBPM() { +export default function setBPM () { return (newBPM: number, meta: ComEventDataMeta) => { // console.info('[ext] set BPM', newBPM); - store.dispatch({ type: 'setBPM', payload: newBPM, meta }); - }; + store.dispatch({ type: 'setBPM', payload: newBPM, meta }) + } } diff --git a/src/extension/commands/setStageSize.ts b/src/extension/commands/setStageSize.ts index d0f4df93..7489e634 100644 --- a/src/extension/commands/setStageSize.ts +++ b/src/extension/commands/setStageSize.ts @@ -1,8 +1,8 @@ -import type { ComEventDataMeta } from '../../utils/com'; -import store from '../store'; +import type { ComEventDataMeta } from '../../utils/com' +import store from '../store' -export default function setStageSize() { - return (size: { width: number; height: number; }, meta: ComEventDataMeta) => { - store.dispatch({ type: 'setStageSize', payload: size, meta }); - }; +export default function setStageSize () { + return (size: { width: number, height: number }, meta: ComEventDataMeta) => { + store.dispatch({ type: 'setStageSize', payload: size, meta }) + } } diff --git a/src/extension/commands/toggleLayer.ts b/src/extension/commands/toggleLayer.ts index 6eb97184..64155562 100644 --- a/src/extension/commands/toggleLayer.ts +++ b/src/extension/commands/toggleLayer.ts @@ -1,9 +1,9 @@ -import { VFCommand } from '../../types'; +import { type VFCommand } from '../../types' // eslint-disable-next-line @typescript-eslint/no-unused-vars const toggleLayer: VFCommand = function (context, extension) { return (layerId) => { - console.info('[command] toggleLayer', extension, layerId); + console.info('[command] toggleLayer', extension, layerId) // const { state: { layers } } = extension; // const layerIndex = layers.findIndex((layer) => layer.id === layerId); @@ -11,9 +11,9 @@ const toggleLayer: VFCommand = function (context, extension) { extension.dispatch({ type: 'toggleLayer', - payload: layerId, - }); - }; -}; + payload: layerId + }) + } +} -export default toggleLayer; \ No newline at end of file +export default toggleLayer diff --git a/src/extension/configuration.ts b/src/extension/configuration.ts index c49c148b..e9a189c4 100644 --- a/src/extension/configuration.ts +++ b/src/extension/configuration.ts @@ -1,16 +1,16 @@ -import { workspace } from 'vscode'; +import { workspace } from 'vscode' -export default function configuration( +export default function configuration ( key?: string, value?: string | boolean | number | null) { - const conf = workspace.getConfiguration('visualFiha.settings'); + const conf = workspace.getConfiguration('visualFiha.settings') if (!key) { - return conf; + return conf } if (typeof value === 'undefined') { - return conf.get(key); + return conf.get(key) } - return conf.update(key, value); -} \ No newline at end of file + return conf.update(key, value) +} diff --git a/src/extension/extension.ts b/src/extension/extension.ts index 5a354309..683bb958 100644 --- a/src/extension/extension.ts +++ b/src/extension/extension.ts @@ -1,13 +1,13 @@ -import * as vscode from 'vscode'; +import type * as vscode from 'vscode' -import VFExtension from './VFExtension'; +import VFExtension from './VFExtension' -const extension = new VFExtension(); +const extension = new VFExtension() -export function activate(context: vscode.ExtensionContext) { - return extension.activate(context); +export async function activate (context: vscode.ExtensionContext) { + await extension.activate(context) } -export function deactivate() { - return extension.deactivate(); +export function deactivate () { + extension.deactivate() } diff --git a/src/extension/getNonce.ts b/src/extension/getNonce.ts index 71d764bc..9e244ac0 100644 --- a/src/extension/getNonce.ts +++ b/src/extension/getNonce.ts @@ -1,8 +1,8 @@ -export default function getNonce() { - let text = ''; - const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; +export default function getNonce () { + let text = '' + const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' for (let i = 0; i < 32; i += 1) { - text += possible.charAt(Math.floor(Math.random() * possible.length)); + text += possible.charAt(Math.floor(Math.random() * possible.length)) } - return text; + return text } diff --git a/src/extension/getWebviewOptions.ts b/src/extension/getWebviewOptions.ts index eda98087..ef2932a2 100644 --- a/src/extension/getWebviewOptions.ts +++ b/src/extension/getWebviewOptions.ts @@ -1,6 +1,6 @@ -import * as vscode from 'vscode'; +import * as vscode from 'vscode' -export default function getWebviewOptions(extensionUri: vscode.Uri): vscode.WebviewOptions { +export default function getWebviewOptions (extensionUri: vscode.Uri): vscode.WebviewOptions { return { // Enable javascript in the webview enableScripts: true, @@ -8,7 +8,7 @@ export default function getWebviewOptions(extensionUri: vscode.Uri): vscode.Webv // And restrict the webview to only loading content from our extension's `media` directory. localResourceRoots: [ vscode.Uri.joinPath(extensionUri, 'media'), - vscode.Uri.joinPath(extensionUri, 'out'), - ], - }; + vscode.Uri.joinPath(extensionUri, 'out') + ] + } } diff --git a/src/extension/getWorkspaceFolder.ts b/src/extension/getWorkspaceFolder.ts index 91a7682a..b149df4f 100644 --- a/src/extension/getWorkspaceFolder.ts +++ b/src/extension/getWorkspaceFolder.ts @@ -1,17 +1,17 @@ -import * as vscode from 'vscode'; +import * as vscode from 'vscode' -export default function getWorkspaceFolder(folderIndex = 0): vscode.WorkspaceFolder { +export default function getWorkspaceFolder (folderIndex = 0): vscode.WorkspaceFolder { const { - workspaceFolders: folders, - } = vscode.workspace; + workspaceFolders: folders + } = vscode.workspace if (!folders?.length) { - throw new Error('Workspace has no folder'); + throw new Error('Workspace has no folder') } if (!folders[folderIndex]) { - throw new Error(`Workspace has no folder with index ${folderIndex} (${folders.length})`); + throw new Error(`Workspace has no folder with index ${folderIndex} (${folders.length})`) } - return folders[folderIndex]; + return folders[folderIndex] } diff --git a/src/extension/readLayerScripts.ts b/src/extension/readLayerScripts.ts index eddb22cd..020cf0c2 100644 --- a/src/extension/readLayerScripts.ts +++ b/src/extension/readLayerScripts.ts @@ -1,12 +1,12 @@ import { - Layer, - TypeDirectory, -} from '../types'; -import readScripts from './readScripts'; + type Layer, + type TypeDirectory +} from '../types' +import readScripts from './readScripts' -export default function readLayerScripts(type: keyof typeof TypeDirectory) { +export default function readLayerScripts (type: keyof typeof TypeDirectory) { return async (info: Layer): Promise => ({ ...info, - ...(await readScripts(type, info.type, info.id)), - }); + ...(await readScripts(type, info.type, info.id)) + }) } diff --git a/src/extension/readScripts.ts b/src/extension/readScripts.ts index 8168e775..cf87e9c0 100644 --- a/src/extension/readScripts.ts +++ b/src/extension/readScripts.ts @@ -1,23 +1,23 @@ -import { TypeDirectory } from '../types'; -import scriptUri from './scriptUri'; -import asyncReadFile from './asyncReadFile'; +import { type TypeDirectory } from '../types' +import scriptUri from './scriptUri' +import asyncReadFile from './asyncReadFile' -export default async function readScripts(type: keyof typeof TypeDirectory, runnerType: string, id: string) { - const setupFSPath = scriptUri(type, runnerType, id, 'setup').path; - const animationFSPath = scriptUri(type, runnerType, id, 'animation').path; +export default async function readScripts (type: keyof typeof TypeDirectory, runnerType: string, id: string) { + const setupFSPath = scriptUri(type, runnerType, id, 'setup').path + const animationFSPath = scriptUri(type, runnerType, id, 'animation').path - let setup = ''; - let animation = ''; + let setup = '' + let animation = '' try { - setup = await asyncReadFile(setupFSPath); + setup = await asyncReadFile(setupFSPath) } catch (e) { /* */ } try { - animation = await asyncReadFile(animationFSPath); + animation = await asyncReadFile(animationFSPath) } catch (e) { /* */ } return { setup, - animation, - }; + animation + } } diff --git a/src/extension/readWorkspaceRC.test.ts b/src/extension/readWorkspaceRC.test.ts index 75c5f2a8..590618c0 100644 --- a/src/extension/readWorkspaceRC.test.ts +++ b/src/extension/readWorkspaceRC.test.ts @@ -1,6 +1,6 @@ // import readWorkspaceRC from './readWorkspaceRC'; describe('readWorkspaceRC', () => { - it.todo('can read from fiha.json'); - it.todo('can read from package.json'); -}); + it.todo('can read from fiha.json') + it.todo('can read from package.json') +}) diff --git a/src/extension/readWorkspaceRC.ts b/src/extension/readWorkspaceRC.ts index 15c03875..95282be1 100644 --- a/src/extension/readWorkspaceRC.ts +++ b/src/extension/readWorkspaceRC.ts @@ -1,13 +1,13 @@ -import * as vscode from 'vscode'; -import * as JSON5 from 'json5'; -import { FihaRC } from '../types'; -import getWorkspaceFolder from './getWorkspaceFolder'; -import asyncReadFile from './asyncReadFile'; +import * as vscode from 'vscode' +import * as JSON5 from 'json5' +import { type FihaRC } from '../types' +import getWorkspaceFolder from './getWorkspaceFolder' +import asyncReadFile from './asyncReadFile' -export default async function readWorkspaceRC(folderIndex = 0): Promise { - const folder = getWorkspaceFolder(folderIndex); +export default async function readWorkspaceRC (folderIndex = 0): Promise { + const folder = getWorkspaceFolder(folderIndex) - const filepath = vscode.Uri.joinPath(folder.uri, 'fiha.json').fsPath; - const content = await asyncReadFile(filepath); - return JSON5.parse(content); + const filepath = vscode.Uri.joinPath(folder.uri, 'fiha.json').fsPath + const content = await asyncReadFile(filepath) + return await JSON5.parse(content) } diff --git a/src/extension/scriptUri.ts b/src/extension/scriptUri.ts index 0ccf4a29..fa86817a 100644 --- a/src/extension/scriptUri.ts +++ b/src/extension/scriptUri.ts @@ -1,11 +1,11 @@ -import * as vscode from 'vscode'; -import { ScriptRole, TypeDirectory } from '../types'; -import getWorkspaceFolder from './getWorkspaceFolder'; +import * as vscode from 'vscode' +import { type ScriptRole, TypeDirectory } from '../types' +import getWorkspaceFolder from './getWorkspaceFolder' -export default function scriptUri(type: keyof typeof TypeDirectory, runnerType: string, id: string, role: ScriptRole) { - const folder = getWorkspaceFolder(); +export default function scriptUri (type: keyof typeof TypeDirectory, runnerType: string, id: string, role: ScriptRole) { + const folder = getWorkspaceFolder() if (id === 'worker') { - return vscode.Uri.joinPath(folder.uri, `worker/${role}.js`); + return vscode.Uri.joinPath(folder.uri, `worker/${role}.js`) } - return vscode.Uri.joinPath(folder.uri, TypeDirectory[type], runnerType, `${id}-${role}.js`); + return vscode.Uri.joinPath(folder.uri, TypeDirectory[type], runnerType, `${id}-${role}.js`) } diff --git a/src/extension/store.ts b/src/extension/store.ts index 12db0fa6..17841b77 100644 --- a/src/extension/store.ts +++ b/src/extension/store.ts @@ -1,66 +1,66 @@ -import { AnyAction, createStore, combineReducers } from 'redux'; +import { type AnyAction, createStore, combineReducers } from 'redux' import { - AppState, Layer, StageInfo, DisplayServerInfo, DisplayBase, -} from '../types'; + type AppState, type Layer, type StageInfo, type DisplayServerInfo, type DisplayBase +} from '../types' const id = (state: string = '', action: AnyAction) => { - if (action.type !== 'setId') return state; - return action.payload || state; -}; + if (action.type !== 'setId') return state + return action.payload || state +} -const defaultBpmInfo = { count: 120, start: 0 }; +const defaultBpmInfo = { count: 120, start: 0 } const bpm = (state = defaultBpmInfo, action: AnyAction) => { - if (action.type !== 'setBPM') return state; + if (action.type !== 'setBPM') return state return { count: action.payload, - start: Date.now(), - }; -}; + start: Date.now() + } +} -const defaultStageInfo = { width: 600, height: 400, autoScale: true }; +const defaultStageInfo = { width: 600, height: 400, autoScale: true } const stage = (state: StageInfo = defaultStageInfo, action: AnyAction) => { - if (action.type !== 'setStage') return state; - return { ...(state || {}) }; -}; + if (action.type !== 'setStage') return state + return { ...(state || {}) } +} export const server = (state: DisplayServerInfo = { host: 'localhost', - port: 9999, + port: 9999 }, action: AnyAction) => { - if (action.type !== 'setDisplayServer') return state; + if (action.type !== 'setDisplayServer') return state return { ...state, - ...action.payload, - }; -}; + ...action.payload + } +} -const defaultWorkerScripts = { setup: '', animation: '' }; +const defaultWorkerScripts = { setup: '', animation: '' } export const worker = (state = defaultWorkerScripts, action: AnyAction) => { - if (action.type !== 'setWorkerScript') return state; + if (action.type !== 'setWorkerScript') return state return { ...state, - ...action.payload, - }; -}; + ...action.payload + } +} const layers = (state: Layer[] = [], action: AnyAction) => { switch (action.type) { case 'setLayers': - return [...(state || [])]; + return [...(state || [])] case 'toggleLayer': return state.map((layer) => { if (layer.id === action.payload) { - return { ...layer, active: !layer.active }; + return { ...layer, active: !layer.active } } - return layer; - }); + return layer + }) default: - return state; + return state } -}; +} export const reducers = { id, @@ -68,54 +68,54 @@ export const reducers = { stage, server, worker, - layers, -}; + layers +} const topReducer = combineReducers({ ...reducers, displays: (state: DisplayBase[] = [], action: AnyAction) => { - if (action.type !== 'setDisplays') return state; - return [...(state || [])]; - }, -}); + if (action.type !== 'setDisplays') return state + return [...(state || [])] + } +}) -export type CombinedState = Parameters[0]; +export type CombinedState = Parameters[0] const defaultState: CombinedState = { id: null, bpm: { count: 0, start: 0 }, stage: { autoScale: true, height: 600, - width: 800, + width: 800 }, displays: [], layers: [], worker: {}, server: { host: 'localhost', - port: 9999, - }, -}; + port: 9999 + } +} const store = createStore((state = defaultState, action: AnyAction) => { switch (action.type) { case 'replaceState': - return action.payload; + return action.payload default: - return topReducer(state as CombinedState, action); + return topReducer(state as CombinedState, action) } -}); +}) export const messageHandlers = { updatestate: (newState: AppState) => { - const localState = store.getState() as AppState; + const localState = store.getState() as AppState if (localState.id !== newState.id) { - store.dispatch({ type: 'setId', payload: newState.id }); + store.dispatch({ type: 'setId', payload: newState.id }) } if (localState.server !== newState.server) { - store.dispatch({ type: 'setDisplayServer', payload: newState.server }); + store.dispatch({ type: 'setDisplayServer', payload: newState.server }) } - }, -}; + } +} -export default store; +export default store diff --git a/src/extension/textDocumentScriptInfo.ts b/src/extension/textDocumentScriptInfo.ts index 74ae120e..691cce20 100644 --- a/src/extension/textDocumentScriptInfo.ts +++ b/src/extension/textDocumentScriptInfo.ts @@ -1,27 +1,27 @@ -import * as vscode from 'vscode'; +import * as vscode from 'vscode' import { - ScriptInfo, - ScriptType, - ScriptRole, - DirectoryTypes, -} from '../types'; + type ScriptInfo, + type ScriptType, + type ScriptRole, + DirectoryTypes +} from '../types' -export default function textDocumentScriptInfo(doc: vscode.TextDocument): ScriptInfo { - const workspacePath: string = (vscode.workspace.workspaceFolders - && vscode.workspace.workspaceFolders.length - && vscode.workspace.workspaceFolders[0].uri.path) || ''; +export default function textDocumentScriptInfo (doc: vscode.TextDocument): ScriptInfo { + const workspacePath: string = ((vscode.workspace.workspaceFolders != null) && + (vscode.workspace.workspaceFolders.length > 0) && + vscode.workspace.workspaceFolders[0].uri.path) || '' - const relativePath = doc.uri.path.replace(workspacePath, ''); + const relativePath = doc.uri.path.replace(workspacePath, '') const [, directory, , id, role] = ( - relativePath.match(/\/([^/]+)\/([^/]+)\/([^/]+)-(setup|animation)\./) || [] - ) as [any, keyof typeof DirectoryTypes, any, string, ScriptRole]; + (relativePath.match(/\/([^/]+)\/([^/]+)\/([^/]+)-(setup|animation)\./) != null) || [] + ) as [any, keyof typeof DirectoryTypes, any, string, ScriptRole] - if (!directory || !id || !role) throw new Error(`Cannot determine script info for ${doc.uri.path}`); + if (!directory || !id || !role) throw new Error(`Cannot determine script info for ${doc.uri.path}`) return { id: directory === 'worker' ? 'worker' : id, relativePath, path: doc.uri.path, type: DirectoryTypes[directory] as ScriptType, - role, - }; + role + } } diff --git a/src/extension/workspaceFileExists.test.ts b/src/extension/workspaceFileExists.test.ts index bc6ebcde..ad4f6a6b 100644 --- a/src/extension/workspaceFileExists.test.ts +++ b/src/extension/workspaceFileExists.test.ts @@ -1,27 +1,28 @@ -import { dir, DirectoryResult } from 'tmp-promise'; +import { dir, type DirectoryResult } from 'tmp-promise' +// eslint-disable-next-line @typescript-eslint/ban-ts-comment, @typescript-eslint/prefer-ts-expect-error // @ts-ignore -import { __setWorkspace } from 'vscode'; +import { __setWorkspace } from 'vscode' -import workspaceFileExists from './workspaceFileExists'; +import workspaceFileExists from './workspaceFileExists' -let tmpDir: DirectoryResult; +let tmpDir: DirectoryResult beforeAll(async () => { - tmpDir = await dir(); -}); + tmpDir = await dir() +}) afterAll(async () => { - await tmpDir.cleanup(); -}); + await tmpDir.cleanup() +}) describe('workspaceFileExists', () => { it('returns false when the relative path does not exists', async () => { - await expect(workspaceFileExists('whatever')).resolves.toBe(false); - }); - + await expect(workspaceFileExists('whatever')).resolves.toBe(false) + }) + it('returns true when the relative path exists', async () => { __setWorkspace(tmpDir.path, { - worksapceFolders: ['folder-a'], - }); - await expect(workspaceFileExists('whatever')).resolves.toBe(false); - }); -}); \ No newline at end of file + worksapceFolders: ['folder-a'] + }) + await expect(workspaceFileExists('whatever')).resolves.toBe(false) + }) +}) diff --git a/src/extension/workspaceFileExists.ts b/src/extension/workspaceFileExists.ts index ae7919d5..86b6ae70 100644 --- a/src/extension/workspaceFileExists.ts +++ b/src/extension/workspaceFileExists.ts @@ -1,13 +1,15 @@ -import * as vscode from 'vscode'; -import { access } from 'fs'; +import * as vscode from 'vscode' +import { access } from 'fs' -import getWorkspaceFolder from './getWorkspaceFolder'; +import getWorkspaceFolder from './getWorkspaceFolder' -export default async function workspaceFileExists(relativePath: string, folderIndex = 0): Promise { - const folder = await getWorkspaceFolder(folderIndex); - const filepath = vscode.Uri.joinPath(folder.uri, relativePath).fsPath; - return new Promise((res) => access(filepath, (err) => { - if (err) return res(false); - res(true); - })); +export default async function workspaceFileExists (relativePath: string, folderIndex = 0): Promise { + const folder = getWorkspaceFolder(folderIndex) + const filepath = vscode.Uri.joinPath(folder.uri, relativePath).fsPath + return await new Promise((res) => { + access(filepath, (err) => { + if (err != null) { res(false); return } + res(true) + }) + }) } diff --git a/src/layers/Canvas2D/Canvas2DLayer.spec.ts b/src/layers/Canvas2D/Canvas2DLayer.spec.ts index 6b3ac219..faeda64e 100644 --- a/src/layers/Canvas2D/Canvas2DLayer.spec.ts +++ b/src/layers/Canvas2D/Canvas2DLayer.spec.ts @@ -1,94 +1,94 @@ /** * @jest-environment jsdom */ -import Canvas2DLayer, { Canvas2DLayerOptions } from './Canvas2DLayer'; +import Canvas2DLayer, { type Canvas2DLayerOptions } from './Canvas2DLayer' -const setupScript = 'console.info("hello"); return { newData: "set" };'; +const setupScript = 'console.info("hello"); return { newData: "set" };' -let layer: Canvas2DLayer; +let layer: Canvas2DLayer const options = { id: 'layerId', - canvas: document.createElement('canvas'), -} as Canvas2DLayerOptions; + canvas: document.createElement('canvas') +} as Canvas2DLayerOptions const compilationErrorListener = jest.fn((err) => { - console.info(err.builderStr); -}); + console.info(err.builderStr) +}) describe('instanciation', () => { it('takes some options', () => { - layer = new Canvas2DLayer(options); - expect(layer).toBeTruthy(); - expect(layer).toHaveProperty('setup.isAsync', false); - expect(layer).toHaveProperty('setup.version', 2); - expect(layer).toHaveProperty('id', options.id); - }); + layer = new Canvas2DLayer(options) + expect(layer).toBeTruthy() + expect(layer).toHaveProperty('setup.isAsync', false) + expect(layer).toHaveProperty('setup.version', 2) + expect(layer).toHaveProperty('id', options.id) + }) it('throws an error if no id is provided', () => { expect(() => new Canvas2DLayer({ - canvas: document.createElement('canvas'), - } as Canvas2DLayerOptions)).toThrowError(); - }); + canvas: document.createElement('canvas') + } as Canvas2DLayerOptions)).toThrowError() + }) it('has a cache', () => { - expect(layer).toHaveProperty('cache', {}); - }); -}); + expect(layer).toHaveProperty('cache', {}) + }) +}) describe('setup script', () => { it('is empty by default', () => { - expect(layer).toHaveProperty('setup.code', ''); - }); + expect(layer).toHaveProperty('setup.code', '') + }) it('has a version number', () => { - expect(layer).toHaveProperty('setup.version', 2); - }); + expect(layer).toHaveProperty('setup.version', 2) + }) it('can be set', () => { - layer.setup.addEventListener('compilationerror', compilationErrorListener); + layer.setup.addEventListener('compilationerror', compilationErrorListener) expect(() => { - layer.setup.code = setupScript; - }).not.toThrowError(); - expect(compilationErrorListener).not.toHaveBeenCalled(); - expect(layer).toHaveProperty('setup.version', 3); - expect(layer).toHaveProperty('setup.code', setupScript); - }); + layer.setup.code = setupScript + }).not.toThrowError() + expect(compilationErrorListener).not.toHaveBeenCalled() + expect(layer).toHaveProperty('setup.version', 3) + expect(layer).toHaveProperty('setup.code', setupScript) + }) it('always executes asynchronimously', async () => { - layer.setup.code = 'return await (new Promise((res) => res({ newData: "set" })))'; - expect(layer).toHaveProperty('setup.isAsync', true); - const promise = layer.execSetup(); - await expect(promise).resolves.toStrictEqual({ newData: 'set' }); - }); + layer.setup.code = 'return await (new Promise((res) => res({ newData: "set" })))' + expect(layer).toHaveProperty('setup.isAsync', true) + const promise = layer.execSetup() + await expect(promise).resolves.toStrictEqual({ newData: 'set' }) + }) it('can be used to set the scripts cache', () => { - expect(layer).toHaveProperty('cache', { newData: 'set' }); - }); -}); + expect(layer).toHaveProperty('cache', { newData: 'set' }) + }) +}) describe('animation script', () => { it('is empty by default', () => { - expect(layer).toHaveProperty('animation.code', ''); - }); + expect(layer).toHaveProperty('animation.code', '') + }) it('can use the script cache', () => { - const logListener = jest.fn(); - const code = 'cache.added = true; scriptLog("cache", cache);'; - layer.animation.code = code; - layer.animation.addEventListener('log', logListener); - layer.animation.addEventListener('executionerror', (err) => console.info(err)); - expect(layer).toHaveProperty('animation.code', code); - expect(layer.cache).toHaveProperty('newData', 'set'); - expect(layer.execAnimation).not.toThrow(); - expect(layer.cache).toHaveProperty('newData', 'set'); - expect(layer.cache).toHaveProperty('added', true); + const logListener = jest.fn() + const code = 'cache.added = true; scriptLog("cache", cache);' + layer.animation.code = code + layer.animation.addEventListener('log', logListener) + layer.animation.addEventListener('executionerror', (err) => { console.info(err) }) + expect(layer).toHaveProperty('animation.code', code) + expect(layer.cache).toHaveProperty('newData', 'set') + expect(layer.execAnimation).not.toThrow() + expect(layer.cache).toHaveProperty('newData', 'set') + expect(layer.cache).toHaveProperty('added', true) expect(logListener).toHaveBeenCalledWith({ data: [ - ['cache', { newData: 'set', added: true }], + ['cache', { newData: 'set', added: true }] ], - type: 'log', - }); - layer.animation.removeEventListener('log', logListener); - }); -}); + type: 'log' + }) + layer.animation.removeEventListener('log', logListener) + }) +}) diff --git a/src/layers/Canvas2D/Canvas2DLayer.ts b/src/layers/Canvas2D/Canvas2DLayer.ts index c684d809..56088fe8 100644 --- a/src/layers/Canvas2D/Canvas2DLayer.ts +++ b/src/layers/Canvas2D/Canvas2DLayer.ts @@ -1,29 +1,29 @@ -import Layer, { LayerOptions } from '../Layer'; -import * as mathTools from '../../utils/mathTools'; -import miscTools from '../../utils/miscTools'; -import * as assetTools from '../../utils/assetTools'; +import Layer, { type LayerOptions } from '../Layer' +import * as mathTools from '../../utils/mathTools' +import miscTools from '../../utils/miscTools' +import * as assetTools from '../../utils/assetTools' import canvasTools, { - CTX, -} from './canvasTools'; + type CTX +} from './canvasTools' export interface Canvas2DLayerOptions extends LayerOptions { } export default class Canvas2DLayer extends Layer { - constructor(options: Canvas2DLayerOptions) { - super(options); - this.#ctx = this.canvas.getContext('2d') as CTX; + constructor (options: Canvas2DLayerOptions) { + super(options) + this.#ctx = this.canvas.getContext('2d') as CTX this.api = { ...mathTools, ...miscTools, ...assetTools, ...canvasTools(this.#ctx), - ...super.api, - }; + ...super.api + } } - #ctx: CTX; + #ctx: CTX - get context() { - return this.#ctx; + get context () { + return this.#ctx } } diff --git a/src/layers/Canvas2D/canvasTools.ts b/src/layers/Canvas2D/canvasTools.ts index eb1dd7dd..0aee5038 100644 --- a/src/layers/Canvas2D/canvasTools.ts +++ b/src/layers/Canvas2D/canvasTools.ts @@ -1,268 +1,243 @@ +/* eslint-disable @typescript-eslint/indent */ /* eslint-disable @typescript-eslint/naming-convention */ -interface OffscreenCanvas extends HTMLCanvasElement { } -interface OffscreenCanvasRenderingContext2D extends CanvasRenderingContext2D { } - import { PI2, arrayMax, arrayMin, arrayAvg, - sDiv, -} from '../../utils/mathTools'; -import { noop, repeat } from '../../utils/miscTools'; + sDiv +} from '../../utils/mathTools' +import { noop, repeat } from '../../utils/miscTools' + +interface OffscreenCanvas extends HTMLCanvasElement { } +interface OffscreenCanvasRenderingContext2D extends CanvasRenderingContext2D { } interface ImageCopyCoordinates { - sx?: number; - sy?: number; - sw?: number; - sh?: number; - dx?: number; - dy?: number; - dw?: number; - dh?: number; + sx?: number + sy?: number + sw?: number + sh?: number + dx?: number + dy?: number + dw?: number + dh?: number } export interface CTX extends OffscreenCanvasRenderingContext2D { } -export interface width { (divider?: number): number; } -export interface height { (divider?: number): number; } - -export interface vmin { (multiplier: number): number; } -export interface vmax { (multiplier: number): number; } -export interface vh { (multiplier: number): number; } -export interface vw { (multiplier: number): number; } - -export interface textLines { - (lines: string[], opts?: { - x?: number; - y?: number; - lineHeight?: number; - position?: string; - fill?: string | false; - stroke?: string | false; - }): void; -} - -export interface mirror { - (distance?: number, axis?: 'x' | 'y', img?: OffscreenCanvas): void; -} - -export interface mediaType { - (url: string): 'image' | 'video'; -} - -export interface clear { - (): void; -} - -export interface copy { - ( - sx?: number, - sy?: number, - sw?: number, - sh?: number, - dx?: number, - dy?: number, - dw?: number, - dh?: number, - ): OffscreenCanvas; -} - -interface PasteOperation { - ( - src: CanvasImageSource & { - videoWidth?: number; - videoHeight?: number; - }, opts?: ImageCopyCoordinates, - ): void; -} - -export interface pasteImage extends PasteOperation {} - -export interface pasteContain extends PasteOperation {} - -export interface pasteCover extends PasteOperation {} - -export interface fontSize { - (size: number, unit?: (v: number) => number): void; -} -export interface fontFamily { (familyName: string): void; } -export interface fontWeight { (weight: string | number): void; } - -export interface plot { - (opts?: { - data?: any[]; - min?: number; - max?: number; - samples?: number; - floor?: number; - top?: number; - bottom?: number; - left?: number; - right?: number; - legend?: string; - color?: string; - fontSize?: number; - flipped?: boolean; - }): void; -} +export type width = (divider?: number) => number +export type height = (divider?: number) => number + +export type vmin = (multiplier: number) => number +export type vmax = (multiplier: number) => number +export type vh = (multiplier: number) => number +export type vw = (multiplier: number) => number + +export type textLines = (lines: string[], opts?: { + x?: number + y?: number + lineHeight?: number + position?: string + fill?: string | false + stroke?: string | false +}) => void + +export type mirror = (distance?: number, axis?: 'x' | 'y', img?: OffscreenCanvas) => void + +export type mediaType = (url: string) => 'image' | 'video' + +export type clear = () => void + +export type copy = ( + sx?: number, + sy?: number, + sw?: number, + sh?: number, + dx?: number, + dy?: number, + dw?: number, + dh?: number, +) => OffscreenCanvas + +type PasteOperation = ( + src: CanvasImageSource & { + videoWidth?: number + videoHeight?: number + }, opts?: ImageCopyCoordinates, +) => void + +export interface pasteImage extends PasteOperation { } + +export interface pasteContain extends PasteOperation { } + +export interface pasteCover extends PasteOperation { } + +export type fontSize = (size: number, unit?: (v: number) => number) => void +export type fontFamily = (familyName: string) => void +export type fontWeight = (weight: string | number) => void + +export type plot = (opts?: { + data?: any[] + min?: number + max?: number + samples?: number + floor?: number + top?: number + bottom?: number + left?: number + right?: number + legend?: string + color?: string + fontSize?: number + flipped?: boolean +}) => void /** * Draws a circle */ -export interface circle { - (opts?: { - x?: number; - y?: number; - radius?: number; - stroke?: string; - fill?: string; - }): void; -} - -export interface polygon { - (opts?: { - x?: number; - y?: number; - tilt?: number; - sides?: number; - radius?: number; - stroke?: string; - fill?: string; - }): void; -} - -export interface grid { - (rows: number, cols: number, func: (...args: any[]) => void): void; -} - -export interface centeredGrid { - (opts: { - cols?: number, - rows?: number, - dist?: number, - unit?: (v: number) => number, - }, cb: (args: { - x: number; - y: number; - r: number; - c: number; - n: number; - d: number; - }) => void): void; -} - -interface CTXMethod { - (...args: any[]): any; -} +export type circle = (opts?: { + x?: number + y?: number + radius?: number + stroke?: string + fill?: string +}) => void + +export type polygon = (opts?: { + x?: number + y?: number + tilt?: number + sides?: number + radius?: number + stroke?: string + fill?: string +}) => void + +export type grid = (rows: number, cols: number, func: (...args: any[]) => void) => void + +export type centeredGrid = (opts: { + cols?: number + rows?: number + dist?: number + unit?: (v: number) => number +}, cb: (args: { + x: number + y: number + r: number + c: number + n: number + d: number +}) => void) => void + +type CTXMethod = (...args: any[]) => any interface CTXValue { - (): T; - (value: T): T; + (): T + (value: T): T } export interface Canvas2DAPI { - width: width; - height: height; - vw: vw; - vh: vh; - vmin: vmin; - vmax: vmax; - textLines: textLines; - mirror: mirror; - mediaType: mediaType; - clear: clear; - copy: copy; - pasteImage: pasteImage; - pasteContain: pasteContain; - pasteCover: pasteCover; - fontSize: fontSize; - fontFamily: fontFamily; - fontWeight: fontWeight; - plot: plot; - circle: circle; - polygon: polygon; - grid: grid; - centeredGrid: centeredGrid; + width: width + height: height + vw: vw + vh: vh + vmin: vmin + vmax: vmax + textLines: textLines + mirror: mirror + mediaType: mediaType + clear: clear + copy: copy + pasteImage: pasteImage + pasteContain: pasteContain + pasteCover: pasteCover + fontSize: fontSize + fontFamily: fontFamily + fontWeight: fontWeight + plot: plot + circle: circle + polygon: polygon + grid: grid + centeredGrid: centeredGrid // based on OffscreenCanvasRenderingContext2D - clip: CTXMethod; - createImageData: CTXMethod; - createLinearGradient: CTXMethod; - createPattern: CTXMethod; - createRadialGradient: CTXMethod; - drawImage: CTXMethod; - fill: CTXMethod; - fillText: CTXMethod; - getImageData: CTXMethod; - getLineDash: CTXMethod; - getTransform: CTXMethod; - isPointInPath: CTXMethod; - isPointInStroke: CTXMethod; - measureText: CTXMethod; - putImageData: CTXMethod; - scale: CTXMethod; - setLineDash: CTXMethod; - setTransform: CTXMethod; - stroke: CTXMethod; - strokeText: CTXMethod; - transform: CTXMethod; - translate: CTXMethod; - arc: CTXMethod; - arcTo: CTXMethod; - beginPath: CTXMethod; - bezierCurveTo: CTXMethod; - clearRect: CTXMethod; - closePath: CTXMethod; - ellipse: CTXMethod; - fillRect: CTXMethod; - lineTo: CTXMethod; - moveTo: CTXMethod; - quadraticCurveTo: CTXMethod; - rect: CTXMethod; - resetTransform: CTXMethod; - restore: CTXMethod; - rotate: CTXMethod; - save: CTXMethod; - strokeRect: CTXMethod; - - globalAlpha: CTXValue; - globalCompositeOperation: CTXValue; - filter: CTXValue; - imageSmoothingEnabled: CTXValue; - imageSmoothingQuality: CTXValue; - strokeStyle: CTXValue; - fillStyle: CTXValue; - shadowOffsetX: CTXValue; - shadowOffsetY: CTXValue; - shadowBlur: CTXValue; - shadowColor: CTXValue; - lineWidth: CTXValue; - lineCap: CTXValue; - lineJoin: CTXValue; - miterLimit: CTXValue; - lineDashOffset: CTXValue; - font: CTXValue; - textAlign: CTXValue; - textBaseline: CTXValue; - direction: CTXValue; + clip: CTXMethod + createImageData: CTXMethod + createLinearGradient: CTXMethod + createPattern: CTXMethod + createRadialGradient: CTXMethod + drawImage: CTXMethod + fill: CTXMethod + fillText: CTXMethod + getImageData: CTXMethod + getLineDash: CTXMethod + getTransform: CTXMethod + isPointInPath: CTXMethod + isPointInStroke: CTXMethod + measureText: CTXMethod + putImageData: CTXMethod + scale: CTXMethod + setLineDash: CTXMethod + setTransform: CTXMethod + stroke: CTXMethod + strokeText: CTXMethod + transform: CTXMethod + translate: CTXMethod + arc: CTXMethod + arcTo: CTXMethod + beginPath: CTXMethod + bezierCurveTo: CTXMethod + clearRect: CTXMethod + closePath: CTXMethod + ellipse: CTXMethod + fillRect: CTXMethod + lineTo: CTXMethod + moveTo: CTXMethod + quadraticCurveTo: CTXMethod + rect: CTXMethod + resetTransform: CTXMethod + restore: CTXMethod + rotate: CTXMethod + save: CTXMethod + strokeRect: CTXMethod + + globalAlpha: CTXValue + globalCompositeOperation: CTXValue + filter: CTXValue + imageSmoothingEnabled: CTXValue + imageSmoothingQuality: CTXValue + strokeStyle: CTXValue + fillStyle: CTXValue + shadowOffsetX: CTXValue + shadowOffsetY: CTXValue + shadowBlur: CTXValue + shadowColor: CTXValue + lineWidth: CTXValue + lineCap: CTXValue + lineJoin: CTXValue + miterLimit: CTXValue + lineDashOffset: CTXValue + font: CTXValue + textAlign: CTXValue + textBaseline: CTXValue + direction: CTXValue } -export default function canvasTools(ctx: CTX) { - if (!ctx) throw new Error('Missing context for canvasTools'); - const { canvas } = ctx; +export default function canvasTools (ctx: CTX) { + if (!ctx) throw new Error('Missing context for canvasTools') + const { canvas } = ctx - const width = (div = 1) => canvas.width * (1 / div); + const width = (div = 1) => canvas.width * (1 / div) - const height = (div = 1) => canvas.height * (1 / div); + const height = (div = 1) => canvas.height * (1 / div) - const vw = (count = 1) => canvas.width * 0.01 * count; + const vw = (count = 1) => canvas.width * 0.01 * count - const vh = (count = 1) => canvas.height * 0.01 * count; + const vh = (count = 1) => canvas.height * 0.01 * count - const vmin = (count = 1) => (Math.min(canvas.width, canvas.height) * 0.01 * count); + const vmin = (count = 1) => (Math.min(canvas.width, canvas.height) * 0.01 * count) - const vmax = (count = 1) => (Math.max(canvas.width, canvas.height) * 0.01 * count); + const vmax = (count = 1) => (Math.max(canvas.width, canvas.height) * 0.01 * count) const textLines: textLines = (lines = [], opts = {}) => { const { @@ -271,124 +246,124 @@ export default function canvasTools(ctx: CTX) { position = 'center', fill = 'white', stroke = false, - lineHeight = 1.618, - } = opts; - const lh = (parseInt(ctx.font, 10) || 20) * lineHeight; - const linesHeight = lines.length * lh; - let top = y - (linesHeight * 0.5); - ctx.textBaseline = 'middle'; - ctx.textAlign = 'center'; + lineHeight = 1.618 + } = opts + const lh = (parseInt(ctx.font, 10) || 20) * lineHeight + const linesHeight = lines.length * lh + let top = y - (linesHeight * 0.5) + ctx.textBaseline = 'middle' + ctx.textAlign = 'center' switch (position) { case 'top': - top = y; - break; + top = y + break case 'bottom': - top = y - linesHeight; - break; + top = y - linesHeight + break case 'left': - ctx.textAlign = 'left'; - break; + ctx.textAlign = 'left' + break case 'right': - ctx.textAlign = 'right'; - break; + ctx.textAlign = 'right' + break case 'top-left': - top = y; - ctx.textAlign = 'left'; - break; + top = y + ctx.textAlign = 'left' + break case 'top-right': - top = y; - ctx.textAlign = 'right'; - break; + top = y + ctx.textAlign = 'right' + break case 'bottom-left': - top = y - linesHeight; - ctx.textAlign = 'left'; - break; + top = y - linesHeight + ctx.textAlign = 'left' + break case 'bottom-right': - top = y - linesHeight; - ctx.textAlign = 'right'; - break; + top = y - linesHeight + ctx.textAlign = 'right' + break default: } // if (stroke && stroke !== true) ctx.strokeStyle = stroke; // if (fill && fill !== true) ctx.fillStyle = fill; - if (stroke) ctx.strokeStyle = stroke; - if (fill) ctx.fillStyle = fill; + if (stroke) ctx.strokeStyle = stroke + if (fill) ctx.fillStyle = fill - let line; - let h; + let line + let h for (let l = 0; l < lines.length; l += 1) { - line = lines[l]; - h = (lh * (l + 0.5)); - if (stroke) ctx.strokeText(line, x, top + h); - if (fill) ctx.fillText(line, x, top + h); + line = lines[l] + h = (lh * (l + 0.5)) + if (stroke) ctx.strokeText(line, x, top + h) + if (fill) ctx.fillText(line, x, top + h) } - }; + } const mirror: mirror = (distance = 0.5, axis = 'x', img = canvas as OffscreenCanvas) => { - let sx; - let sy; - let sw; - let sh; - let dx; - let dy; - let dw; - let dh; - let scaleX; - let translateX; - let translateY; + let sx + let sy + let sw + let sh + let dx + let dy + let dw + let dh + let scaleX + let translateX + let translateY if (axis === 'y') { - sx = img.width * distance; - sy = 0; - sw = img.width * 0.5; - sh = img.height; - dx = 0; - dy = 0; - dw = canvas.width * 0.5; - dh = canvas.height; - scaleX = -1; - translateX = 0 - canvas.width; - translateY = 0; + sx = img.width * distance + sy = 0 + sw = img.width * 0.5 + sh = img.height + dx = 0 + dy = 0 + dw = canvas.width * 0.5 + dh = canvas.height + scaleX = -1 + translateX = 0 - canvas.width + translateY = 0 } else { - sx = 0; - sy = img.height * distance; - sw = img.width; - sh = img.height * 0.5; - dx = 0; - dy = 0; - dw = canvas.width; - dh = canvas.height * 0.5; - scaleX = 1; - translateX = 0; - translateY = 0 - canvas.height; + sx = 0 + sy = img.height * distance + sw = img.width + sh = img.height * 0.5 + dx = 0 + dy = 0 + dw = canvas.width + dh = canvas.height * 0.5 + scaleX = 1 + translateX = 0 + translateY = 0 - canvas.height } - ctx.save(); + ctx.save() - ctx.scale(scaleX, scaleX * -1); - ctx.translate(translateX, translateY); + ctx.scale(scaleX, scaleX * -1) + ctx.translate(translateX, translateY) - ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh); - ctx.restore(); + ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh) + ctx.restore() - ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh); - }; + ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh) + } - const mediaType: mediaType = (url) => (/\.(mp4|webm)$/.test(url) ? 'video' : 'image'); + const mediaType: mediaType = (url) => (/\.(mp4|webm)$/.test(url) ? 'video' : 'image') const clear: clear = () => { - ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.beginPath(); - }; + ctx.clearRect(0, 0, canvas.width, canvas.height) + ctx.beginPath() + } const copy: copy = ( sx = 0, @@ -398,17 +373,18 @@ export default function canvasTools(ctx: CTX) { dx = 0, dy = 0, dw = canvas.width, - dh = canvas.height, + dh = canvas.height ) => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment, @typescript-eslint/prefer-ts-expect-error // @ts-ignore - const ofc = new OffscreenCanvas(canvas.width, canvas.height) as HTMLCanvasElement; - ofc.getContext('2d')?.drawImage(canvas, sx, sy, sw, sh, dx, dy, dw, dh); - return ofc; - }; + const ofc = new OffscreenCanvas(canvas.width, canvas.height) as HTMLCanvasElement + ofc.getContext('2d')?.drawImage(canvas, sx, sy, sw, sh, dx, dy, dw, dh) + return ofc + } const pasteImage: pasteImage = (src, opts = {}) => { - const w = (src.width || src.videoWidth || canvas.width || 0) as number; - const h = (src.height || src.videoHeight || canvas.height || 0) as number; + const w = (src.width || src.videoWidth || canvas.width || 0) as number + const h = (src.height || src.videoHeight || canvas.height || 0) as number const { sx = 0, sy = 0, @@ -417,19 +393,19 @@ export default function canvasTools(ctx: CTX) { dx = (canvas.width - w) * 0.5, dy = (canvas.height - h) * 0.5, dw = w, - dh = h, - } = opts; - ctx.drawImage(src, sx, sy, sw, sh, dx, dy, dw, dh); - }; + dh = h + } = opts + ctx.drawImage(src, sx, sy, sw, sh, dx, dy, dw, dh) + } const pasteContain: pasteContain = (src, opts = {}) => { - const w = (src.width || src.videoWidth || canvas.width || 0) as number; - const h = (src.height || src.videoHeight || canvas.height || 0) as number; - const wp = canvas.width / w; - const hp = canvas.height / h; - const p = Math.abs(wp) < Math.abs(hp) ? wp : hp; - const ddw = p * w; - const ddh = p * h; + const w = (src.width || src.videoWidth || canvas.width || 0) as number + const h = (src.height || src.videoHeight || canvas.height || 0) as number + const wp = canvas.width / w + const hp = canvas.height / h + const p = Math.abs(wp) < Math.abs(hp) ? wp : hp + const ddw = p * w + const ddh = p * h const { sx = 0, sy = 0, @@ -438,19 +414,19 @@ export default function canvasTools(ctx: CTX) { dx = (canvas.width - ddw) * 0.5, dy = (canvas.height - ddh) * 0.5, dw = ddw, - dh = ddh, - } = opts; - ctx.drawImage(src, sx, sy, sw, sh, dx, dy, dw, dh); - }; + dh = ddh + } = opts + ctx.drawImage(src, sx, sy, sw, sh, dx, dy, dw, dh) + } const pasteCover: pasteCover = (src, opts = {}) => { - const w = (src.width || src.videoWidth || canvas.width || 0) as number; - const h = (src.height || src.videoHeight || canvas.height || 0) as number; - const wp = canvas.width / w; - const hp = canvas.height / h; - const p = Math.abs(wp) > Math.abs(hp) ? wp : hp; - const ddw = p * w; - const ddh = p * h; + const w = (src.width || src.videoWidth || canvas.width || 0) as number + const h = (src.height || src.videoHeight || canvas.height || 0) as number + const wp = canvas.width / w + const hp = canvas.height / h + const p = Math.abs(wp) > Math.abs(hp) ? wp : hp + const ddw = p * w + const ddh = p * h const { sx = 0, sy = 0, @@ -459,37 +435,37 @@ export default function canvasTools(ctx: CTX) { dx = (canvas.width - ddw) * 0.5, dy = (canvas.height - ddh) * 0.5, dw = ddw, - dh = ddh, - } = opts; - ctx.drawImage(src, sx, sy, sw, sh, dx, dy, dw, dh); - }; + dh = ddh + } = opts + ctx.drawImage(src, sx, sy, sw, sh, dx, dy, dw, dh) + } const fontSize: fontSize = (size: number, unit = vmin) => { - const parts = ctx.font.split(' '); + const parts = ctx.font.split(' ') if (parts.length === 2) { - ctx.font = `normal ${unit(size)}px ${parts[1]}`; + ctx.font = `normal ${unit(size)}px ${parts[1]}` } else { - ctx.font = `${parts[0]} ${unit(size)}px ${parts[2]}`; + ctx.font = `${parts[0]} ${unit(size)}px ${parts[2]}` } - }; + } const fontFamily: fontFamily = (val = 'sans-serif') => { - const parts = ctx.font.split(' '); + const parts = ctx.font.split(' ') if (parts.length === 2) { - ctx.font = `normal ${parts[0]} ${val}`; + ctx.font = `normal ${parts[0]} ${val}` } else { - ctx.font = `${parts[0]} ${parts[1]} ${val}`; + ctx.font = `${parts[0]} ${parts[1]} ${val}` } - }; + } const fontWeight: fontWeight = (weight = 'normal') => { - const parts = ctx.font.split(' '); + const parts = ctx.font.split(' ') if (parts.length === 2) { - ctx.font = `${weight} ${parts[0]} ${parts[1]}`; + ctx.font = `${weight} ${parts[0]} ${parts[1]}` } else { - ctx.font = `${weight} ${parts[1]} ${parts[2]}`; + ctx.font = `${weight} ${parts[1]} ${parts[2]}` } - }; + } const plot: plot = ({ data = [], @@ -504,129 +480,129 @@ export default function canvasTools(ctx: CTX) { legend = 'top-left', color = '', fontSize: fs = vmin(3), - flipped = false, + flipped = false } = {}) => { - const pWidth = right - left; - const pHeight = bottom - top; - const diff = Math.abs(min - max); - const w = sDiv(pWidth, samples - 1); - const h = sDiv(pHeight, diff); + const pWidth = right - left + const pHeight = bottom - top + const diff = Math.abs(min - max) + const w = sDiv(pWidth, samples - 1) + const h = sDiv(pHeight, diff) if (color) { - ctx.strokeStyle = color; + ctx.strokeStyle = color } - ctx.setLineDash([3, 5]); - ctx.beginPath(); - ctx.moveTo(left, bottom - (h * (floor - min))); - ctx.lineTo(right, bottom - (h * (floor - min))); - ctx.stroke(); - ctx.closePath(); - ctx.setLineDash([]); + ctx.setLineDash([3, 5]) + ctx.beginPath() + ctx.moveTo(left, bottom - (h * (floor - min))) + ctx.lineTo(right, bottom - (h * (floor - min))) + ctx.stroke() + ctx.closePath() + ctx.setLineDash([]) - ctx.beginPath(); + ctx.beginPath() - let val; - let py; - let px; + let val + let py + let px for (let v = 0; v < data.length; v += 1) { - val = data[v]; - py = top + (pHeight - (h * (val - min))); - px = flipped ? left + (pWidth - (w * v)) : (left + (w * v)); + val = data[v] + py = top + (pHeight - (h * (val - min))) + px = flipped ? left + (pWidth - (w * v)) : (left + (w * v)) if (v) { - ctx.lineTo(px, py); + ctx.lineTo(px, py) } else { - ctx.moveTo(px, py); + ctx.moveTo(px, py) } } - ctx.stroke(); - ctx.closePath(); + ctx.stroke() + ctx.closePath() - if (!legend) return; - let ptop: number; - let pleft: number; + if (!legend) return + let ptop: number + let pleft: number switch (legend) { case 'top': - ptop = top; - pleft = left + (pWidth * 0.5); - break; + ptop = top + pleft = left + (pWidth * 0.5) + break case 'bottom': - ptop = bottom; - pleft = left + (pWidth * 0.5); - break; + ptop = bottom + pleft = left + (pWidth * 0.5) + break case 'left': - ptop = top + (pHeight * 0.5); - pleft = left; - break; + ptop = top + (pHeight * 0.5) + pleft = left + break case 'right': - ptop = top + (pHeight * 0.5); - pleft = right; - break; + ptop = top + (pHeight * 0.5) + pleft = right + break case 'top-left': - ptop = top; - pleft = right; - break; + ptop = top + pleft = right + break case 'top-right': - ptop = top; - pleft = left; - break; + ptop = top + pleft = left + break case 'bottom-left': - ptop = bottom; - pleft = right; - break; + ptop = bottom + pleft = right + break case 'bottom-right': - ptop = bottom; - pleft = left; - break; + ptop = bottom + pleft = left + break default: - ptop = top + (pHeight * 0.5); - pleft = left + (pWidth * 0.5); + ptop = top + (pHeight * 0.5) + pleft = left + (pWidth * 0.5) } - const originalFont = ctx.font; - ctx.font = `${fs}px monospace`; + const originalFont = ctx.font + ctx.font = `${fs}px monospace` textLines( [ `max: ${max.toFixed(3).padStart(7, ' ')}`, `avg: ${arrayAvg(data).toFixed(3).padStart(7, ' ')}`, - `min: ${min.toFixed(3).padStart(7, ' ')}`, + `min: ${min.toFixed(3).padStart(7, ' ')}` ], { x: pleft, y: ptop, position: legend, fill: color || 'white', - stroke: 'black', - }, - ); - ctx.font = originalFont; - }; + stroke: 'black' + } + ) + ctx.font = originalFont + } const circle: circle = ({ x = width(), y = height(), radius = 10, stroke = '', - fill = '', + fill = '' } = {}) => { - if (stroke) ctx.strokeStyle = stroke; - if (fill) ctx.fillStyle = fill; + if (stroke) ctx.strokeStyle = stroke + if (fill) ctx.fillStyle = fill - ctx.beginPath(); - ctx.arc(x, y, radius, 0, PI2); - ctx.closePath(); + ctx.beginPath() + ctx.arc(x, y, radius, 0, PI2) + ctx.closePath() - if (stroke) ctx.stroke(); - if (fill) ctx.fill(); - }; + if (stroke) ctx.stroke() + if (fill) ctx.fill() + } const polygon: polygon = ({ x = width(), @@ -635,64 +611,64 @@ export default function canvasTools(ctx: CTX) { radius = 10, tilt = 0, stroke = '', - fill = '', + fill = '' } = {}) => { - let px; - let py; - const a = PI2 * (1 / sides); - if (stroke) ctx.strokeStyle = stroke; - if (fill) ctx.fillStyle = fill; + let px + let py + const a = PI2 * (1 / sides) + if (stroke) ctx.strokeStyle = stroke + if (fill) ctx.fillStyle = fill - ctx.beginPath(); + ctx.beginPath() for (let s = 0; s < sides; s += 1) { - px = x + (Math.sin(tilt + (a * s)) * radius); - py = y + (Math.cos(tilt + (a * s)) * radius); - ctx[!s ? 'moveTo' : 'lineTo'](px, py); + px = x + (Math.sin(tilt + (a * s)) * radius) + py = y + (Math.cos(tilt + (a * s)) * radius) + ctx[!s ? 'moveTo' : 'lineTo'](px, py) } - ctx.closePath(); + ctx.closePath() - if (stroke) ctx.stroke(); - if (fill) ctx.fill(); - }; + if (stroke) ctx.stroke() + if (fill) ctx.fill() + } const grid: grid = (rows = 4, cols = 4, func = noop) => { - const xs = width(rows); - const ys = height(cols); - let x; - let y; - let n = 0; + const xs = width(rows) + const ys = height(cols) + let x + let y + let n = 0 for (let r = 0; r < rows; r += 1) { for (let c = 0; c < cols; c += 1) { - x = xs * (r + 0.5); - y = ys * (c + 0.5); - func(x, y, n, r, c); - n += 1; + x = xs * (r + 0.5) + y = ys * (c + 0.5) + func(x, y, n, r, c) + n += 1 } } - }; + } const centeredGrid: centeredGrid = ({ cols = 4, rows = 4, dist = vmin(10), - unit = vmin, + unit = vmin } = { - cols: 4, - rows: 4, - dist: vmin(10), - unit: vmin, - }, cb = noop) => { + cols: 4, + rows: 4, + dist: vmin(10), + unit: vmin + }, cb = noop) => { const d = typeof dist === 'undefined' ? unit(100 / Math.max(Math.max(cols, 1), Math.max(rows, 1))) - : dist; - const xs = (width() - ((cols - 1) * d)) * 0.5; - const ys = (height() - ((rows - 1) * d)) * 0.5; - let n = 0; - const data: any[] = []; + : dist + const xs = (width() - ((cols - 1) * d)) * 0.5 + const ys = (height() - ((rows - 1) * d)) * 0.5 + let n = 0 + const data: any[] = [] repeat(rows, (r) => { - const row: any[] = []; + const row: any[] = [] repeat(cols, (c) => { const args = { @@ -701,24 +677,24 @@ export default function canvasTools(ctx: CTX) { r, c, n, - d, - }; - cb(args); - row.push(args); + d + } + cb(args) + row.push(args) - n += 1; - }); + n += 1 + }) - data.push(row); - }); + data.push(row) + }) return { x: xs, y: ys, size: d, - data, - }; - }; + data + } + } const baseTools = { width, @@ -743,31 +719,32 @@ export default function canvasTools(ctx: CTX) { polygon, grid, // TODO: redo grid in distributedGrid with same params and returned as centeredGrid - centeredGrid, - }; + centeredGrid + } - const tools = { ...baseTools } as Canvas2DAPI; + const tools = { ...baseTools } as Canvas2DAPI const instKeys = Object.keys(ctx) - .filter((key) => !['function', 'object'].includes(key)); - const proto = ctx.constructor.prototype; + .filter((key) => !['function', 'object'].includes(key)) + const proto = ctx.constructor.prototype Object.keys(proto) .filter((key) => !instKeys.includes(key)) .forEach((key) => { - if (key === 'canvas') return; + if (key === 'canvas') return - const prop: any = (ctx as any)[key as string]; + const prop: any = (ctx as any)[key] if (typeof prop === 'function') { - tools[key as keyof typeof tools] = prop.bind(ctx); + tools[key as keyof typeof tools] = prop.bind(ctx) } else if (!tools[key as keyof typeof tools]) { tools[key as keyof typeof tools] = (value = prop) => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment, @typescript-eslint/prefer-ts-expect-error // @ts-ignore - if (prop !== value) ctx[key] = value; - return prop; - }; + if (prop !== value) ctx[key] = value + return prop + } } - }); + }) - return tools; + return tools } diff --git a/src/layers/Layer.spec.ts b/src/layers/Layer.spec.ts index 2d103e1c..d8c728f7 100644 --- a/src/layers/Layer.spec.ts +++ b/src/layers/Layer.spec.ts @@ -1,94 +1,94 @@ /** * @jest-environment jsdom */ -import Layer, { LayerOptions } from './Layer'; +import Layer, { type LayerOptions } from './Layer' -const setupScript = 'console.info("hello"); return { newData: "set" };'; +const setupScript = 'console.info("hello"); return { newData: "set" };' -let layer: Layer; +let layer: Layer const options = { id: 'layerId', - canvas: document.createElement('canvas'), -} as LayerOptions; + canvas: document.createElement('canvas') +} as LayerOptions const compilationErrorListener = jest.fn((err) => { - console.info(err.builderStr); -}); + console.info(err.builderStr) +}) describe('instanciation', () => { it('takes some options', () => { - layer = new Layer(options); - expect(layer).toBeTruthy(); - expect(layer).toHaveProperty('setup.isAsync', false); - expect(layer).toHaveProperty('setup.version', 1); - expect(layer).toHaveProperty('id', options.id); - }); + layer = new Layer(options) + expect(layer).toBeTruthy() + expect(layer).toHaveProperty('setup.isAsync', false) + expect(layer).toHaveProperty('setup.version', 1) + expect(layer).toHaveProperty('id', options.id) + }) it('throws an error if no id is provided', () => { expect(() => new Layer({ - canvas: document.createElement('canvas'), - } as LayerOptions)).toThrowError(); - }); + canvas: document.createElement('canvas') + } as LayerOptions)).toThrowError() + }) it('has a cache', () => { - expect(layer).toHaveProperty('cache', {}); - }); -}); + expect(layer).toHaveProperty('cache', {}) + }) +}) describe('setup script', () => { it('is empty by default', () => { - expect(layer).toHaveProperty('setup.code', ''); - }); + expect(layer).toHaveProperty('setup.code', '') + }) it('has a version number', () => { - expect(layer).toHaveProperty('setup.version', 1); - }); + expect(layer).toHaveProperty('setup.version', 1) + }) it('can be set', () => { - layer.setup.addEventListener('compilationerror', compilationErrorListener); + layer.setup.addEventListener('compilationerror', compilationErrorListener) expect(() => { - layer.setup.code = setupScript; - }).not.toThrowError(); - expect(compilationErrorListener).not.toHaveBeenCalled(); - expect(layer).toHaveProperty('setup.version', 2); - expect(layer).toHaveProperty('setup.code', setupScript); - }); + layer.setup.code = setupScript + }).not.toThrowError() + expect(compilationErrorListener).not.toHaveBeenCalled() + expect(layer).toHaveProperty('setup.version', 2) + expect(layer).toHaveProperty('setup.code', setupScript) + }) it('always executes asynchronimously', async () => { - layer.setup.code = 'return await (new Promise((res) => res({ newData: "set" })))'; - expect(layer).toHaveProperty('setup.isAsync', true); - const promise = layer.execSetup(); - await expect(promise).resolves.toStrictEqual({ newData: 'set' }); - }); + layer.setup.code = 'return await (new Promise((res) => res({ newData: "set" })))' + expect(layer).toHaveProperty('setup.isAsync', true) + const promise = layer.execSetup() + await expect(promise).resolves.toStrictEqual({ newData: 'set' }) + }) it('can be used to set the scripts cache', () => { - expect(layer).toHaveProperty('cache', { newData: 'set' }); - }); -}); + expect(layer).toHaveProperty('cache', { newData: 'set' }) + }) +}) describe('animation script', () => { it('is empty by default', () => { - expect(layer).toHaveProperty('animation.code', ''); - }); + expect(layer).toHaveProperty('animation.code', '') + }) it('can use the script cache', () => { - const logListener = jest.fn(); - const code = 'cache.added = true; scriptLog("cache", cache);'; - layer.animation.code = code; - layer.animation.addEventListener('log', logListener); - layer.animation.addEventListener('executionerror', (err) => console.info(err)); - expect(layer).toHaveProperty('animation.code', code); - expect(layer.cache).toHaveProperty('newData', 'set'); - expect(layer.execAnimation).not.toThrow(); - expect(layer.cache).toHaveProperty('newData', 'set'); - expect(layer.cache).toHaveProperty('added', true); + const logListener = jest.fn() + const code = 'cache.added = true; scriptLog("cache", cache);' + layer.animation.code = code + layer.animation.addEventListener('log', logListener) + layer.animation.addEventListener('executionerror', (err) => { console.info(err) }) + expect(layer).toHaveProperty('animation.code', code) + expect(layer.cache).toHaveProperty('newData', 'set') + expect(layer.execAnimation).not.toThrow() + expect(layer.cache).toHaveProperty('newData', 'set') + expect(layer.cache).toHaveProperty('added', true) expect(logListener).toHaveBeenCalledWith({ data: [ - ['cache', { newData: 'set', added: true }], + ['cache', { newData: 'set', added: true }] ], - type: 'log', - }); - layer.animation.removeEventListener('log', logListener); - }); -}); + type: 'log' + }) + layer.animation.removeEventListener('log', logListener) + }) +}) diff --git a/src/layers/Layer.ts b/src/layers/Layer.ts index 399c133d..b70b22a2 100644 --- a/src/layers/Layer.ts +++ b/src/layers/Layer.ts @@ -1,50 +1,51 @@ -import Scriptable, { ScriptableOptions } from '../utils/Scriptable'; +import Scriptable, { type ScriptableOptions } from '../utils/Scriptable' interface OffscreenCanvas extends HTMLCanvasElement { } export interface LayerOptions extends Omit { - id: string; - canvas?: HTMLCanvasElement | OffscreenCanvas; - active?: boolean; + id: string + canvas?: HTMLCanvasElement | OffscreenCanvas + active?: boolean } export default class Layer extends Scriptable { - constructor(options: LayerOptions) { - super(options); - this.active = typeof options.active !== 'undefined' ? options.active : true; - if (!options.id) throw new Error('Missing id option'); + constructor (options: LayerOptions) { + super(options) + this.active = typeof options.active !== 'undefined' ? options.active : true + if (!options.id) throw new Error('Missing id option') + // eslint-disable-next-line @typescript-eslint/ban-ts-comment, @typescript-eslint/prefer-ts-expect-error // @ts-ignore - this.#canvas = (options.canvas || new OffscreenCanvas(600, 400)) as OffscreenCanvas; + this.#canvas = ((options.canvas != null) || new OffscreenCanvas(600, 400)) as OffscreenCanvas } - active: boolean = true; + active: boolean = true - #canvas: HTMLCanvasElement | OffscreenCanvas; + #canvas: HTMLCanvasElement | OffscreenCanvas - get canvas() { - return this.#canvas; + get canvas () { + return this.#canvas } - get width() { - return this.#canvas.width as number; + get width () { + return this.#canvas.width } - set width(val) { - const canvas = this.#canvas; - canvas.width = val; + set width (val) { + const canvas = this.#canvas + canvas.width = val } - get height() { - return this.#canvas.height as number; + get height () { + return this.#canvas.height } - set height(val) { - const canvas = this.#canvas; - canvas.height = val; + set height (val) { + const canvas = this.#canvas + canvas.height = val } execAnimation = () => { - if (!this.active) return; - this.animation.exec(); - }; + if (!this.active) return + this.animation.exec() + } } diff --git a/src/layers/ThreeJS/ThreeJSLayer.skippedspec.ts b/src/layers/ThreeJS/ThreeJSLayer.skippedspec.ts index 94334cac..65ff4452 100644 --- a/src/layers/ThreeJS/ThreeJSLayer.skippedspec.ts +++ b/src/layers/ThreeJS/ThreeJSLayer.skippedspec.ts @@ -1,91 +1,91 @@ -import ThreeJSLayer, { ThreeJSLayerOptions } from './ThreeJSLayer'; +import ThreeJSLayer, { type ThreeJSLayerOptions } from './ThreeJSLayer' -const setupScript = 'console.info("hello"); return { newData: "set" };'; +const setupScript = 'console.info("hello"); return { newData: "set" };' -let layer: ThreeJSLayer; +let layer: ThreeJSLayer const options = { id: 'layerId', - canvas: document.createElement('canvas'), -} as ThreeJSLayerOptions; + canvas: document.createElement('canvas') +} as ThreeJSLayerOptions const compilationErrorListener = jest.fn((err) => { - console.info(err.builderStr); -}); + console.info(err.builderStr) +}) describe.skip('instanciation', () => { it('takes some options', () => { - layer = new ThreeJSLayer(options); - expect(layer).toBeTruthy(); - expect(layer).toHaveProperty('setup.isAsync', false); - expect(layer).toHaveProperty('setup.version', 2); - expect(layer).toHaveProperty('id', options.id); - }); + layer = new ThreeJSLayer(options) + expect(layer).toBeTruthy() + expect(layer).toHaveProperty('setup.isAsync', false) + expect(layer).toHaveProperty('setup.version', 2) + expect(layer).toHaveProperty('id', options.id) + }) it('throws an error if no id is provided', () => { expect(() => new ThreeJSLayer({ - canvas: document.createElement('canvas'), - } as ThreeJSLayerOptions)).toThrowError(); - }); + canvas: document.createElement('canvas') + } as ThreeJSLayerOptions)).toThrowError() + }) it('has a cache', () => { - expect(layer).toHaveProperty('cache', {}); - }); -}); + expect(layer).toHaveProperty('cache', {}) + }) +}) describe.skip('setup script', () => { it('is empty by default', () => { - expect(layer).toHaveProperty('setup.code', ''); - }); + expect(layer).toHaveProperty('setup.code', '') + }) it('has a version number', () => { - expect(layer).toHaveProperty('setup.version', 2); - }); + expect(layer).toHaveProperty('setup.version', 2) + }) it('can be set', () => { - layer.setup.addEventListener('compilationerror', compilationErrorListener); + layer.setup.addEventListener('compilationerror', compilationErrorListener) expect(() => { - layer.setup.code = setupScript; - }).not.toThrowError(); - expect(compilationErrorListener).not.toHaveBeenCalled(); - expect(layer).toHaveProperty('setup.version', 3); - expect(layer).toHaveProperty('setup.code', setupScript); - }); + layer.setup.code = setupScript + }).not.toThrowError() + expect(compilationErrorListener).not.toHaveBeenCalled() + expect(layer).toHaveProperty('setup.version', 3) + expect(layer).toHaveProperty('setup.code', setupScript) + }) it('always executes asynchronimously', async () => { - layer.setup.code = 'return await (new Promise((res) => res({ newData: "set" })))'; - expect(layer).toHaveProperty('setup.isAsync', true); - const promise = layer.execSetup(); - await expect(promise).resolves.toStrictEqual({ newData: 'set' }); - }); + layer.setup.code = 'return await (new Promise((res) => res({ newData: "set" })))' + expect(layer).toHaveProperty('setup.isAsync', true) + const promise = layer.execSetup() + await expect(promise).resolves.toStrictEqual({ newData: 'set' }) + }) it('can be used to set the scripts cache', () => { - expect(layer).toHaveProperty('cache', { newData: 'set' }); - }); -}); + expect(layer).toHaveProperty('cache', { newData: 'set' }) + }) +}) describe.skip('animation script', () => { it('is empty by default', () => { - expect(layer).toHaveProperty('animation.code', ''); - }); + expect(layer).toHaveProperty('animation.code', '') + }) it('can use the script cache', () => { - const logListener = jest.fn(); - const code = 'cache.added = true; scriptLog("cache", cache);'; - layer.animation.code = code; - layer.animation.addEventListener('log', logListener); - layer.animation.addEventListener('executionerror', (err) => console.info(err)); - expect(layer).toHaveProperty('animation.code', code); - expect(layer.cache).toHaveProperty('newData', 'set'); - expect(layer.execAnimation).not.toThrow(); - expect(layer.cache).toHaveProperty('newData', 'set'); - expect(layer.cache).toHaveProperty('added', true); + const logListener = jest.fn() + const code = 'cache.added = true; scriptLog("cache", cache);' + layer.animation.code = code + layer.animation.addEventListener('log', logListener) + layer.animation.addEventListener('executionerror', (err) => { console.info(err) }) + expect(layer).toHaveProperty('animation.code', code) + expect(layer.cache).toHaveProperty('newData', 'set') + expect(layer.execAnimation).not.toThrow() + expect(layer.cache).toHaveProperty('newData', 'set') + expect(layer.cache).toHaveProperty('added', true) expect(logListener).toHaveBeenCalledWith({ data: [ - ['cache', { newData: 'set', added: true }], + ['cache', { newData: 'set', added: true }] ], - type: 'log', - }); - layer.animation.removeEventListener('log', logListener); - }); -}); + type: 'log' + }) + layer.animation.removeEventListener('log', logListener) + }) +}) diff --git a/src/layers/ThreeJS/ThreeJSLayer.ts b/src/layers/ThreeJS/ThreeJSLayer.ts index 4e379baf..d76ea69c 100644 --- a/src/layers/ThreeJS/ThreeJSLayer.ts +++ b/src/layers/ThreeJS/ThreeJSLayer.ts @@ -1,33 +1,33 @@ -import * as THREE from 'three'; -import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'; +import * as THREE from 'three' +import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader' -import Layer, { LayerOptions } from '../Layer'; -import * as mathTools from '../../utils/mathTools'; -import miscTools from '../../utils/miscTools'; +import Layer, { type LayerOptions } from '../Layer' +import * as mathTools from '../../utils/mathTools' +import miscTools from '../../utils/miscTools' export interface ThreeJSLayerOptions extends LayerOptions { } export default class ThreeJSLayer extends Layer { - constructor(options: ThreeJSLayerOptions) { - super(options); + constructor (options: ThreeJSLayerOptions) { + super(options) - const { canvas, canvas: { width, height } } = this; + const { canvas, canvas: { width, height } } = this this.renderer = new THREE.WebGLRenderer({ alpha: true, - canvas, - }); - this.scene = new THREE.Scene(); - this.camera = new THREE.PerspectiveCamera(20, width / height, 1, 1000); + canvas + }) + this.scene = new THREE.Scene() + this.camera = new THREE.PerspectiveCamera(20, width / height, 1, 1000) - const { renderer, camera, scene } = this; + const { renderer, camera, scene } = this - renderer.setClearColor(0x000000, 0); + renderer.setClearColor(0x000000, 0) - camera.position.z = 400; - camera.position.x = 400; - camera.position.y = 100; - camera.lookAt(0, 0, 0); + camera.position.z = 400 + camera.position.x = 400 + camera.position.y = 100 + camera.lookAt(0, 0, 0) this.api = { ...mathTools, @@ -38,49 +38,49 @@ export default class ThreeJSLayer extends Layer { scene, renderer, GLTFLoader, - clear: this.#clearScene, - }; + clear: this.#clearScene + } } - renderer: THREE.WebGLRenderer; + renderer: THREE.WebGLRenderer - camera: THREE.PerspectiveCamera; + camera: THREE.PerspectiveCamera - scene: THREE.Scene; + scene: THREE.Scene #clearScene = () => { this.scene.children.forEach((child) => { - console.info('[ThreeJS] clear scene child', child.name || 'no name'); - this.scene.remove(child); - }); - }; + console.info('[ThreeJS] clear scene child', child.name || 'no name') + this.scene.remove(child) + }) + } #update = () => { - if (!this.camera) return; + if (!this.camera) return try { - this.camera.aspect = this.width / this.height; + this.camera.aspect = this.width / this.height } catch (e) { - console.info('[ThreeJS] cannot set aspect', (e as Error).message); + console.info('[ThreeJS] cannot set aspect', (e as Error).message) } - }; + } - get width() { - return this.canvas.width; + get width () { + return this.canvas.width } - get height() { - return this.canvas.height; + set width (width: number) { + this.canvas.width = width + this.renderer.setSize(width, this.canvas.height, false) + this.#update() } - set width(width: number) { - this.canvas.width = width; - this.renderer.setSize(width, this.canvas.height, false); - this.#update(); + get height () { + return this.canvas.height } - set height(height: number) { - this.canvas.height = height; - this.renderer.setSize(this.canvas.width, height, false); - this.#update(); + set height (height: number) { + this.canvas.height = height + this.renderer.setSize(this.canvas.width, height, false) + this.#update() } } diff --git a/src/types.ts b/src/types.ts index be3f4b89..739abe72 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,99 +1,97 @@ -import type * as vscode from 'vscode'; -import type { Socket } from 'socket.io'; +import type * as vscode from 'vscode' +import type { Socket } from 'socket.io' -import type VFExtension from './extension/VFExtension'; -import type { ScriptableOptions } from './utils/Scriptable'; +import type VFExtension from './extension/VFExtension' +import type { ScriptableOptions } from './utils/Scriptable' export interface StageInfo { - width: number; - height: number; - autoScale: boolean; + width: number + height: number + autoScale: boolean } export interface DisplayBase { - id: string; - width?: number; - height?: number; - resolution?: number; - readonly control: boolean; + id: string + width?: number + height?: number + resolution?: number + readonly control: boolean } export interface AppDisplay extends DisplayBase { app: { - id: string; - stage: StageInfo; - }; + id: string + stage: StageInfo + } } export interface ServerDisplay extends Omit { - socket: Socket; + socket: Socket } export type Layer = Omit & { - type: 'canvas' | 'threejs' | 'canvas2d' | 'webgl' | 'webgl2'; - id: string; - weight?: number; - active?: boolean; - setup?: string; - animation?: string; -}; + type: 'canvas' | 'threejs' | 'canvas2d' | 'webgl' | 'webgl2' + id: string + weight?: number + active?: boolean + setup?: string + animation?: string +} export interface DisplayServerInfo { - host: string; - port: number; + host: string + port: number } export interface AppState { - id: string; + id: string bpm: { - count: number; - start: number; - }; + count: number + start: number + } worker: { - setup: string; - animation: string; - }; - server: DisplayServerInfo; - displays: DisplayBase[]; - layers: Layer[]; - stage: StageInfo; + setup: string + animation: string + } + server: DisplayServerInfo + displays: DisplayBase[] + layers: Layer[] + stage: StageInfo } -export type FihaRC = { - id: string; +export interface FihaRC { + id: string // bpm?: number; - layers: Layer[]; - assets?: { name: string }[]; -}; + layers: Layer[] + assets?: Array<{ name: string }> +} -export type ScriptableAPIReference = { - [key: string]: { - category?: string; - type?: string; - description?: string; - snippet?: string; - link?: string; - }; -}; +export type ScriptableAPIReference = Record -export type ScriptingData = { - iterationCount: number; - now: number; - deltaNow: number; - frequency: number[]; - volume: number[]; +export interface ScriptingData { + iterationCount: number + now: number + deltaNow: number + frequency: number[] + volume: number[] [k: string]: any -}; +} -export type ScriptRole = 'setup' | 'animation'; -export type ScriptType = 'layer' | 'signal' | 'worker' | 'server'; -export type ScriptInfo = { - relativePath: string; - path: string; - id: string; - type: ScriptType; - role: ScriptRole; -}; +export type ScriptRole = 'setup' | 'animation' +export type ScriptType = 'layer' | 'signal' | 'worker' | 'server' +export interface ScriptInfo { + relativePath: string + path: string + id: string + type: ScriptType + role: ScriptRole +} export enum DirectoryTypes { layers = 'layer', signals = 'signal', @@ -105,9 +103,7 @@ export enum TypeDirectory { worker = 'worker', } -export interface VFCommand { - ( - context: vscode.ExtensionContext, - extension: VFExtension - ): (...args: any[]) => any, thisArg?: any; -} \ No newline at end of file +export type VFCommand = ( + context: vscode.ExtensionContext, + extension: VFExtension +) => (...args: any[]) => any diff --git a/src/utils/ScriptRunner.spec.ts b/src/utils/ScriptRunner.spec.ts index c998743c..d065d1c6 100644 --- a/src/utils/ScriptRunner.spec.ts +++ b/src/utils/ScriptRunner.spec.ts @@ -1,14 +1,14 @@ -import ScriptRunner from './ScriptRunner'; +import ScriptRunner from './ScriptRunner' const api = { val1: 1, val2: 2, - fnA: () => { }, -}; + fnA: () => { } +} -const scope = { stuff: true }; +const scope = { stuff: true } -const syncCode = 'return val1 + val2;'; +const syncCode = 'return val1 + val2;' const asyncCode = ` const a = await (new Promise((res) => { @@ -18,232 +18,232 @@ const b = await (new Promise((res) => { res(val2); })); return a + b; -`; +` const compilationBrokenCode = ` breaks % line 6; -`; +` const executionBrokenCode = ` a.substr(0, 1); -`; +` describe('object ScriptRunner', () => { describe('compilation', () => { describe('with sync code', () => { - let runner: ScriptRunner; + let runner: ScriptRunner it('instanciates', () => { expect(() => { - runner = new ScriptRunner(); - runner.code = syncCode; - }).not.toThrow(); - expect(runner).toBeInstanceOf(ScriptRunner); - expect(runner.isAsync).toBe(false); - }); - }); + runner = new ScriptRunner() + runner.code = syncCode + }).not.toThrow() + expect(runner).toBeInstanceOf(ScriptRunner) + expect(runner.isAsync).toBe(false) + }) + }) describe('with async code', () => { - let runner: ScriptRunner; + let runner: ScriptRunner it('instanciates', () => { expect(() => { - runner = new ScriptRunner(scope); - runner.api = api; - runner.code = asyncCode; - }).not.toThrow(); + runner = new ScriptRunner(scope) + runner.api = api + runner.code = asyncCode + }).not.toThrow() - expect(runner.isAsync).toBe(true); + expect(runner.isAsync).toBe(true) - expect(runner.version).toBe(2); + expect(runner.version).toBe(2) - expect(runner).toBeInstanceOf(ScriptRunner); + expect(runner).toBeInstanceOf(ScriptRunner) - expect(runner.isAsync).toBe(true); - }); + expect(runner.isAsync).toBe(true) + }) it('returns a promise when executed', async () => { - const promise = runner.exec(); - expect(promise).toBeInstanceOf(Promise); - await expect(promise).resolves.toBeTruthy(); - }); + const promise = runner.exec() + expect(promise).toBeInstanceOf(Promise) + await expect(promise).resolves.toBeTruthy() + }) it('executes', async () => { - const result = await runner.exec(); - expect(result).toBe(3); - }); - }); - }); + const result = await runner.exec() + expect(result).toBe(3) + }) + }) + }) describe('execution', () => { describe('with sync code', () => { it('does not return a promise when executed', async () => { - const runner = new ScriptRunner(scope); - runner.code = syncCode; - runner.api = api; + const runner = new ScriptRunner(scope) + runner.code = syncCode + runner.api = api - let result; + let result await expect((async () => { - result = await runner.exec(); - })()).resolves.toBeUndefined(); - expect(result).not.toBeInstanceOf(Promise); + result = await runner.exec() + })()).resolves.toBeUndefined() + expect(result).not.toBeInstanceOf(Promise) - expect(result).toBe(3); - }); - }); + expect(result).toBe(3) + }) + }) describe('with async code', () => { it('does not return a promise when executed', async () => { - const runner = new ScriptRunner(scope); - runner.code = asyncCode; - runner.api = api; - const promise = runner.exec(); + const runner = new ScriptRunner(scope) + runner.code = asyncCode + runner.api = api + const promise = runner.exec() - expect(promise).toBeInstanceOf(Promise); + expect(promise).toBeInstanceOf(Promise) - expect(await promise).toBe(3); - }); - }); + expect(await promise).toBe(3) + }) + }) describe('scope', () => { it('prevents access to global', async () => { - const runner = new ScriptRunner(scope); - runner.code = 'return console'; + const runner = new ScriptRunner(scope) + runner.code = 'return console' - let result; + let result await expect((async () => { - result = await runner.exec(); - })()).resolves.toBeUndefined(); + result = await runner.exec() + })()).resolves.toBeUndefined() - expect(result).toBeUndefined(); - }); + expect(result).toBeUndefined() + }) it('can be set', async () => { - const runner = new ScriptRunner(scope); + const runner = new ScriptRunner(scope) runner.code = ` return this.stuff - `; + ` - let result; + let result await expect((async () => { - result = await runner.exec(); - })()).resolves.toBeUndefined(); + result = await runner.exec() + })()).resolves.toBeUndefined() - expect(result).toBe(true); - }); - }); + expect(result).toBe(true) + }) + }) describe('scriptLog()', () => { it('logs', async () => { - const runner = new ScriptRunner(scope); - const compilationErrorListener = jest.fn(); - const executionErrorListener = jest.fn(); - const logListener = jest.fn(); - runner.addEventListener('compilationerror', compilationErrorListener); - runner.addEventListener('executionerror', executionErrorListener); - runner.addEventListener('log', logListener); - runner.api = api; + const runner = new ScriptRunner(scope) + const compilationErrorListener = jest.fn() + const executionErrorListener = jest.fn() + const logListener = jest.fn() + runner.addEventListener('compilationerror', compilationErrorListener) + runner.addEventListener('executionerror', executionErrorListener) + runner.addEventListener('log', logListener) + runner.api = api runner.code = ` scriptLog("script logged", this, typeof self, typeof window, val1, val2); return this.stuff - `; + ` - let result; + let result await expect((async () => { - result = await runner.exec(); - })()).resolves.toBeUndefined(); + result = await runner.exec() + })()).resolves.toBeUndefined() - expect(compilationErrorListener).not.toHaveBeenCalled(); - expect(executionErrorListener).not.toHaveBeenCalled(); - expect(logListener).toHaveBeenCalledTimes(1); + expect(compilationErrorListener).not.toHaveBeenCalled() + expect(executionErrorListener).not.toHaveBeenCalled() + expect(logListener).toHaveBeenCalledTimes(1) - expect(runner.log).toHaveLength(1); - expect(runner.log[0]).toHaveLength(6); - expect(runner.log[0][0]).toBe('script logged'); - expect(runner.log[0][1]).toStrictEqual(scope); + expect(runner.log).toHaveLength(1) + expect(runner.log[0]).toHaveLength(6) + expect(runner.log[0][0]).toBe('script logged') + expect(runner.log[0][1]).toStrictEqual(scope) // we use typeof in the script, so we need to assert on the string "undefined" - expect(runner.log[0][2]).toBe('undefined'); - expect(runner.log[0][3]).toBe('undefined'); - expect(runner.log[0][4]).toBe(api.val1); - expect(runner.log[0][5]).toBe(api.val2); + expect(runner.log[0][2]).toBe('undefined') + expect(runner.log[0][3]).toBe('undefined') + expect(runner.log[0][4]).toBe(api.val1) + expect(runner.log[0][5]).toBe(api.val2) - expect(result).toBe(true); - }); - }); - }); + expect(result).toBe(true) + }) + }) + }) describe('events', () => { describe('"compilationerror" type', () => { it('is triggered when code cannot be compiled', () => { - const listener = jest.fn(); + const listener = jest.fn() - const runner = new ScriptRunner(scope); - runner.addEventListener('compilationerror', listener); - runner.code = compilationBrokenCode; + const runner = new ScriptRunner(scope) + runner.addEventListener('compilationerror', listener) + runner.code = compilationBrokenCode - expect(listener).toHaveBeenCalledTimes(1); - }); - }); + expect(listener).toHaveBeenCalledTimes(1) + }) + }) describe('"executionerror" type', () => { it('is triggered when code execution fails', async () => { - const compilationErrorListener = jest.fn(); - const executionErrorListener = jest.fn(); - const runner = new ScriptRunner(scope); + const compilationErrorListener = jest.fn() + const executionErrorListener = jest.fn() + const runner = new ScriptRunner(scope) - runner.addEventListener('compilationerror', compilationErrorListener); - runner.addEventListener('executionerror', executionErrorListener); + runner.addEventListener('compilationerror', compilationErrorListener) + runner.addEventListener('executionerror', executionErrorListener) - runner.code = executionBrokenCode; + runner.code = executionBrokenCode - try { await runner.exec(); } catch (e) { /* */ } + try { await runner.exec() } catch (e) { /* */ } - expect(executionErrorListener).toHaveBeenCalledTimes(1); - expect(compilationErrorListener).toHaveBeenCalledTimes(0); - }); - }); + expect(executionErrorListener).toHaveBeenCalledTimes(1) + expect(compilationErrorListener).toHaveBeenCalledTimes(0) + }) + }) describe('"log" type', () => { it('is triggered after execution of code', async () => { - const listener = jest.fn(); + const listener = jest.fn() - const runner = new ScriptRunner(scope); - runner.code = 'scriptLog("log");'; - runner.addEventListener('log', listener); + const runner = new ScriptRunner(scope) + runner.code = 'scriptLog("log");' + runner.addEventListener('log', listener) - try { await runner.exec(); } catch (e) { /* */ } + try { await runner.exec() } catch (e) { /* */ } - expect(listener).toHaveBeenCalledTimes(1); - }); + expect(listener).toHaveBeenCalledTimes(1) + }) it('is triggered only if log() has been called within the code', async () => { - const listener = jest.fn(); + const listener = jest.fn() - const runner = new ScriptRunner(scope); - runner.addEventListener('log', listener); + const runner = new ScriptRunner(scope) + runner.addEventListener('log', listener) - try { await runner.exec(); } catch (e) { /* */ } + try { await runner.exec() } catch (e) { /* */ } - expect(listener).toHaveBeenCalledTimes(0); - }); - }); + expect(listener).toHaveBeenCalledTimes(0) + }) + }) describe('.removeEventListener()', () => { it('can be used to remove event listeners', async () => { - const listener = jest.fn(); + const listener = jest.fn() - const runner = new ScriptRunner(scope); - runner.code = 'scriptLog("log");'; - runner.addEventListener('log', listener); + const runner = new ScriptRunner(scope) + runner.code = 'scriptLog("log");' + runner.addEventListener('log', listener) - try { await runner.exec(); } catch (e) { /* */ } + try { await runner.exec() } catch (e) { /* */ } - runner.removeEventListener('log', listener); + runner.removeEventListener('log', listener) - try { await runner.exec(); } catch (e) { /* */ } + try { await runner.exec() } catch (e) { /* */ } - expect(listener).toHaveBeenCalledTimes(1); - }); - }); - }); -}); + expect(listener).toHaveBeenCalledTimes(1) + }) + }) + }) +}) diff --git a/src/utils/ScriptRunner.ts b/src/utils/ScriptRunner.ts index e0f46948..0bf2e62e 100644 --- a/src/utils/ScriptRunner.ts +++ b/src/utils/ScriptRunner.ts @@ -1,45 +1,42 @@ // eslint-disable-next-line max-classes-per-file -const asyncNoop = async () => { }; +const asyncNoop = async () => { } -export interface ScriptLog { - (...args: any[]): void; -} +export type ScriptLog = (...args: any[]) => void // import { JSHINT } from 'jshint'; -export type ScriptRunnerEventTypes = 'compilationerror' | 'executionerror' | 'log'; +export type ScriptRunnerEventTypes = 'compilationerror' | 'executionerror' | 'log' export interface ScriptRunnerCodeError extends Error { - lineNumber?: number; - columnNumber?: number; - details?: object[]; + lineNumber?: number + columnNumber?: number + details?: object[] } export interface ScriptRunnerEvent { - defaultPrevented?: boolean; - readonly type: ScriptRunnerEventTypes; + defaultPrevented?: boolean + readonly type: ScriptRunnerEventTypes } export interface ScriptRunnerErrorEvent extends ScriptRunnerEvent { - error: ScriptRunnerCodeError | ScriptRunnerLintingError; - readonly type: 'compilationerror' | 'executionerror'; - builderStr?: string; - code?: string; + error: ScriptRunnerCodeError | ScriptRunnerLintingError + readonly type: 'compilationerror' | 'executionerror' + builderStr?: string + code?: string } export interface ScriptRunnerLogEvent extends ScriptRunnerEvent { - data: any; - readonly type: 'log'; + data: any + readonly type: 'log' } -export interface ScriptRunnerEventListener { - (event: ScriptRunnerErrorEvent | ScriptRunnerLogEvent): boolean | void; -} +export type ScriptRunnerEventListener = (event: ScriptRunnerErrorEvent | ScriptRunnerLogEvent) => boolean | undefined -export type API = { [scriptGlobalName: string]: any }; +export type API = Record -export const removeExportCrutch = (str: string) => str.replace(/export\s+{\s?};?/g, ''); +export const removeExportCrutch = (str: string) => str.replace(/export\s+{\s?};?/g, '') +// eslint-disable-next-line @typescript-eslint/no-extraneous-class class EmptyScope { } /* eslint-disable */ @@ -56,98 +53,99 @@ const forbidden = [ /* eslint-enable */ class ScriptRunnerLintingError extends Error { - constructor(details: object[]) { - super('ScriptRunnerLintingError'); - this.details = details; + constructor (details: object[]) { + super('ScriptRunnerLintingError') + this.details = details } - details: object[] = []; + details: object[] = [] } class ScriptRunner { - constructor(scope: any = null, name = `sr${Date.now()}`) { - this.#scope = scope; - this.#name = name; + constructor (scope: any = null, name = `sr${Date.now()}`) { + this.#scope = scope + this.#name = name } - #name: string; + #name: string - #listeners: { [type: string]: ScriptRunnerEventListener[] } = {}; + #listeners: Record = {} #errors: { - compilation?: Error | null, - execution?: Error | null, + compilation?: Error | null + execution?: Error | null } = { - compilation: null, - execution: null, - }; + compilation: null, + execution: null + } - #version = 0; + #version = 0 - #code = ''; + #code = '' - #scope: any = new EmptyScope(); + #scope: any = new EmptyScope() - #fn: Function = asyncNoop; + // eslint-disable-next-line @typescript-eslint/ban-types + #fn: Function = asyncNoop - #logs: any[] = []; + #logs: any[] = [] #log = (...whtvr: any[]) => { - this.#logs.push(whtvr); - }; + this.#logs.push(whtvr) + } - #api: API = {}; + #api: API = {} - get version() { - return this.#version; + get version () { + return this.#version } - get scope() { - return this.#scope; + get scope () { + return this.#scope } - set scope(newScope: any) { - this.#scope = newScope; + set scope (newScope: any) { + this.#scope = newScope } - get api(): API & { scriptLog: (...args: any[]) => void } { + get api (): API & { scriptLog: (...args: any[]) => void } { return { ...this.#api, - scriptLog: this.#log, - }; + scriptLog: this.#log + } } - set api({ scriptLog, ...api }: API) { - this.#api = api; - this.code = this.#code; + set api ({ scriptLog, ...api }: API) { + this.#api = api + this.code = this.#code } - get log() { return this.#logs; } + get log () { return this.#logs } - get isAsync() { return this.#code.includes('await'); } + get isAsync () { return this.#code.includes('await') } - get code() { - return this.#code; + get code () { + return this.#code } - set code(code: string) { - this.#errors.compilation = null; - this.#errors.execution = null; + set code (code: string) { + this.#errors.compilation = null + this.#errors.execution = null - const paramsStr = Object.keys(this.api).join(', '); + const paramsStr = Object.keys(this.api).join(', ') const forbiddenStr = [ ...forbidden, - ...Object.keys(this), + ...Object.keys(this) ] .reduce((acc: string[], val: string) => ( (acc.includes(val) || paramsStr.includes(val)) ? acc : [...acc, val] ), []) - .join(', '); + .join(', ') - const sync = code.includes('await') ? 'async' : ''; + const sync = code.includes('await') ? 'async' : '' // const jshintReadOnly = Object.keys(this.api) // .reduce((obj, name) => ({ ...obj, [name]: false }), {}); @@ -169,91 +167,91 @@ class ScriptRunner { return ${sync} function ${this.#name}_${this.#version + 1}(${paramsStr}) { ${forbiddenStr ? `let ${forbiddenStr};` : ''} ${removeExportCrutch(code || '// empty')} - };`.trim(); + };`.trim() try { // eslint-disable-next-line no-new-func, @typescript-eslint/no-implied-eval - const builder = new Function(builderStr); + const builder = new Function(builderStr) - const fn = builder(); + const fn = builder() if (fn.toString() !== this.#fn.toString()) { - this.#fn = fn; - this.#code = code; - this.#version += 1; + this.#fn = fn + this.#code = code + this.#version += 1 } } catch (error) { - const err = error as ScriptRunnerCodeError; - this.#errors.compilation = err; + const err = error as ScriptRunnerCodeError + this.#errors.compilation = err this.dispatchEvent({ type: 'compilationerror', error, lineNumber: err.lineNumber || 0, columnNumber: err.columnNumber || 0, code, - builderStr, - } as ScriptRunnerErrorEvent); + builderStr + } as ScriptRunnerErrorEvent) } } - addEventListener(type: ScriptRunnerEventTypes, callback: ScriptRunnerEventListener) { + addEventListener (type: ScriptRunnerEventTypes, callback: ScriptRunnerEventListener) { /* istanbul ignore next */ - if (!this.#listeners[type]) this.#listeners[type] = []; + if (!this.#listeners[type]) this.#listeners[type] = [] - this.#listeners[type].push(callback); + this.#listeners[type].push(callback) } - removeEventListener(type: ScriptRunnerEventTypes, callback: ScriptRunnerEventListener) { + removeEventListener (type: ScriptRunnerEventTypes, callback: ScriptRunnerEventListener) { /* istanbul ignore next */ - if (!this.#listeners[type]) return; + if (!this.#listeners[type]) return - const stack = this.#listeners[type]; + const stack = this.#listeners[type] for (let i = 0, l = stack.length; i < l; i += 1) { if (stack[i] === callback) { - stack.splice(i, 1); - return; + stack.splice(i, 1) + return } } } - dispatchEvent(event: ScriptRunnerErrorEvent | ScriptRunnerLogEvent) { - if (!this.#listeners[event.type]) return undefined; + dispatchEvent (event: ScriptRunnerErrorEvent | ScriptRunnerLogEvent) { + if (!this.#listeners[event.type]) return undefined - const stack = this.#listeners[event.type].slice(); + const stack = this.#listeners[event.type].slice() for (let i = 0, l = stack.length; i < l; i += 1) { - stack[i].call(this, event); + stack[i].call(this, event) } - return !event.defaultPrevented; + return !event.defaultPrevented } - exec() { - this.#logs = []; + exec () { + this.#logs = [] try { - this.#errors.execution = null; + this.#errors.execution = null - const args = Object.values(this.api); - const result = this.#fn.call(this.#scope, ...args); - if (this.#logs.length) { + const args = Object.values(this.api) + const result = this.#fn.call(this.#scope, ...args) + if (this.#logs.length > 0) { this.dispatchEvent({ type: 'log', - data: this.#logs, - } as ScriptRunnerLogEvent); + data: this.#logs + } as ScriptRunnerLogEvent) } /* istanbul ignore next */ - return result instanceof EmptyScope ? undefined : result; + return result instanceof EmptyScope ? undefined : result } catch (error) { - const err = error as ScriptRunnerCodeError; - this.#errors.execution = err; + const err = error as ScriptRunnerCodeError + this.#errors.execution = err this.dispatchEvent({ type: 'executionerror', - error, - } as ScriptRunnerErrorEvent); + error + } as ScriptRunnerErrorEvent) // console.info('[%s] execution error', this.#name, error.message || error.stack); - return undefined; + return undefined } } } -export default ScriptRunner; +export default ScriptRunner diff --git a/src/utils/Scriptable.ts b/src/utils/Scriptable.ts index 2662a476..2f4e0686 100644 --- a/src/utils/Scriptable.ts +++ b/src/utils/Scriptable.ts @@ -1,110 +1,106 @@ /* eslint-disable no-debugger */ -import ScriptRunner, { ScriptRunnerEventListener, API } from './ScriptRunner'; - -export type Cache = { [k: string]: any }; -export interface ReadInterface { - (name: string, defaultValue?: any): any -} - -export interface WriteInterface { - (data: { [k: string]: any }): void; +import ScriptRunner, { type ScriptRunnerEventListener, type API } from './ScriptRunner' + +export type Cache = Record +export type ReadInterface = (name: string, defaultValue?: any) => any + +export type WriteInterface = (data: Record) => void + +export interface ScriptableOptions { + onCompilationError?: ScriptRunnerEventListener + onExecutionError?: ScriptRunnerEventListener + animation?: string + setup?: string + read?: ReadInterface + api?: API + scope?: any + id: string } -export type ScriptableOptions = { - onCompilationError?: ScriptRunnerEventListener; - onExecutionError?: ScriptRunnerEventListener; - animation?: string; - setup?: string; - read?: ReadInterface; - api?: API; - scope?: any; - id: string; -}; - export default class Scriptable { - constructor(options: ScriptableOptions = { id: `scriptable${Date.now()}` }) { - this.read = options.read || this.read; - this.#id = options.id; + constructor (options: ScriptableOptions = { id: `scriptable${Date.now()}` }) { + this.read = (options.read != null) || this.read + this.#id = options.id this.#runners = { setup: new ScriptRunner(options.scope, `${this.#id}_S`), - animation: new ScriptRunner(options.scope, `${this.#id}_A`), - }; + animation: new ScriptRunner(options.scope, `${this.#id}_A`) + } this.api = { read: this.read, cache: this.cache, - ...(options.api || {}), - }; - this.initialize(options); + ...((options.api != null) || {}) + } + this.initialize(options) } - #id: string; + #id: string #runners: { - setup: ScriptRunner; - animation: ScriptRunner; - }; + setup: ScriptRunner + animation: ScriptRunner + } // TODO: make it private? - cache: Cache = {}; + cache: Cache = {} - read: ReadInterface = (key, fb) => (/* Scriptable read */ typeof this.cache[key] === 'undefined' ? fb : this.cache[key]); + read: ReadInterface = (key, fb) => (/* Scriptable read */ typeof this.cache[key] === 'undefined' ? fb : this.cache[key]) - get id() { - return this.#id; + get id () { + return this.#id } - get api(): API & { cache: Cache } { + get api (): API & { cache: Cache } { return { read: this.read, cache: this.cache, - ...this.#runners.animation.api, - }; + ...this.#runners.animation.api + } } - set api(api: API) { - this.#runners.setup.api = api; - this.#runners.animation.api = api; + set api (api: API) { + this.#runners.setup.api = api + this.#runners.animation.api = api } - get setup() { - return this.#runners.setup; + get setup () { + return this.#runners.setup } - set setup(sr: ScriptRunner) { - this.#runners.setup = sr; + set setup (sr: ScriptRunner) { + this.#runners.setup = sr } - get animation() { - return this.#runners.animation; + get animation () { + return this.#runners.animation } - set animation(sr: ScriptRunner) { - this.#runners.animation = sr; + set animation (sr: ScriptRunner) { + this.#runners.animation = sr } initialize = ({ setup, animation, onCompilationError, - onExecutionError, + onExecutionError }: ScriptableOptions) => { - if (onCompilationError) { - this.setup.addEventListener('compilationerror', onCompilationError); - this.animation.addEventListener('compilationerror', onCompilationError); + if (onCompilationError != null) { + this.setup.addEventListener('compilationerror', onCompilationError) + this.animation.addEventListener('compilationerror', onCompilationError) } - if (onExecutionError) { - this.setup.addEventListener('executionerror', onExecutionError); - this.animation.addEventListener('executionerror', onExecutionError); + if (onExecutionError != null) { + this.setup.addEventListener('executionerror', onExecutionError) + this.animation.addEventListener('executionerror', onExecutionError) } - if (this.setup.code !== setup && setup) this.setup.code = setup; - if (this.animation.code !== animation && animation) this.animation.code = animation; - }; + if (this.setup.code !== setup && setup) this.setup.code = setup + if (this.animation.code !== animation && animation) this.animation.code = animation + } execSetup = async () => { - const result = await this.setup.exec(); - Object.assign(this.cache, result || {}); - return result; - }; + const result = await this.setup.exec() + Object.assign(this.cache, result || {}) + return result + } - execAnimation = () => this.animation.exec(); + execAnimation = () => this.animation.exec() } diff --git a/src/utils/VFS.ts b/src/utils/VFS.ts index 642ae191..66d55b75 100644 --- a/src/utils/VFS.ts +++ b/src/utils/VFS.ts @@ -1,24 +1,24 @@ // eslint-disable-next-line @typescript-eslint/no-unused-vars -async function writeFile(vfsPath: string, content: string) { +async function writeFile (vfsPath: string, content: string) { // } export default class VFS { - #tmppath = ''; + #tmppath = '' - #files: { [vfsPath: string]: string }; + #files: Record - add(vfsPath: string, content: string) { + add (vfsPath: string, content: string) { this.#files[vfsPath] = this.#files[vfsPath] ? `${this.#files[vfsPath]}${content}` - : content; + : content } - async write() { - return Promise.all(Object.keys(this.#files) - .reduce((promises: Promise[], key) => [ + async write () { + return await Promise.all(Object.keys(this.#files) + .reduce((promises: Array>, key) => [ ...promises, - writeFile(key, this.#files[key]), - ], [])); + writeFile(key, this.#files[key]) + ], [])) } -} \ No newline at end of file +} diff --git a/src/utils/assetTools.ts b/src/utils/assetTools.ts index 5254c715..f0d52153 100644 --- a/src/utils/assetTools.ts +++ b/src/utils/assetTools.ts @@ -2,42 +2,42 @@ // https://developer.mozilla.org/en-US/docs/Web/API/Streams_API // https://nodejs.org/en/blog/release/v16.5.0/ -let fetchedURLBlobs: any = {}; +let fetchedURLBlobs: any = {} -async function fetchBlob(url: string, force?: boolean): Promise { +async function fetchBlob (url: string, force?: boolean): Promise { fetchedURLBlobs[url] = force - ? fetch(url).then((r) => r.blob()) - : fetchedURLBlobs[url] || fetch(url).then((r) => r.blob()); - return fetchedURLBlobs[url]; + ? fetch(url).then(async (r) => await r.blob()) + : fetchedURLBlobs[url] || fetch(url).then(async (r) => await r.blob()) + return fetchedURLBlobs[url] } -export function clearFetchedAssets(url?: string) { +export function clearFetchedAssets (url?: string) { if (!url) { - fetchedURLBlobs = {}; + fetchedURLBlobs = {} } else { - delete fetchedURLBlobs[url]; + delete fetchedURLBlobs[url] } } -export async function loadImage(url: string) { - const blob = await fetchBlob(url); - const img = await createImageBitmap(blob); - return img; +export async function loadImage (url: string) { + const blob = await fetchBlob(url) + const img = await createImageBitmap(blob) + return img } -export async function loadVideo(url: string) { - const blob = await fetchBlob(url); - const img = await createImageBitmap(blob); - return img; +export async function loadVideo (url: string) { + const blob = await fetchBlob(url) + const img = await createImageBitmap(blob) + return img } -export async function asset(url: string) { - const blob = await fetchBlob(url); +export async function asset (url: string) { + const blob = await fetchBlob(url) if (blob.type.startsWith('image/')) { - return loadImage(url); + return await loadImage(url) } if (blob.type.startsWith('video/')) { - return loadVideo(url); + return await loadVideo(url) } - return blob; + return blob } diff --git a/src/utils/blob2DataURI.ts b/src/utils/blob2DataURI.ts index 7cabbab9..61fe101c 100644 --- a/src/utils/blob2DataURI.ts +++ b/src/utils/blob2DataURI.ts @@ -1,8 +1,8 @@ -export default function blob2DataURI(blob: Blob) { - const fileReader = new FileReader(); - return new Promise((resolve, reject) => { - fileReader.onerror = () => reject(new Error('FileReader error')); - fileReader.onload = (evt: ProgressEvent) => resolve(evt.target?.result); - fileReader.readAsDataURL(blob); - }); +export default async function blob2DataURI (blob: Blob) { + const fileReader = new FileReader() + return await new Promise((resolve, reject) => { + fileReader.onerror = () => { reject(new Error('FileReader error')) } + fileReader.onload = (evt: ProgressEvent) => { resolve(evt.target?.result) } + fileReader.readAsDataURL(blob) + }) } diff --git a/src/utils/blobURI2DataURI.ts b/src/utils/blobURI2DataURI.ts index 59c8c44b..547051d6 100644 --- a/src/utils/blobURI2DataURI.ts +++ b/src/utils/blobURI2DataURI.ts @@ -1,7 +1,7 @@ -import blob2DataURI from './blob2DataURI'; +import blob2DataURI from './blob2DataURI' -export default function blobURI2DataURI(blobURI: string) { - return fetch(blobURI) - .then((res) => res.blob()) - .then((blob) => blob2DataURI(blob)); +export default async function blobURI2DataURI (blobURI: string) { + return await fetch(blobURI) + .then(async (res) => await res.blob()) + .then(async (blob) => await blob2DataURI(blob)) } diff --git a/src/utils/com.spec.ts b/src/utils/com.spec.ts index fc29ab70..c8094a17 100644 --- a/src/utils/com.spec.ts +++ b/src/utils/com.spec.ts @@ -1,4 +1,4 @@ -import * as com from './com'; +import * as com from './com' // const expectedError = new Error('Expected'); @@ -6,117 +6,117 @@ const handlers = { actionA: jest.fn(() => { }), actionB: jest.fn(async () => 'B'), actionC: jest.fn(async () => { - throw new Error('Expected'); - }), -}; + throw new Error('Expected') + }) +} describe('com.messenger', () => { it('properly use the posting function', () => { - const post = jest.fn(); - const messenger = com.makeChannelPost(post, 'side-1'); - messenger('actionA'); + const post = jest.fn() + const messenger = com.makeChannelPost(post, 'side-1') + messenger('actionA') - expect(post).toHaveBeenCalledTimes(1); + expect(post).toHaveBeenCalledTimes(1) - const [args] = post.mock.calls; - expect(args).toHaveLength(1); - expect(args[0]).toHaveProperty('type', 'actionA'); - expect(args[0]).toHaveProperty('payload'); - expect(args[0]).toHaveProperty('meta.source', 'side-1'); - expect(args[0]).toHaveProperty('meta.sent'); - expect(args[0]).not.toHaveProperty('meta.operationId'); - }); + const [args] = post.mock.calls + expect(args).toHaveLength(1) + expect(args[0]).toHaveProperty('type', 'actionA') + expect(args[0]).toHaveProperty('payload') + expect(args[0]).toHaveProperty('meta.source', 'side-1') + expect(args[0]).toHaveProperty('meta.sent') + expect(args[0]).not.toHaveProperty('meta.operationId') + }) it('can use async', async () => { - const post = jest.fn(); - const messenger = com.makeChannelPost(post, 'side-1'); + const post = jest.fn() + const messenger = com.makeChannelPost(post, 'side-1') - const promise = messenger('actionB', {}, true); - expect(post).toHaveBeenCalledTimes(1); - expect(promise).toHaveProperty('then'); + const promise = messenger('actionB', {}, true) + expect(post).toHaveBeenCalledTimes(1) + expect(promise).toHaveProperty('then') - const [args] = post.mock.calls; + const [args] = post.mock.calls - expect(args).toHaveLength(1); - expect(args[0]).toHaveProperty('type', 'actionB'); - expect(args[0]).toHaveProperty('payload'); - expect(args[0]).toHaveProperty('meta.source', 'side-1'); - expect(args[0]).toHaveProperty('meta.sent'); - expect(args[0]).toHaveProperty('meta.operationId'); + expect(args).toHaveLength(1) + expect(args[0]).toHaveProperty('type', 'actionB') + expect(args[0]).toHaveProperty('payload') + expect(args[0]).toHaveProperty('meta.source', 'side-1') + expect(args[0]).toHaveProperty('meta.sent') + expect(args[0]).toHaveProperty('meta.operationId') - const postBack = jest.fn(); - const listener2 = com.makeChannelListener(postBack, handlers); + const postBack = jest.fn() + const listener2 = com.makeChannelListener(postBack, handlers) - const listener1 = com.makeChannelListener(post, {}); + const listener1 = com.makeChannelListener(post, {}) listener2({ - data: { ...args[0] }, - } as MessageEvent); + data: { ...args[0] } + } as MessageEvent) - await new Promise((res) => setTimeout(res, 1)); - expect(postBack).toHaveBeenCalledTimes(1); + await new Promise((res) => setTimeout(res, 1)) + expect(postBack).toHaveBeenCalledTimes(1) listener1({ - data: postBack.mock.calls[0][0], - } as MessageEvent); + data: postBack.mock.calls[0][0] + } as MessageEvent) - await new Promise((res) => setTimeout(res, 1)); - await expect(promise).resolves.toBe('B'); - }); + await new Promise((res) => setTimeout(res, 1)) + await expect(promise).resolves.toBe('B') + }) it('handles async error', async () => { - const post = jest.fn(); - const messenger = com.makeChannelPost(post, 'side-1'); + const post = jest.fn() + const messenger = com.makeChannelPost(post, 'side-1') - const listener1 = com.makeChannelListener(post, {}); + const listener1 = com.makeChannelListener(post, {}) - const postBack = jest.fn(); - const listener2 = com.makeChannelListener(postBack, handlers); - const promise: Promise = messenger('actionC', {}, true); + const postBack = jest.fn() + const listener2 = com.makeChannelListener(postBack, handlers) + const promise: Promise = messenger('actionC', {}, true) - const [args] = post.mock.calls; + const [args] = post.mock.calls listener2({ - data: { ...args[0] }, - } as MessageEvent); + data: { ...args[0] } + } as MessageEvent) - expect(post).toHaveBeenCalledTimes(1); - expect(promise).toHaveProperty('then'); + expect(post).toHaveBeenCalledTimes(1) + expect(promise).toHaveProperty('then') - await new Promise((res) => setTimeout(res, 1)); - expect(postBack).toHaveBeenCalledTimes(1); + await new Promise((res) => setTimeout(res, 1)) + expect(postBack).toHaveBeenCalledTimes(1) - expect(postBack.mock.calls[0][0]).toHaveProperty('meta.error', 'Expected'); + expect(postBack.mock.calls[0][0]).toHaveProperty('meta.error', 'Expected') const postBackEvent = { - data: postBack.mock.calls[0][0], - } as MessageEvent; + data: postBack.mock.calls[0][0] + } as MessageEvent - listener1(postBackEvent); + listener1(postBackEvent) - await expect(promise).rejects.toThrow('Expected'); - }); -}); + await expect(promise).rejects.toThrow('Expected') + }) +}) describe('com.autoBind', () => { it('creates the post and listener', async () => { - let post: com.ChannelPost; - let listener: com.ComMessageEventListener; + let post: com.ChannelPost + let listener: com.ComMessageEventListener const obj = { - postMessage: jest.fn(), - }; + postMessage: jest.fn() + } expect(() => { - const api = com.autoBind(obj, 'some-name', handlers); - post = api.post; - listener = api.listener; - }).not.toThrow(); + const api = com.autoBind(obj, 'some-name', handlers) + post = api.post + listener = api.listener - expect(typeof post).toBe('function'); - expect(typeof listener).toBe('function'); + expect(typeof post).toBe('function') + expect(typeof listener).toBe('function') - post('actionA'); + post('actionA') + }).not.toThrow() - expect(obj.postMessage).toHaveBeenCalledTimes(1); + expect(obj.postMessage).toHaveBeenCalledTimes(1) // const postPromise = post('actionB', null, true); // const [[response]] = obj.postMessage.mock.calls; @@ -128,5 +128,5 @@ describe('com.autoBind', () => { // await expect(postPromise).resolves.toBe('B'); // expect(obj.postMessage).toHaveBeenCalledTimes(2); - }); -}); + }) +}) diff --git a/src/utils/com.ts b/src/utils/com.ts index a70abe49..d6aba198 100644 --- a/src/utils/com.ts +++ b/src/utils/com.ts @@ -1,153 +1,137 @@ export interface ComEventDataMeta { - [custom: string]: any; - operationId?: string; - sent?: number; - received?: number; - processed?: number; - answered?: number; - source?: string; - error?: string; + [custom: string]: any + operationId?: string + sent?: number + received?: number + processed?: number + answered?: number + source?: string + error?: string } export interface ComEventData { - type: string; - payload?: any; - meta?: ComEventDataMeta; + type: string + payload?: any + meta?: ComEventDataMeta } -export interface ComActionHandler { - (payload?: any, meta?: ComEventDataMeta): Promise | any -} +export type ComActionHandler = (payload?: any, meta?: ComEventDataMeta) => Promise | any -export type ComActionHandlers = { - [type: string]: ComActionHandler; -}; +export type ComActionHandlers = Record -export interface ComAnswerer { - (type: ComEventData): any; -} +export type ComAnswerer = (type: ComEventData) => any -const promises: { - [operationId: string]: (err: any, result?: any) => void; -} = {}; +const promises: Record void> = {} -export interface Poster { - (message: ComEventData): any; -} +export type Poster = (message: ComEventData) => any -export interface ChannelPost { - (type: string, payload?: any, originalMeta?: ComEventDataMeta | true): Promise -} +export type ChannelPost = (type: string, payload?: any, originalMeta?: ComEventDataMeta | true) => Promise -export interface ChannelPostMaker { - (poster: Poster, source: string): ChannelPost; -} +export type ChannelPostMaker = (poster: Poster, source: string) => ChannelPost export interface ComMessageEvent { data: ComEventData } -export interface ComMessageEventListener { - (event: ComMessageEvent): void -} +export type ComMessageEventListener = (event: ComMessageEvent) => void -export interface ChannelListenerMaker { - (postBack: ComAnswerer, handlers: ComActionHandlers): ComMessageEventListener; -} +export type ChannelListenerMaker = (postBack: ComAnswerer, handlers: ComActionHandlers) => ComMessageEventListener // eslint-disable-next-line max-len -export const makeChannelPost: ChannelPostMaker = (poster, source) => (type, payload, originalMeta) => { +export const makeChannelPost: ChannelPostMaker = (poster, source) => async (type, payload, originalMeta) => { const meta: ComEventDataMeta = { - ...(originalMeta === true ? { - operationId: `${Date.now()}-${source}-${(Math.random() * 1000000).toFixed()}`, - } : originalMeta || {}), + ...(originalMeta === true + ? { + operationId: `${Date.now()}-${source}-${(Math.random() * 1000000).toFixed()}` + } + : (originalMeta != null) || {}), sent: Date.now(), - source, - }; + source + } if (meta.operationId) { - const { operationId } = meta; + const { operationId } = meta const promise = new Promise((res, rej) => { - if (promises[operationId]) throw new Error('Promise already exists'); + if (promises[operationId]) throw new Error('Promise already exists') promises[operationId] = (err, result) => { if (err) { - rej(err); - return; + rej(err) + return } - res(result); - }; - poster({ type, payload, meta }); - }); - return promise; + res(result) + } + poster({ type, payload, meta }) + }) + return await promise } poster({ type, payload, - meta, - }); - return Promise.resolve(); -}; + meta + }) + await Promise.resolve() +} const handleComReply = (payload: any, meta: ComEventDataMeta) => { if (!meta.operationId) { - return; + return } if (typeof promises[meta.operationId] !== 'function') { - throw new Error('No promise found'); + throw new Error('No promise found') } - const cb = promises[meta.operationId]; + const cb = promises[meta.operationId] if (meta.error) { - cb(new Error(meta.error)); + cb(new Error(meta.error)) } else { - cb(null, payload); + cb(null, payload) } -}; +} // eslint-disable-next-line max-len const replyError = (postBack: ComAnswerer, err: Error | string, type: string, meta: ComEventDataMeta) => { - const error = typeof err === 'string' ? err : err.message || 'Unexpected error'; + const error = typeof err === 'string' ? err : err.message || 'Unexpected error' postBack({ type: 'com/reply', meta: { ...meta, originalType: type, processed: Date.now(), - error, - }, - }); -}; + error + } + }) +} export const makeChannelListener: ChannelListenerMaker = (postBack, handlers) => (event) => { const { type, payload, - meta: originalMeta, - } = event.data; + meta: originalMeta + } = event.data const meta: ComEventDataMeta = { ...originalMeta, - received: Date.now(), - }; + received: Date.now() + } if (type === 'com/reply' && meta.operationId) { - handleComReply(payload, meta); - return; + handleComReply(payload, meta) + return } - const { [event.data.type]: handler } = handlers; + const { [event.data.type]: handler } = handlers if (!handler) { - const err = `Unexepected ${type} action type`; + const err = `Unexepected ${type} action type` - if (!meta.operationId) return; + if (!meta.operationId) return - replyError(postBack, err, type, meta); - return; + replyError(postBack, err, type, meta) + return } if (!meta.operationId) { - handler(payload, meta); - return; + handler(payload, meta) + return } handler(payload, meta) @@ -157,28 +141,28 @@ export const makeChannelListener: ChannelListenerMaker = (postBack, handlers) => meta: { ...meta, originalType: type, - processed: Date.now(), - }, + processed: Date.now() + } })) - .catch((err: Error) => replyError(postBack, err, type, meta)); -}; + .catch((err: Error) => { replyError(postBack, err, type, meta) }) +} export interface ComMessageChannel { - postMessage: Poster; - addEventListener?: (eventName: string, listener: (event: any) => void) => void; + postMessage: Poster + addEventListener?: (eventName: string, listener: (event: any) => void) => void } -export type ComChannel = ComMessageChannel; +export type ComChannel = ComMessageChannel export interface ChannelBindings { - post: ChannelPost; - listener: (event: ComMessageEvent) => void; + post: ChannelPost + listener: (event: ComMessageEvent) => void } export const autoBind = (obj: ComChannel, source: string, handlers: ComActionHandlers) => { - const originalPost = obj.postMessage.bind(obj) as Poster; + const originalPost = obj.postMessage.bind(obj) return { post: makeChannelPost(originalPost, source), - listener: makeChannelListener(originalPost, handlers), - }; -}; + listener: makeChannelListener(originalPost, handlers) + } +} diff --git a/src/utils/dataURI2Blob.ts b/src/utils/dataURI2Blob.ts index b53558ca..5e5adfee 100644 --- a/src/utils/dataURI2Blob.ts +++ b/src/utils/dataURI2Blob.ts @@ -1,5 +1,5 @@ // borrowed from https://stackoverflow.com/questions/12168909/blob-from-dataurl -export default function dataURI2Blob(dataURI, mimeType) { +export default function dataURI2Blob (dataURI, mimeType) { // const binary = atob(dataURI.split(',')[1]); // const array = []; // for (let i = 0; i < binary.length; i += 1) array.push(binary.charCodeAt(i)); @@ -7,23 +7,23 @@ export default function dataURI2Blob(dataURI, mimeType) { // convert base64 to raw binary data held in a string // doesn't handle URLEncoded DataURIs - see SO answer #6850276 for code that does this - const byteString = atob(dataURI.split(',')[1]); + const byteString = atob(dataURI.split(',')[1]) // separate out the mime component - const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; + const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0] // write the bytes of the string to an ArrayBuffer - const ab = new ArrayBuffer(byteString.length); + const ab = new ArrayBuffer(byteString.length) // create a view into the buffer - const ia = new Uint8Array(ab); + const ia = new Uint8Array(ab) // set the bytes of the buffer to the correct values for (let i = 0; i < byteString.length; i += 1) { - ia[i] = byteString.charCodeAt(i); + ia[i] = byteString.charCodeAt(i) } // write the ArrayBuffer to a blob, and you're done - const blob = new Blob([ab], { type: mimeType || mimeString }); - return blob; + const blob = new Blob([ab], { type: mimeType || mimeString }) + return blob } diff --git a/src/utils/deprecate.ts b/src/utils/deprecate.ts index a86323da..4d4334d2 100644 --- a/src/utils/deprecate.ts +++ b/src/utils/deprecate.ts @@ -1,12 +1,12 @@ -const warned: string[] = []; -export default function deprecate(fn: any, message: string) { - const { name, displayName } = fn; +const warned: string[] = [] +export default function deprecate (fn: any, message: string) { + const { name, displayName } = fn return (...args: any[]) => { - const cached = name || displayName || 'anonymous'; + const cached = name || displayName || 'anonymous' if (!warned.includes(cached)) { - warned.push(cached); - console.warn('[deprecated] %s: %s', cached, message); + warned.push(cached) + console.warn('[deprecated] %s: %s', cached, message) } - return fn.apply(null, ...args); - }; + return fn.apply(null, ...args) + } } diff --git a/src/utils/mathTools.ts b/src/utils/mathTools.ts index 5504848e..7a75d01f 100644 --- a/src/utils/mathTools.ts +++ b/src/utils/mathTools.ts @@ -1,6 +1,6 @@ /* eslint-disable no-restricted-properties */ /* eslint-disable @typescript-eslint/naming-convention */ -type Box = { width: number, height: number }; +interface Box { width: number, height: number } const { abs, @@ -45,8 +45,8 @@ const { LOG2E, PI, SQRT1_2, - SQRT2, -} = Math; + SQRT2 +} = Math export { abs, acos, @@ -90,87 +90,87 @@ export { LOG2E, PI, SQRT1_2, - SQRT2, -}; + SQRT2 +} -export const PI2 = Math.PI * 2; +export const PI2 = Math.PI * 2 -export const GR = 1.6180339887498948482; +export const GR = 1.6180339887498948482 -export const sDiv = (val: number, div: number): number => val * (1 / div); +export const sDiv = (val: number, div: number): number => val * (1 / div) export const arrayMax = (arr: number[]) => - arr.reduce((val, prev) => Math.max(val, prev), 0); + arr.reduce((val, prev) => Math.max(val, prev), 0) export const arrayMin = (arr: number[]) => - arr.reduce((val, prev) => Math.min(val, prev), Infinity); + arr.reduce((val, prev) => Math.min(val, prev), Infinity) export const arraySum = (arr: number[]) => - arr.reduce((val, prev) => val + prev, 0); + arr.reduce((val, prev) => val + prev, 0) export const arrayDiff = (arr: number[]) => - (Math.abs(arrayMax(arr) - arrayMin(arr))); + (Math.abs(arrayMax(arr) - arrayMin(arr))) export const arrayAvg = (arr: number[]) => - sDiv(arraySum(arr), arr.length); + sDiv(arraySum(arr), arr.length) export const arrayMirror = (arr: number[]) => - [...arr, ...arr.reverse()]; + [...arr, ...arr.reverse()] export const arrayDownsample = (arr: number[], samples = 2) => { - const result: number[] = []; + const result: number[] = [] arr.forEach((item, i) => { - if ((i % samples) === 0) result.push(item); - }); - return result; -}; + if ((i % samples) === 0) result.push(item) + }) + return result +} export const arraySmooth = (arr: number[], factor = 2) => arr.reduce((acc: number[], val: number, index: number) => { - acc.push(arrayAvg(arr.slice(index, index + factor))); - return acc; - }, []); + acc.push(arrayAvg(arr.slice(index, index + factor))) + return acc + }, []) -export const deg2rad = (deg:number) => (PI2 / 360) * deg; +export const deg2rad = (deg: number) => (PI2 / 360) * deg -export const rad2deg = (rad:number) => (360 / PI2) * rad; +export const rad2deg = (rad: number) => (360 / PI2) * rad -export const cap = (val:number, minVal = 0, maxVal = 127) => - Math.min(Math.max(val, minVal), maxVal); +export const cap = (val: number, minVal = 0, maxVal = 127) => + Math.min(Math.max(val, minVal), maxVal) -export const between = (val:number, minVal = 0, maxVal = 127) => - val < maxVal && val > minVal; +export const between = (val: number, minVal = 0, maxVal = 127) => + val < maxVal && val > minVal -export const beatPrct = (now:number, bpm = 120) => { - const timeBetweenBeats = sDiv(60, bpm) * 1000; - return cap(sDiv(now % timeBetweenBeats, timeBetweenBeats), 0, 1); -}; +export const beatPrct = (now: number, bpm = 120) => { + const timeBetweenBeats = sDiv(60, bpm) * 1000 + return cap(sDiv(now % timeBetweenBeats, timeBetweenBeats), 0, 1) +} -export const beat = (now:number, bpm = 120) => { +export const beat = (now: number, bpm = 120) => { // eslint-disable-next-line no-console - console.log('[DERECATED]: beat(), use beatPrct() instead'); - return beatPrct(now, bpm); -}; + console.log('[DERECATED]: beat(), use beatPrct() instead') + return beatPrct(now, bpm) +} export const orientation = (width: number, height: number) => - (width >= height ? 'landscape' : 'portrait'); + (width >= height ? 'landscape' : 'portrait') -export const objOrientation = (obj: Box) => orientation(obj.width, obj.height); +export const objOrientation = (obj: Box) => orientation(obj.width, obj.height) export const containBox = (box1: Box, box2: Box) => { - const { width, height } = box1; - const { width: box2Width, height: box2Height } = box2; - const { width: box1Width, height: box1Height } = box1; - const x = (box2Width / box1Width) * width; - const y = (box2Height / box1Height) * height; - return { width: x, height: y }; -}; + const { width, height } = box1 + const { width: box2Width, height: box2Height } = box2 + const { width: box1Width, height: box1Height } = box1 + const x = (box2Width / box1Width) * width + const y = (box2Height / box1Height) * height + return { width: x, height: y } +} export const coverBox = (box1: Box, box2: Box) => { - const { width, height } = box1; - const { width: box2Width, height: box2Height } = box2; - const { width: box1Width, height: box1Height } = box1; - const x = (box1Width / box2Width) * width; - const y = (box1Height / box2Height) * height; - return { width: x, height: y }; -}; + const { width, height } = box1 + const { width: box2Width, height: box2Height } = box2 + const { width: box1Width, height: box1Height } = box1 + const x = (box1Width / box2Width) * width + const y = (box1Height / box2Height) * height + return { width: x, height: y } +} diff --git a/src/utils/miscTools.ts b/src/utils/miscTools.ts index 41eacca0..eb75edd8 100644 --- a/src/utils/miscTools.ts +++ b/src/utils/miscTools.ts @@ -1,11 +1,9 @@ -import blobURI2DataURI from './blobURI2DataURI'; +import blobURI2DataURI from './blobURI2DataURI' -interface ReadInterface { - (name: string, defaultValue?: any): any -} +type ReadInterface = (name: string, defaultValue?: any) => any // eslint-disable-next-line @typescript-eslint/no-unused-vars -export const noop = (...args: any[]): any => {}; +export const noop = (...args: any[]): any => {} export const rgba = (r = 0.5, g = 0.5, b = 0.5, a = 1) => `rgba(${ @@ -16,7 +14,7 @@ export const rgba = (r = 0.5, g = 0.5, b = 0.5, a = 1) => (b * 255).toFixed() }, ${ a.toFixed(3) - })`; + })` export const hsla = (h = 0.5, s = 0.5, l = 0.5, a = 1) => `hsla(${ @@ -27,58 +25,58 @@ export const hsla = (h = 0.5, s = 0.5, l = 0.5, a = 1) => (l * 100).toFixed() }%, ${ a.toFixed(3) - })`; + })` export const repeat = (times = 1, func = noop) => { for (let t = 0; t < times; t += 1) { - func(t, times); + func(t, times) } -}; +} export const assetDataURI = async (asset: any) => { - const uri = await blobURI2DataURI(asset.src); - return uri; -}; + const uri = await blobURI2DataURI(asset.src) + return uri +} -export const isFunction = (what: any) => typeof what === 'function'; +export const isFunction = (what: any) => typeof what === 'function' -export const toggled: { [key: string]: boolean } = {}; -export const prevToggle: { [key: string]: any } = {}; +export const toggled: Record = {} +export const prevToggle: Record = {} export const toggle = (read: ReadInterface, name: string) => (on: any, off: any) => { - const val = read(name); - if (prevToggle[name] !== val && val) toggled[name] = !toggled[name]; - if (toggled[name] && isFunction(on)) on(); - if (!toggled[name] && isFunction(off)) off(); - prevToggle[name] = val; - return toggled[name]; -}; + const val = read(name) + if (prevToggle[name] !== val && val) toggled[name] = !toggled[name] + if (toggled[name] && isFunction(on)) on() + if (!toggled[name] && isFunction(off)) off() + prevToggle[name] = val + return toggled[name] +} -export const inOut = (read:ReadInterface, name:string) => (on: any, off: any) => { - const val = read(name); - if (val && isFunction(on)) on(); - if (!val && isFunction(off)) off(); - return val; -}; +export const inOut = (read: ReadInterface, name: string) => (on: any, off: any) => { + const val = read(name) + if (val && isFunction(on)) on() + if (!val && isFunction(off)) off() + return val +} -export const steps: { [key: string]: number } = {}; -export const prevStepVals: { [key: string]: any } = {}; -export const stepper = (read:ReadInterface, name:string, distance = 1) => { - const val = read(name, 0); - steps[name] = steps[name] || 0; - if (!prevStepVals[name] && val) steps[name] += distance; - prevStepVals[name] = val; - return steps[name]; -}; +export const steps: Record = {} +export const prevStepVals: Record = {} +export const stepper = (read: ReadInterface, name: string, distance = 1) => { + const val = read(name, 0) + steps[name] = steps[name] || 0 + if (!prevStepVals[name] && val) steps[name] += distance + prevStepVals[name] = val + return steps[name] +} -export const merge = (...objs: { [k: string]: any }[]) => { - const result: { [k: string]: any } = {}; +export const merge = (...objs: Array>) => { + const result: Record = {} objs.forEach((obj) => { Object.keys(obj).forEach((key) => { - result[key] = obj[key]; - }); - }); - return result; -}; + result[key] = obj[key] + }) + }) + return result +} const tools = { rgba, @@ -90,9 +88,9 @@ const tools = { toggle, inOut, stepper, - merge, -}; + merge +} // export const apiReference = reference; -export default tools; +export default tools diff --git a/src/webviews/ComContext.tsx b/src/webviews/ComContext.tsx index b1ce76b4..f8f04191 100644 --- a/src/webviews/ComContext.tsx +++ b/src/webviews/ComContext.tsx @@ -1,63 +1,63 @@ -import * as React from 'react'; -import { ChannelPost } from '../utils/com'; +import * as React from 'react' +import { type ChannelPost } from '../utils/com' // A context that can be used to send messages to the VSCode extension. // See https://code.visualstudio.com/docs/extensionAPI/api-comm-messages export interface ComContextInterface { - post: ChannelPost; + post: ChannelPost } -const ComContext = React.createContext({} as ComContextInterface); +const ComContext = React.createContext({} as ComContextInterface) -export const { Consumer } = ComContext; +export const { Consumer } = ComContext export interface ProviderProps { - children: React.ReactNode; - post: ChannelPost; + children: React.ReactNode + post: ChannelPost } export const Provider = ({ children, post }: ProviderProps) => ( {children} -); +) -export const useComContext = () => React.useContext(ComContext); +export const useComContext = () => React.useContext(ComContext) export const useComPost = () => { - const { post } = useComContext(); - return post; -}; + const { post } = useComContext() + return post +} export const useVSCOpen = () => { - const { post } = useComContext(); - return (relativePath: string) => post('openEditor', relativePath); -}; + const { post } = useComContext() + return async (relativePath: string) => await post('openEditor', relativePath) +} export const useSetBPM = () => { - const { post } = useComContext(); - return (bpm: number) => post('setBPM', bpm); -}; + const { post } = useComContext() + return async (bpm: number) => await post('setBPM', bpm) +} export const useSetStageSize = () => { - const { post } = useComContext(); - return (size: { - width: number; - height: number; - }) => post('setStageSize', size); -}; + const { post } = useComContext() + return async (size: { + width: number + height: number + }) => await post('setStageSize', size) +} export const useToggleLayer = () => { - const { post } = useComContext(); - return (id: string) => post('toggleLayer', id); -}; + const { post } = useComContext() + return async (id: string) => await post('toggleLayer', id) +} export const useCreateLayer = () => { - const { post } = useComContext(); - return (id: string, type: string) => post('createLayer', { id, type }); -}; + const { post } = useComContext() + return async (id: string, type: string) => await post('createLayer', { id, type }) +} export const useRemoveLayer = () => { - const { post } = useComContext(); - return (id: string) => post('removeLayer', id); -}; + const { post } = useComContext() + return async (id: string) => await post('removeLayer', id) +} diff --git a/src/webviews/components/AppInfo.tsx b/src/webviews/components/AppInfo.tsx index e7628f14..d1fe15bd 100644 --- a/src/webviews/components/AppInfo.tsx +++ b/src/webviews/components/AppInfo.tsx @@ -1,23 +1,23 @@ -import * as React from 'react'; -import { useSelector } from 'react-redux'; -import { useVSCOpen } from '../ComContext'; -import { WebviewAppState } from '../store'; +import * as React from 'react' +import { useSelector } from 'react-redux' +import { useVSCOpen } from '../ComContext' +import { type WebviewAppState } from '../store' const AppInfo = () => { const { id, - stage, + stage // bpm: { count: bpm }, // server, - } = useSelector((state: WebviewAppState) => state); - const open = useVSCOpen(); + } = useSelector((state: WebviewAppState) => state) + const open = useVSCOpen() // const serverURL = `http://${server.host}:${server.port}`; const handleRCOpen = () => { - console.info('open fiha.json', open); - open('fiha.json'); - }; + console.info('open fiha.json', open) + open('fiha.json') + } return (
@@ -46,7 +46,7 @@ const AppInfo = () => {
- ); -}; + ) +} -export default AppInfo; +export default AppInfo diff --git a/src/webviews/components/Audio.tsx b/src/webviews/components/Audio.tsx index 976b2405..caf7b7a8 100644 --- a/src/webviews/components/Audio.tsx +++ b/src/webviews/components/Audio.tsx @@ -1,27 +1,27 @@ -import * as React from 'react'; -import { useSelector } from 'react-redux'; -import { useSetBPM } from '../ComContext'; -import { WebviewAppState } from '../store'; +import * as React from 'react' +import { useSelector } from 'react-redux' +import { useSetBPM } from '../ComContext' +import { type WebviewAppState } from '../store' const defaultState = { lastMeasure: 0, measuresCount: 0, - seriesStart: 0, -}; + seriesStart: 0 +} const Audio = () => { const { bpm: { count: bpm }, - server, - } = useSelector((state: WebviewAppState) => state); - const setBPM = useSetBPM(); + server + } = useSelector((state: WebviewAppState) => state) + const setBPM = useSetBPM() const [{ lastMeasure, measuresCount, - seriesStart, - }, setState] = React.useState(defaultState); - const serverURL = `http://${server.host}:${server.port}`; + seriesStart + }, setState] = React.useState(defaultState) + const serverURL = `http://${server.host}:${server.port}` const openCaptureLink = ( { > {`${serverURL}/capture/`} - ); + ) - const newBPM = Math.round(60000 / ((Date.now() - seriesStart) * (1 / (measuresCount)))); + const newBPM = Math.round(60000 / ((Date.now() - seriesStart) * (1 / (measuresCount)))) const handleBPMClick = () => { - const now = Date.now(); + const now = Date.now() if ((now - lastMeasure) > 2000) { setState({ lastMeasure: now, measuresCount: 0, - seriesStart: now, - }); - return; + seriesStart: now + }) + return } if (measuresCount > 2) { - setBPM(newBPM); + setBPM(newBPM) } setState({ lastMeasure: now, measuresCount: measuresCount + 1, - seriesStart: seriesStart || now, - }); - }; + seriesStart: seriesStart || now + }) + } return (
@@ -76,7 +76,7 @@ const Audio = () => { borderRadius: 40, margin: 20, width: 60, - height: 60, + height: 60 }} type="button" onClick={handleBPMClick} @@ -89,7 +89,7 @@ const Audio = () => {
- ); -}; + ) +} -export default Audio; +export default Audio diff --git a/src/webviews/components/ControlDisplay.tsx b/src/webviews/components/ControlDisplay.tsx index 1061146b..d5897cdc 100644 --- a/src/webviews/components/ControlDisplay.tsx +++ b/src/webviews/components/ControlDisplay.tsx @@ -1,14 +1,14 @@ -import * as React from 'react'; -import { useSelector } from 'react-redux'; -import { AppState } from '../../types'; +import * as React from 'react' +import { useSelector } from 'react-redux' +import { type AppState } from '../../types' const ControlDisplay = () => { - const info = useSelector((state: AppState) => ({ ...state.server, ...state.stage })); - const iframeRef = React.useRef(null); - const src = `http://${info.host}:${info.port}/display/#control`; + const info = useSelector((state: AppState) => ({ ...state.server, ...state.stage })) + const iframeRef = React.useRef(null) + const src = `http://${info.host}:${info.port}/display/#control` const handleReload = () => { - if (iframeRef.current) iframeRef.current.src = src; - }; + if (iframeRef.current != null) iframeRef.current.src = src + } return (