This commit is contained in:
AlexandruPopovici 2024-07-16 10:22:38 +03:00
Родитель 3142412509
Коммит e04510577f
10 изменённых файлов: 576 добавлений и 92 удалений

Просмотреть файл

@ -58,6 +58,7 @@
"@speckle/objectloader": "workspace:^",
"@speckle/shared": "workspace:^",
"@types/flat": "^5.0.2",
"canvas": "^2.11.2",
"flat": "^5.0.2",
"js-logger": "1.6.1",
"lodash-es": "^4.17.21",

Просмотреть файл

@ -34,15 +34,15 @@ export interface ViewerParams {
}
export enum AssetType {
TEXTURE_8BPP = 'png', // For now
TEXTURE_HDR = 'hdr',
TEXTURE_EXR = 'exr',
FONT_JSON = 'font-json'
}
export interface Asset {
id: string
src: string
type: AssetType
src?: string
contentsBuffer?: ArrayBuffer
}
/**

Просмотреть файл

@ -6,32 +6,31 @@ import {
DataTexture,
DataTextureLoader,
Matrix4,
Euler
Euler,
ClampToEdgeWrapping,
LinearFilter,
FloatType
} from 'three'
import { EXRLoader } from 'three/examples/jsm/loaders/EXRLoader.js'
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js'
import { FontLoader, Font } from 'three/examples/jsm/loaders/FontLoader.js'
import { type Asset, AssetType } from '../IViewer'
import Logger from 'js-logger'
import { RotatablePMREMGenerator } from './objects/RotatablePMREMGenerator'
import { Image as CanvasImage, createCanvas } from 'canvas'
export class Assets {
private static _cache: { [name: string]: Texture | Font } = {}
public static _cache: { [name: string]: Texture | Font } = {}
private static getLoader(
src: string,
assetType: AssetType
): TextureLoader | DataTextureLoader | null {
if (assetType === undefined) assetType = src.split('.').pop() as AssetType
if (!Object.values(AssetType).includes(assetType)) {
Logger.warn(`Asset ${src} could not be loaded. Unknown type`)
Logger.warn(`Asset ${assetType} could not be loaded. Unknown type`)
return null
}
switch (assetType) {
case AssetType.TEXTURE_EXR:
return new EXRLoader()
case AssetType.TEXTURE_HDR:
return new RGBELoader()
case AssetType.TEXTURE_8BPP:
return new TextureLoader()
default:
@ -61,19 +60,40 @@ export class Assets {
}
return new Promise<Texture>((resolve, reject) => {
const loader = Assets.getLoader(asset.src, asset.type)
const loader = Assets.getLoader(asset.type)
if (loader) {
loader.load(
asset.src,
(texture) => {
this._cache[asset.id] = texture
resolve(Assets.hdriToPMREM(renderer, texture))
},
undefined,
(error: ErrorEvent) => {
reject(`Loading asset ${asset.id} failed ${error.message}`)
}
)
if (asset.src) {
loader.load(
asset.src,
(texture) => {
this._cache[asset.id] = texture
resolve(Assets.hdriToPMREM(renderer, texture))
},
undefined,
(error: ErrorEvent) => {
reject(`Loading asset ${asset.id} failed ${error.message}`)
}
)
} else if (asset.contentsBuffer) {
const texData = (loader as EXRLoader).parse(asset.contentsBuffer)
if (!texData) reject(`Loading asset ${asset.id} failed`)
const texture = new DataTexture(
texData.data,
texData.width,
texData.height,
texData.format,
FloatType,
undefined,
ClampToEdgeWrapping,
ClampToEdgeWrapping,
LinearFilter,
LinearFilter,
1
)
texture.needsUpdate = true
this._cache[asset.id] = texture
resolve(Assets.hdriToPMREM(renderer, texture))
}
} else {
reject(`Loading asset ${asset.id} failed`)
}
@ -85,64 +105,80 @@ export class Assets {
return Promise.resolve(this._cache[asset.id] as Texture)
}
return new Promise<Texture>((resolve, reject) => {
// Hack to load 'data:image's - for some reason, the frontend receives the default
// gradient map as a data image url, rather than a file (?).
if (asset.src.includes('data:image')) {
const image = new Image()
image.src = asset.src
if (asset.src) {
if (asset.src.includes('data:image')) {
const image = new CanvasImage()
image.src = asset.src
image.onload = () => {
const canvas = createCanvas(image.width, image.height)
const ctx = canvas.getContext('2d')
ctx.drawImage(image, 0, 0)
const texture = new Texture(new Image())
texture.needsUpdate = true
this._cache[asset.id] = texture
resolve(texture)
}
image.onerror = (ev) => {
reject(`Loading asset ${asset.id} failed with ${ev.toString()}`)
}
} else {
const loader = Assets.getLoader(asset.type)
if (loader) {
if (asset.src) {
loader.load(
asset.src,
(texture) => {
this._cache[asset.id] = texture
resolve(this._cache[asset.id] as Texture)
},
undefined,
(error: ErrorEvent) => {
reject(`Loading asset ${asset.id} failed ${error.message}`)
}
)
}
} else {
reject(`Loading asset ${asset.id} failed`)
}
}
}
if (asset.contentsBuffer) {
const image = new CanvasImage()
image.onload = () => {
const texture = new Texture(image)
const canvas = createCanvas(image.width, image.height)
const ctx = canvas.getContext('2d')
ctx.drawImage(image, 0, 0)
const texture = new Texture(new Image())
texture.needsUpdate = true
this._cache[asset.id] = texture
resolve(texture)
}
image.onerror = (ev) => {
reject(`Loading asset ${asset.id} failed with ${ev.toString()}`)
}
} else {
const loader = Assets.getLoader(asset.src, asset.type)
if (loader) {
loader.load(
asset.src,
(texture) => {
this._cache[asset.id] = texture
resolve(this._cache[asset.id] as Texture)
},
undefined,
(error: ErrorEvent) => {
reject(`Loading asset ${asset.id} failed ${error.message}`)
}
)
} else {
reject(`Loading asset ${asset.id} failed`)
image.onerror = (err) => {
throw err
}
image.src = Buffer.from(asset.contentsBuffer)
}
})
}
public static getFont(asset: Asset | string): Promise<Font> {
let srcUrl: string | null = null
if ((<Asset>asset).src) {
srcUrl = (asset as Asset).src
} else {
srcUrl = asset as string
}
if (this._cache[srcUrl]) {
return Promise.resolve(this._cache[srcUrl] as Font)
public static getFont(asset: Asset): Promise<Font> {
if (this._cache[asset.id]) {
return Promise.resolve(this._cache[asset.id] as Font)
}
return new Promise<Font>((resolve, reject) => {
new FontLoader().load(
srcUrl as string,
(font: Font) => {
resolve(font)
},
undefined,
(error: ErrorEvent) => {
reject(`Loading asset ${srcUrl} failed ${error.message}`)
}
)
if (asset.src) {
new FontLoader().load(
asset.src,
(font: Font) => {
resolve(font)
},
undefined,
(error: ErrorEvent) => {
reject(`Loading asset ${asset.id} failed ${error.message}`)
}
)
}
})
}

Просмотреть файл

@ -78,13 +78,17 @@ export class RenderingStats {
public batchCount: number = 0
public drawCalls: number = 0
public trisCount: number = 0
public lineCount: number = 0
public pointCount: number = 0
public vertCount: number = 0
public batchDetails!: Array<{
drawCalls: number
minDrawCalls: number
tris: number
verts: number
tris?: number
lines?: number
points?: number
}>
public frameStart() {
@ -133,6 +137,7 @@ export default class SpeckleRenderer {
public viewer: Viewer // TEMPORARY
public pipeline: Pipeline
public input: Input
public materials: Materials
/********************************
* Renderer and rendering flags */
@ -295,14 +300,24 @@ export default class SpeckleRenderer {
(a: number, c: Batch) => a + c.vertCount,
0
)),
(this._renderinStats.pointCount = batches.reduce(
(a: number, c: Batch) => a + c.pointCount,
0
)),
(this._renderinStats.lineCount = batches.reduce(
(a: number, c: Batch) => a + c.lineCount,
0
)),
(this._renderinStats.batchDetails = batches.map((batch: Batch) => {
return {
type: batch.constructor.name,
objCount: batch.renderViews.length,
drawCalls: batch.drawCalls,
minDrawCalls: batch.minDrawCalls,
tris: batch.triCount,
verts: batch.vertCount
verts: batch.vertCount,
...(batch.triCount && { tris: batch.triCount }),
...(batch.lineCount && { lines: batch.lineCount }),
...(batch.pointCount && { points: batch.pointCount })
}
}))
return this._renderinStats
@ -345,7 +360,9 @@ export default class SpeckleRenderer {
this._renderer.setSize(container.offsetWidth, container.offsetHeight)
container.appendChild(this._renderer.domElement)
this.materials = new Materials()
this.batcher = new Batcher(
this.materials,
this.renderer.capabilities.maxVertexUniforms,
this.renderer.capabilities.floatVertexTextures
)
@ -404,6 +421,10 @@ export default class SpeckleRenderer {
this._scene.add(this._shadowcatcher.shadowcatcherMesh)
}
public async init() {
await this.materials.createDefaultMaterials()
}
public update(deltaTime: number) {
if (!this.renderingCamera) return
this.batcher.update(deltaTime)
@ -709,11 +730,8 @@ export default class SpeckleRenderer {
return {
offset: value.batchStart,
count: value.batchCount,
material: this.batcher.materials.getFilterMaterial(
value,
material
) as Material,
materialOptions: this.batcher.materials.getFilterMaterialOptions(
material: this.materials.getFilterMaterial(value, material) as Material,
materialOptions: this.materials.getFilterMaterialOptions(
material
) as FilterMaterialOptions
}
@ -729,7 +747,7 @@ export default class SpeckleRenderer {
) {
for (const k in rvs) {
const drawRanges = rvs[k].map((value: NodeRenderView) => {
const material = this.batcher.materials.getDataMaterial(value, materialData)
const material = this.materials.getDataMaterial(value, materialData)
;(material as unknown as SpeckleMaterial).setMaterialOptions(materialData)
return {
offset: value.batchStart,

Просмотреть файл

@ -240,6 +240,7 @@ export class Viewer extends EventEmitter implements IViewer {
Logger.error('Fallback to null environment!')
})
}
await this.speckleRenderer.init()
}
on<T extends ViewerEvent>(

Просмотреть файл

@ -39,17 +39,20 @@ export default class Batcher {
private maxBatchObjects = 0
private maxBatchVertices = 500000
private minInstancedBatchVertices = 10000
public materials: Materials
private materials: Materials
public batches: { [id: string]: Batch } = {}
public constructor(maxUniformCount: number, floatTextures: boolean) {
public constructor(
materials: Materials,
maxUniformCount: number,
floatTextures: boolean
) {
this.maxHardwareUniformCount = maxUniformCount
this.maxBatchObjects = Math.floor(
(this.maxHardwareUniformCount - Materials.UNIFORM_VECTORS_USED) / 4
)
this.floatTextures = floatTextures
this.materials = new Materials()
void this.materials.createDefaultMaterials()
this.materials = materials
}
public async *makeBatches(

Просмотреть файл

@ -49,11 +49,20 @@ export default class LineBatch implements Batch {
}
public get triCount(): number {
return 0
const indexCount = this.geometry.index ? this.geometry.index.count : 0
const buffer = (
this.geometry.attributes.instanceStart as InterleavedBufferAttribute
).data as InstancedInterleavedBuffer
return (indexCount / 3) * buffer.meshPerAttribute * buffer.count
}
public get vertCount(): number {
return this.geometry.attributes.position.count * this.geometry.instanceCount
const buffer = (
this.geometry.attributes.instanceStart as InterleavedBufferAttribute
).data as InstancedInterleavedBuffer
return (
this.geometry.attributes.position.count * buffer.meshPerAttribute * buffer.count
)
}
public constructor(id: string, subtreeId: string, renderViews: NodeRenderView[]) {
@ -61,15 +70,19 @@ export default class LineBatch implements Batch {
this.subtreeId = subtreeId
this.renderViews = renderViews
}
public get pointCount(): number {
return 0
}
public get lineCount(): number {
/** Catering to typescript
* There is no unniverse where the geometry is non-indexed. LineSegments2 are **explicitly** indexed
*/
const indexCount = this.geometry.index ? this.geometry.index.count : 0
return (indexCount / 3) * (this.geometry as never)['_maxInstanceCount']
const buffer = (
this.geometry.attributes.instanceStart as InterleavedBufferAttribute
).data as InstancedInterleavedBuffer
return buffer.meshPerAttribute * buffer.count
}
public get renderObject(): Object3D {

Просмотреть файл

@ -1,16 +1,327 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import {
BufferAttribute,
BufferGeometry,
CubeUVReflectionMapping,
FloatType,
LinearEncoding,
LinearFilter,
Matrix4,
NoBlending,
PMREMGenerator,
RGBAFormat,
ShaderMaterial,
WebGLRenderer
Vector3,
WebGLRenderer,
WebGLRenderTarget,
WebGLRenderTargetOptions
} from 'three'
const MAX_SAMPLES = 20
const LOD_MIN = 4
const EXTRA_LOD_SIGMA = [0.125, 0.215, 0.35, 0.446, 0.526, 0.582]
export class RotatablePMREMGenerator extends PMREMGenerator {
constructor(renderer: WebGLRenderer) {
super(renderer)
}
_allocateTargets() {
//@ts-ignore
const width = 3 * Math.max(this._cubeSize, 16 * 7)
//@ts-ignore
const height = 4 * this._cubeSize
const params = {
magFilter: LinearFilter,
minFilter: LinearFilter,
generateMipmaps: false,
type: FloatType,
format: RGBAFormat,
encoding: LinearEncoding,
depthBuffer: false
}
//@ts-ignore
const cubeUVRenderTarget = this._createRenderTarget(width, height, params)
if (
//@ts-ignore
this._pingPongRenderTarget === null ||
//@ts-ignore
this._pingPongRenderTarget.width !== width
) {
//@ts-ignore
if (this._pingPongRenderTarget !== null) {
//@ts-ignore
this._dispose()
}
//@ts-ignore
this._pingPongRenderTarget = this._createRenderTarget(width, height, params)
//@ts-ignore
const { _lodMax } = this
;({
//@ts-ignore
sizeLods: this._sizeLods,
//@ts-ignore
lodPlanes: this._lodPlanes,
//@ts-ignore
sigmas: this._sigmas
//@ts-ignore
} = this._createPlanes(_lodMax))
//@ts-ignore
this._blurMaterial = this._getBlurShader(_lodMax, width, height)
}
return cubeUVRenderTarget
}
public _createRenderTarget(
width: number,
height: number,
params: WebGLRenderTargetOptions
) {
const cubeUVRenderTarget = new WebGLRenderTarget(width, height, params)
cubeUVRenderTarget.texture.mapping = CubeUVReflectionMapping
cubeUVRenderTarget.texture.name = 'PMREM.cubeUv'
cubeUVRenderTarget.scissorTest = true
return cubeUVRenderTarget
}
public _getBlurShader(lodMax: number, width: number, height: number) {
const weights = new Float32Array(MAX_SAMPLES)
const poleAxis = new Vector3(0, 1, 0)
const shaderMaterial = new ShaderMaterial({
name: 'SphericalGaussianBlur',
defines: {
n: MAX_SAMPLES,
CUBEUV_TEXEL_WIDTH: 1.0 / width,
CUBEUV_TEXEL_HEIGHT: 1.0 / height,
CUBEUV_MAX_MIP: `${lodMax}.0`
},
uniforms: {
envMap: { value: null },
samples: { value: 1 },
weights: { value: weights },
latitudinal: { value: false },
dTheta: { value: 0 },
mipInt: { value: 0 },
poleAxis: { value: poleAxis }
},
vertexShader: this._getCommonVertexShader(),
fragmentShader: /* glsl */ `
precision mediump float;
precision mediump int;
varying vec3 vOutputDirection;
uniform sampler2D envMap;
uniform int samples;
uniform float weights[ n ];
uniform bool latitudinal;
uniform float dTheta;
uniform float mipInt;
uniform vec3 poleAxis;
#define ENVMAP_TYPE_CUBE_UV
#include <cube_uv_reflection_fragment>
vec3 getSample( float theta, vec3 axis ) {
float cosTheta = cos( theta );
// Rodrigues' axis-angle rotation
vec3 sampleDirection = vOutputDirection * cosTheta
+ cross( axis, vOutputDirection ) * sin( theta )
+ axis * dot( axis, vOutputDirection ) * ( 1.0 - cosTheta );
return bilinearCubeUV( envMap, sampleDirection, mipInt );
}
void main() {
vec3 axis = latitudinal ? poleAxis : cross( poleAxis, vOutputDirection );
if ( all( equal( axis, vec3( 0.0 ) ) ) ) {
axis = vec3( vOutputDirection.z, 0.0, - vOutputDirection.x );
}
axis = normalize( axis );
gl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 );
gl_FragColor.rgb += weights[ 0 ] * getSample( 0.0, axis );
for ( int i = 1; i < n; i++ ) {
if ( i >= samples ) {
break;
}
float theta = dTheta * float( i );
gl_FragColor.rgb += weights[ i ] * getSample( -1.0 * theta, axis );
gl_FragColor.rgb += weights[ i ] * getSample( theta, axis );
}
}
`,
blending: NoBlending,
depthTest: false,
depthWrite: false
})
return shaderMaterial
}
public _getCommonVertexShader() {
return /* glsl */ `
precision mediump float;
precision mediump int;
attribute float faceIndex;
varying vec3 vOutputDirection;
// RH coordinate system; PMREM face-indexing convention
vec3 getDirection( vec2 uv, float face ) {
uv = 2.0 * uv - 1.0;
vec3 direction = vec3( uv, 1.0 );
if ( face == 0.0 ) {
direction = direction.zyx; // ( 1, v, u ) pos x
} else if ( face == 1.0 ) {
direction = direction.xzy;
direction.xz *= -1.0; // ( -u, 1, -v ) pos y
} else if ( face == 2.0 ) {
direction.x *= -1.0; // ( -u, v, 1 ) pos z
} else if ( face == 3.0 ) {
direction = direction.zyx;
direction.xz *= -1.0; // ( -1, v, -u ) neg x
} else if ( face == 4.0 ) {
direction = direction.xzy;
direction.xy *= -1.0; // ( -u, -1, v ) neg y
} else if ( face == 5.0 ) {
direction.z *= -1.0; // ( u, v, -1 ) neg z
}
return direction;
}
void main() {
vOutputDirection = getDirection( uv, faceIndex );
gl_Position = vec4( position, 1.0 );
}
`
}
public _createPlanes(lodMax: number) {
const lodPlanes = []
const sizeLods = []
const sigmas = []
let lod = lodMax
const totalLods = lodMax - LOD_MIN + 1 + EXTRA_LOD_SIGMA.length
for (let i = 0; i < totalLods; i++) {
const sizeLod = Math.pow(2, lod)
sizeLods.push(sizeLod)
let sigma = 1.0 / sizeLod
if (i > lodMax - LOD_MIN) {
sigma = EXTRA_LOD_SIGMA[i - lodMax + LOD_MIN - 1]
} else if (i === 0) {
sigma = 0
}
sigmas.push(sigma)
const texelSize = 1.0 / (sizeLod - 2)
const min = -texelSize
const max = 1 + texelSize
const uv1 = [min, min, max, min, max, max, min, min, max, max, min, max]
const cubeFaces = 6
const vertices = 6
const positionSize = 3
const uvSize = 2
const faceIndexSize = 1
const position = new Float32Array(positionSize * vertices * cubeFaces)
const uv = new Float32Array(uvSize * vertices * cubeFaces)
const faceIndex = new Float32Array(faceIndexSize * vertices * cubeFaces)
for (let face = 0; face < cubeFaces; face++) {
const x = ((face % 3) * 2) / 3 - 1
const y = face > 2 ? 0 : -1
const coordinates = [
x,
y,
0,
x + 2 / 3,
y,
0,
x + 2 / 3,
y + 1,
0,
x,
y,
0,
x + 2 / 3,
y + 1,
0,
x,
y + 1,
0
]
position.set(coordinates, positionSize * vertices * face)
uv.set(uv1, uvSize * vertices * face)
const fill = [face, face, face, face, face, face]
faceIndex.set(fill, faceIndexSize * vertices * face)
}
const planes = new BufferGeometry()
planes.setAttribute('position', new BufferAttribute(position, positionSize))
planes.setAttribute('uv', new BufferAttribute(uv, uvSize))
planes.setAttribute('faceIndex', new BufferAttribute(faceIndex, faceIndexSize))
lodPlanes.push(planes)
if (lod > LOD_MIN) {
lod--
}
}
return { lodPlanes, sizeLods, sigmas }
}
public compileProperEquirectShader(rotationMatrix?: Matrix4) {
const fixedEnvFlip = function _getEquirectMaterial() {
return new ShaderMaterial({
@ -76,10 +387,9 @@ export class RotatablePMREMGenerator extends PMREMGenerator {
})
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this._equirectMaterial = fixedEnvFlip()
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this._compileMaterial(this._equirectMaterial)
}

Просмотреть файл

@ -1,8 +1,17 @@
import { test } from 'vitest'
import { JSDOM } from 'jsdom'
import { Viewer } from '../src'
import {
Assets,
AssetType,
CameraController,
SpeckleLoader,
UrlHelper,
Viewer,
ViewerEvent
} from '../src'
import { readFile } from 'fs/promises'
test('Viewer', async () => {
test('Viewer', { timeout: 20000 }, async () => {
const width = 64
const height = 64
// eslint-disable-next-line @typescript-eslint/no-var-requires
@ -10,5 +19,58 @@ test('Viewer', async () => {
gl
const dom = new JSDOM(`<!DOCTYPE html><p>Hello world</p>`)
const viewer = new Viewer(dom.window.document.createElement('div'), undefined, gl)
console.warn(viewer)
// console.log(viewer.getRenderer().renderer.extensions)
const hdriContents = await readFile('src/assets/hdri/Mild-dwab.png')
const gradientContents = await readFile('src/assets/gradient.png')
await Assets.getEnvironment(
{
id: 'defaultHDRI',
type: AssetType.TEXTURE_EXR,
contentsBuffer: hdriContents.buffer
},
viewer.getRenderer().renderer
)
await Assets.getTexture({
id: 'defaultGradient',
type: AssetType.TEXTURE_8BPP,
contentsBuffer: gradientContents.buffer
})
// Assets._cache['defaultHDRI'] = new EXRLoader().parse(buffer)
// Assets._cache['defaultGradient'] = new DataTexture(null, 512, 1)
try {
await viewer.init()
} catch (e) {}
viewer.createExtension(CameraController)
viewer.on(ViewerEvent.LoadComplete, () => {
viewer.getRenderer().render()
setTimeout(() => {
const pixels = new Uint8Array(width * height * 4)
gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels)
process.stdout.write(['P3\n# gl.ppm\n', width, ' ', height, '\n255\n'].join(''))
for (let i = 0; i < pixels.length; i += 4) {
for (let j = 0; j < 3; ++j) {
process.stdout.write(pixels[i + j] + ' ')
}
}
}, 1000)
})
const url = 'https://speckle.xyz/streams/da9e320dad/commits/5388ef24b8'
const authToken = localStorage.getItem(
url.includes('latest') ? 'AuthTokenLatest' : 'AuthToken'
) as string
const objUrls = await UrlHelper.getResourceUrls(url, authToken)
for (const objURL of objUrls) {
// console.log(`Loading ${url}`)
const loader = new SpeckleLoader(
viewer.getWorldTree(),
objURL,
authToken,
true,
undefined
)
await viewer.loadObject(loader, false)
}
// console.warn(viewer.getRenderer().renderingStats)
})

Просмотреть файл

@ -11748,7 +11748,7 @@ __metadata:
languageName: node
linkType: hard
"@mapbox/node-pre-gyp@npm:^1.0.11":
"@mapbox/node-pre-gyp@npm:^1.0.0, @mapbox/node-pre-gyp@npm:^1.0.11":
version: 1.0.11
resolution: "@mapbox/node-pre-gyp@npm:1.0.11"
dependencies:
@ -16026,6 +16026,7 @@ __metadata:
"@typescript-eslint/eslint-plugin": "npm:^7.12.0"
"@typescript-eslint/parser": "npm:^7.12.0"
"@vitest/ui": "npm:^1.4.0"
canvas: "npm:^2.11.2"
core-js: "npm:^3.21.1"
eslint: "npm:^9.4.0"
eslint-config-prettier: "npm:^9.1.0"
@ -24916,6 +24917,18 @@ __metadata:
languageName: node
linkType: hard
"canvas@npm:^2.11.2":
version: 2.11.2
resolution: "canvas@npm:2.11.2"
dependencies:
"@mapbox/node-pre-gyp": "npm:^1.0.0"
nan: "npm:^2.17.0"
node-gyp: "npm:latest"
simple-get: "npm:^3.0.3"
checksum: 10/500040e93310b33f5733746b909712fdeced56aa74a1370c563f0c7ffc5b4a31006b2d881644b59eea8ab6c465406705a59b280f1d4e3ec4e1e2c88d4a725ca6
languageName: node
linkType: hard
"capital-case@npm:^1.0.4":
version: 1.0.4
resolution: "capital-case@npm:1.0.4"
@ -27363,6 +27376,15 @@ __metadata:
languageName: node
linkType: hard
"decompress-response@npm:^4.2.0":
version: 4.2.1
resolution: "decompress-response@npm:4.2.1"
dependencies:
mimic-response: "npm:^2.0.0"
checksum: 10/4e783ca4dfe9417354d61349750fe05236f565a4415a6ca20983a311be2371debaedd9104c0b0e7b36e5f167aeaae04f84f1a0b3f8be4162f1d7d15598b8fdba
languageName: node
linkType: hard
"decompress-response@npm:^6.0.0":
version: 6.0.0
resolution: "decompress-response@npm:6.0.0"
@ -37591,6 +37613,13 @@ __metadata:
languageName: node
linkType: hard
"mimic-response@npm:^2.0.0":
version: 2.1.0
resolution: "mimic-response@npm:2.1.0"
checksum: 10/014fad6ab936657e5f2f48bd87af62a8e928ebe84472aaf9e14fec4fcb31257a5edff77324d8ac13ddc6685ba5135cf16e381efac324e5f174fb4ddbf902bf07
languageName: node
linkType: hard
"mimic-response@npm:^3.1.0":
version: 3.1.0
resolution: "mimic-response@npm:3.1.0"
@ -39119,7 +39148,7 @@ __metadata:
languageName: node
linkType: hard
"nan@npm:^2.18.0, nan@npm:^2.19.0":
"nan@npm:^2.17.0, nan@npm:^2.18.0, nan@npm:^2.19.0":
version: 2.20.0
resolution: "nan@npm:2.20.0"
dependencies:
@ -46484,6 +46513,17 @@ __metadata:
languageName: node
linkType: hard
"simple-get@npm:^3.0.3":
version: 3.1.1
resolution: "simple-get@npm:3.1.1"
dependencies:
decompress-response: "npm:^4.2.0"
once: "npm:^1.3.1"
simple-concat: "npm:^1.0.0"
checksum: 10/94fa04e74077c2607142f7597af8409c6c8d1e9487b597ce1da6f824e732b3e51ef492e495a4d8a2a12a94780214d77a8d3bb81c2139b3ec4ce21b93224442c0
languageName: node
linkType: hard
"simple-get@npm:^4.0.0, simple-get@npm:^4.0.1":
version: 4.0.1
resolution: "simple-get@npm:4.0.1"