From 6726ac26091edcd173ead518667e0169a9d51fc4 Mon Sep 17 00:00:00 2001 From: Maksim Makushchenko Date: Thu, 27 Nov 2025 21:53:21 +0300 Subject: [PATCH 01/10] =?UTF-8?q?WIP=20=D0=BF=D1=80=D0=B8=D0=B2=D1=8F?= =?UTF-8?q?=D0=B7=D0=BA=D0=B0=20=D1=82=D0=B5=D0=BA=D1=81=D1=82=D0=BE=D0=B2?= =?UTF-8?q?=D1=8B=D1=85=20=D1=8D=D0=BB=D0=B5=D0=BC=D0=B5=D0=BD=D1=82=D0=BE?= =?UTF-8?q?=D0=B2=20=D0=BA=20=D0=B0=D1=83=D0=B4=D0=B8=D0=BE=20=D1=8D=D0=BB?= =?UTF-8?q?=D0=B5=D0=BC=D0=B5=D0=BD=D1=82=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Models/FieldItem.ts | 28 +++++++++++++++---- src/Models/FieldItemContents/AudioContent.ts | 16 ++++++----- src/Models/FieldItemContents/ImagesContent.ts | 17 +++++------ src/Models/FieldItemInfo.ts | 3 +- src/Viewer.ts | 13 +++++++-- 5 files changed, 53 insertions(+), 24 deletions(-) diff --git a/src/Models/FieldItem.ts b/src/Models/FieldItem.ts index 6dff1a1..464a195 100644 --- a/src/Models/FieldItem.ts +++ b/src/Models/FieldItem.ts @@ -58,7 +58,7 @@ export class FieldItem extends LinkToState { material, async () => { await this.onTrigger(); - await onOpen(this); + await this.onOpen(this); }, null, scene, @@ -147,7 +147,16 @@ export class FieldItem extends LinkToState { const navMenuItems = []; + /** Как временное решение - изображения включают свое звуковое сопровождение через глобальный проигрыватель аудио на интерактивном компоненте. Если он занят - просто audio на компоненте включить не поулчится */ + const audioContentUsedByImageContent = this.fieldItemInfo.images.some( + (i) => !!i.audio, + ); + if (this.fieldItemInfo.images && this.fieldItemInfo.images.length > 0) { + if (audioContentUsedByImageContent) { + // если в фотографиях есть необходимость в аудио - создаем его + createEmptyAudioContent(); + } const imageContent = new ImagesContent( this.fieldItemInfo.images, backgroundPlane, @@ -207,8 +216,19 @@ export class FieldItem extends LinkToState { } if (this.fieldItemInfo.audios && this.fieldItemInfo.audios.length > 0) { + if (audioContentUsedByImageContent) { + console.warn( + `На элементе ${this.name} есть audio, хотя оно уже занято изображениями, потому пропускается`, + ); + } else { + createEmptyAudioContent().setAudioContent(this.fieldItemInfo.audios[0]); + } + } + this.contentBackground = backgroundPlane; + this.changeContent(0); + + function createEmptyAudioContent() { const audioContent = new AudioContent( - this.fieldItemInfo.audios[0], backgroundPlane, FieldItem.containerSize * 1.6, FieldItem.containerSize / 2, @@ -217,13 +237,11 @@ export class FieldItem extends LinkToState { this.videoContent?.pauseVideo(); }, this.gui3Dmanager, - this.assetsManager, this.scene, ); this.contentList.push(audioContent); + return audioContent; } - this.contentBackground = backgroundPlane; - this.changeContent(0); } private changeContent(contentIndex: number) { diff --git a/src/Models/FieldItemContents/AudioContent.ts b/src/Models/FieldItemContents/AudioContent.ts index 8069630..269d635 100644 --- a/src/Models/FieldItemContents/AudioContent.ts +++ b/src/Models/FieldItemContents/AudioContent.ts @@ -5,7 +5,6 @@ import { Color3 } from "@babylonjs/core/Maths/math.color"; import { CreatePlane } from "@babylonjs/core/Meshes/Builders/planeBuilder"; import type { Mesh } from "@babylonjs/core/Meshes/mesh"; import type { TransformNode } from "@babylonjs/core/Meshes/transformNode"; -import type { AssetsManager } from "@babylonjs/core/Misc/assetsManager"; import type { Scene } from "@babylonjs/core/scene"; import { AdvancedDynamicTexture } from "@babylonjs/gui/2D/advancedDynamicTexture"; import { TextBlock, TextWrapping } from "@babylonjs/gui/2D/controls/textBlock"; @@ -26,7 +25,7 @@ export class AudioContent implements FieldItemContent { private playPauseButton: CustomHolographicButton; private playPauseButtonText: TextBlock; private currentPositionText: TextBlock; - private currentPositionTimer: any; + private currentPositionTimer?: NodeJS.Timeout; private static backgroundMaterial: Material; private backgroundPlane: Mesh; @@ -34,14 +33,14 @@ export class AudioContent implements FieldItemContent { private audio: Sound; private uiLayerPlane: Mesh; + private audioInfo: FieldItemAudioContent | null = null; + constructor( - private audioInfo: FieldItemAudioContent, private parent: TransformNode, private contentWidth: number, private contentHeight: number, private onPlay: () => void, private gui3Dmanager: GUI3DManager, - private assetsManager: AssetsManager, private scene: Scene, ) { if (!AudioContent.backgroundMaterial) { @@ -55,7 +54,6 @@ export class AudioContent implements FieldItemContent { } this.createAudioContentPlane(); - this.createSound(); this.createPlayPauseButton(); } @@ -69,7 +67,7 @@ export class AudioContent implements FieldItemContent { } } - createAudioContentPlane() { + private createAudioContentPlane() { var backgroundPlane = CreatePlane("audio-content-plane", { width: 2, height: 2, @@ -162,13 +160,17 @@ export class AudioContent implements FieldItemContent { } } - private createSound() { + public setAudioContent(audioContent: FieldItemAudioContent) { + this.audioInfo = audioContent; const audio = new Sound( "audio_content", this.audioInfo.src, this.scene, () => { + clearInterval(this.currentPositionTimer); + this.audio?.dispose(); this.audio = audio; + this.playPauseButtonText.text = ExcursionConstants.PlayIcon; this.currentPositionTimer = setInterval(() => { this.currentPositionText.text = this.getCurrentPositionText(); diff --git a/src/Models/FieldItemContents/ImagesContent.ts b/src/Models/FieldItemContents/ImagesContent.ts index 22d80c1..3342f4d 100644 --- a/src/Models/FieldItemContents/ImagesContent.ts +++ b/src/Models/FieldItemContents/ImagesContent.ts @@ -17,6 +17,7 @@ import type { GUI3DManager } from "@babylonjs/gui/3D/gui3DManager"; import { CustomHolographicButton } from "../../Stuff/CustomHolographicButton"; import { NavigationMenu } from "../NavigationMenu"; import type { FieldItemContent } from "./FieldItemContent"; +import type { FieldItemImageContent } from "../ExcursionModels/FieldItemImageContent"; export class ImagesContent implements FieldItemContent { get type(): string { @@ -56,7 +57,7 @@ export class ImagesContent implements FieldItemContent { } constructor( - private images: string[], + private images: FieldItemImageContent[], private parent: TransformNode, private contentWidth: number, private contentHeight: number, @@ -86,7 +87,7 @@ export class ImagesContent implements FieldItemContent { }, ); } - this.resources = images.map((i) => null); + this.resources = images.map(() => null as any); this.openPicture(this.currentImage); } @@ -120,7 +121,7 @@ export class ImagesContent implements FieldItemContent { index = this.resources.length + index; } index = index % this.resources.length; - this.imageButtons && this.imageButtons.setCurrentIndex(index); + this.imageButtons?.setCurrentIndex(index); for (let i = 0; i < this.resources.length; i++) { const imageResource = this.resources[i]; if (index === i) { @@ -131,7 +132,7 @@ export class ImagesContent implements FieldItemContent { texture: null, material: null, plane: null, - task: this.loadPictureResources(index, this.images[index]), + task: this.loadPictureResources(index, this.images[index].imageSrc), }; } else if (imageResource.plane) { // Loaded @@ -140,7 +141,7 @@ export class ImagesContent implements FieldItemContent { // In loading, just wait } } else { - if (imageResource && imageResource.plane) { + if (imageResource?.plane) { // Hide all loaded images imageResource.plane.isVisible = false; } @@ -203,9 +204,9 @@ export class ImagesContent implements FieldItemContent { } public dispose(): void { - this.imageButtons && this.imageButtons.dispose(); - this.rightButton && this.rightButton.dispose(); - this.leftButton && this.leftButton.dispose(); + this.imageButtons?.dispose(); + this.rightButton?.dispose(); + this.leftButton?.dispose(); for (const resource of this.resources) { if (!resource) { continue; diff --git a/src/Models/FieldItemInfo.ts b/src/Models/FieldItemInfo.ts index 6a7d44c..f93c0d0 100644 --- a/src/Models/FieldItemInfo.ts +++ b/src/Models/FieldItemInfo.ts @@ -1,10 +1,11 @@ import type { Vector3 } from "@babylonjs/core/Maths/math.vector"; import type { FieldItemAudioContent } from "./ExcursionModels/FieldItemAudioContent"; +import type { FieldItemImageContent } from "./ExcursionModels/FieldItemImageContent"; export class FieldItemInfo { constructor( public vertex: Vector3[], - public images: string[], + public images: FieldItemImageContent[], public videos: string[], public text: string, public audios: FieldItemAudioContent[], diff --git a/src/Viewer.ts b/src/Viewer.ts index 599cf60..a6e1b8f 100644 --- a/src/Viewer.ts +++ b/src/Viewer.ts @@ -390,9 +390,16 @@ export class Viewer { fieldItem.vertices.map((q) => MathStuff.GetPositionForMarker(q, this.backgroundRadius * 0.99), ), - fieldItem.imageContent.map( - (i) => this.configuration.sceneUrl + i.imageSrc, - ), + fieldItem.imageContent.map((i) => ({ + ...i, + imageSrc: this.configuration.sceneUrl + i.imageSrc, + audio: i.audio + ? { + ...i.audio, + src: this.configuration.sceneUrl + i.audio.src, + } + : null, + })), fieldItem.videos.map((v) => this.configuration.sceneUrl + v), fieldItem.text, fieldItem.audios.map((a) => ({ From 02773fb2a2aa3a3d05961b85e11e2ef064700df9 Mon Sep 17 00:00:00 2001 From: Maksim Makushchenko Date: Fri, 28 Nov 2025 10:16:18 +0300 Subject: [PATCH 02/10] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=BE=20=D1=81=D1=83=D0=BC=D0=BC=D0=B8=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=B0=D0=B4=D1=80=D0=B5?= =?UTF-8?q?=D1=81=D0=BE=D0=B2=20=D0=B4=D0=BB=D1=8F=20=D1=84=D0=BE=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D0=BE=D0=B3=D0=BE=20=D1=81=D0=BE=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=BE=D0=B6=D0=B4=D0=B5=D0=BD=D0=B8=D1=8F,=20=20=D0=BD?= =?UTF-8?q?=D0=B5=D0=BC=D0=BD=D0=BE=D0=B3=D0=BE=20=D1=80=D0=B5=D1=84=D0=B0?= =?UTF-8?q?=D0=BA=D1=82=D0=BE=D1=80=D0=B8=D0=BD=D0=B3=D0=B0=20biome?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Models/BackgroundAudio/AudioContainer.ts | 3 +- src/Models/FieldItem.ts | 2 -- src/Models/FieldItemContents/ImagesContent.ts | 2 +- src/Models/FieldItemContents/TextContent.ts | 30 +++++++++---------- src/Stuff/UrlHelpers.ts | 16 ++++++++++ src/Viewer.ts | 3 +- 6 files changed, 35 insertions(+), 21 deletions(-) create mode 100644 src/Stuff/UrlHelpers.ts diff --git a/src/Models/BackgroundAudio/AudioContainer.ts b/src/Models/BackgroundAudio/AudioContainer.ts index f073de7..ecb7385 100644 --- a/src/Models/BackgroundAudio/AudioContainer.ts +++ b/src/Models/BackgroundAudio/AudioContainer.ts @@ -1,5 +1,6 @@ import { Sound } from "@babylonjs/core/Audio/sound"; import type { Scene } from "@babylonjs/core/scene"; +import { concatUrlPath } from "../../Stuff/UrlHelpers"; import { PlayAudioHelper } from "../..//WorkWithAudio/PlayAudioHelper"; import type { BackgroundAudioInfo } from "../ExcursionModels/BackgroundAudioInfo"; @@ -25,7 +26,7 @@ export class AudioContainer { sceneUrl: string, ) { this.sounds = info.audios.map((s, i) => - this.createSound(this.info.id, sceneUrl + s, i), + this.createSound(this.info.id, concatUrlPath(sceneUrl, s), i), ); } diff --git a/src/Models/FieldItem.ts b/src/Models/FieldItem.ts index 464a195..f9599f5 100644 --- a/src/Models/FieldItem.ts +++ b/src/Models/FieldItem.ts @@ -193,8 +193,6 @@ export class FieldItem extends LinkToState { backgroundPlane, FieldItem.containerSize * 1, FieldItem.containerSize * 0.8, - this.gui3Dmanager, - this.assetsManager, this.scene, ); this.contentList.push(textContent); diff --git a/src/Models/FieldItemContents/ImagesContent.ts b/src/Models/FieldItemContents/ImagesContent.ts index 3342f4d..272fd0b 100644 --- a/src/Models/FieldItemContents/ImagesContent.ts +++ b/src/Models/FieldItemContents/ImagesContent.ts @@ -15,9 +15,9 @@ import type { Scene } from "@babylonjs/core/scene"; import { TextBlock, TextWrapping } from "@babylonjs/gui/2D/controls/textBlock"; import type { GUI3DManager } from "@babylonjs/gui/3D/gui3DManager"; import { CustomHolographicButton } from "../../Stuff/CustomHolographicButton"; +import type { FieldItemImageContent } from "../ExcursionModels/FieldItemImageContent"; import { NavigationMenu } from "../NavigationMenu"; import type { FieldItemContent } from "./FieldItemContent"; -import type { FieldItemImageContent } from "../ExcursionModels/FieldItemImageContent"; export class ImagesContent implements FieldItemContent { get type(): string { diff --git a/src/Models/FieldItemContents/TextContent.ts b/src/Models/FieldItemContents/TextContent.ts index 1d87c07..359c4fb 100644 --- a/src/Models/FieldItemContents/TextContent.ts +++ b/src/Models/FieldItemContents/TextContent.ts @@ -1,3 +1,4 @@ +import type { IWheelEvent, Observer } from "@babylonjs/core"; import { PointerEventTypes, type PointerInfo, @@ -5,13 +6,11 @@ import { import { CreatePlane } from "@babylonjs/core/Meshes/Builders/planeBuilder"; import type { Mesh } from "@babylonjs/core/Meshes/mesh"; import type { TransformNode } from "@babylonjs/core/Meshes/transformNode"; -import type { AssetsManager } from "@babylonjs/core/Misc/assetsManager"; import type { Scene } from "@babylonjs/core/scene"; import { AdvancedDynamicTexture } from "@babylonjs/gui/2D/advancedDynamicTexture"; import { Control } from "@babylonjs/gui/2D/controls/control"; import { ScrollViewer } from "@babylonjs/gui/2D/controls/scrollViewers/scrollViewer"; import { TextBlock, TextWrapping } from "@babylonjs/gui/2D/controls/textBlock"; -import type { GUI3DManager } from "@babylonjs/gui/3D/gui3DManager"; import type { FieldItemContent } from "./FieldItemContent"; export class TextContent implements FieldItemContent { @@ -26,31 +25,30 @@ export class TextContent implements FieldItemContent { private scrollView: ScrollViewer; - private wheelHandler: any; + private wheelHandlerObserver: Observer; constructor( private text: string, private parent: TransformNode, private contentWidth: number, private contentHeight: number, - private gui3Dmanager: GUI3DManager, - private assetsManager: AssetsManager, private scene: Scene, ) { // this.loadVideoResources(videoUrl); // this.playPauseButton = this.createPlayPauseButton(); this.createTextPanel(); - this.wheelHandler = (event: PointerInfo) => { - if (event.pickInfo.pickedMesh === this.textPlane) { - if (event.type === PointerEventTypes.POINTERWHEEL) { - let delta = (event.event as any).deltaY; - delta = delta > 0 ? 1 : -1; - this.scrollView.verticalBar.value += - this.scrollView.wheelPrecision * delta; + this.wheelHandlerObserver = scene.onPointerObservable.add( + (event: PointerInfo) => { + if (event.pickInfo.pickedMesh === this.textPlane) { + if (event.type === PointerEventTypes.POINTERWHEEL) { + let delta = (event.event as IWheelEvent).deltaY; + delta = delta > 0 ? 1 : -1; + this.scrollView.verticalBar.value += + this.scrollView.wheelPrecision * delta; + } } - } - }; - scene.onPointerObservable.add(this.wheelHandler); + }, + ); } setIsVisible(visible: boolean) { @@ -109,6 +107,6 @@ export class TextContent implements FieldItemContent { this.textPlane.dispose(); this.textPlane.material.dispose(); this.textTexture.dispose(); - this.scene.onPointerObservable.remove(this.wheelHandler); + this.scene.onPointerObservable.remove(this.wheelHandlerObserver); } } diff --git a/src/Stuff/UrlHelpers.ts b/src/Stuff/UrlHelpers.ts new file mode 100644 index 0000000..195528a --- /dev/null +++ b/src/Stuff/UrlHelpers.ts @@ -0,0 +1,16 @@ +export function concatUrlPath(baseUrl: string, ...parts: string[]): string { + if (baseUrl.endsWith("/")) { + baseUrl = baseUrl.substring(0, baseUrl.lastIndexOf("/")); + } + let resultUrl = baseUrl; + for (let part of parts) { + if (part.endsWith("/")) { + part = part.substring(0, part.lastIndexOf("/")); + } + if (part.startsWith("/")) { + part = part.substring(1, part.length); + } + resultUrl += `/${part}`; + } + return resultUrl; +} diff --git a/src/Viewer.ts b/src/Viewer.ts index a6e1b8f..48a558f 100644 --- a/src/Viewer.ts +++ b/src/Viewer.ts @@ -38,6 +38,7 @@ import "@babylonjs/core/Culling/ray"; // нужно для работы клик import { KeyboardEventTypes, PointerEventTypes } from "@babylonjs/core"; import type { WebXRInterface } from "./AsyncModules/AsyncModuleInterfaces"; import { PrefetchResourcesManager } from "./Models/PrefetchResourcesManager"; +import { concatUrlPath } from "./Stuff/UrlHelpers"; import { PlayAudioHelper } from "./WorkWithAudio/PlayAudioHelper"; export class Viewer { @@ -463,7 +464,7 @@ export class Viewer { )?.audios || []; audios.forEach((a) => { this.prefetchResourcesManager.addResource( - this.configuration.sceneUrl + a, + concatUrlPath(this.configuration.sceneUrl, a), ); }); state.fieldItems.forEach((fi) => { From 572df910147e656cc211d115ce47d9bb13cac3c5 Mon Sep 17 00:00:00 2001 From: Maksim Makushchenko Date: Fri, 28 Nov 2025 10:45:34 +0300 Subject: [PATCH 03/10] =?UTF-8?q?=D0=98=D0=BD=D1=82=D0=B5=D1=80=D0=B0?= =?UTF-8?q?=D0=BA=D1=82=D0=B8=D0=B2=D0=BD=D1=8B=D0=B9=20=D1=8D=D0=BB=D0=B5?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D1=82=20=D0=BC=D0=BE=D0=B6=D0=B5=D1=82=20?= =?UTF-8?q?=D1=81=D0=BE=D0=B4=D0=B5=D1=80=D0=B6=D0=B0=D1=82=D1=8C=20=D1=81?= =?UTF-8?q?=D0=BE=D0=B1=D1=81=D1=82=D0=B2=D0=B5=D0=BD=D0=BD=D1=83=D1=8E=20?= =?UTF-8?q?=D0=BE=D0=B7=D0=B2=D1=83=D1=87=D0=BA=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + .../BackgroundAudio/BackgroundAudioView.ts | 4 +- src/Models/FieldItem.ts | 39 ++++++++++--------- src/Models/FieldItemContents/AudioContent.ts | 5 ++- src/Models/FieldItemContents/ImagesContent.ts | 5 +++ src/Models/IconBottom.ts | 2 +- src/Models/NavigationMenu.ts | 12 +++--- src/Models/ObjectsStackPanelHelper.ts | 26 ++++++------- src/StateChangeLoadingScreen.ts | 2 +- src/Viewer.ts | 2 +- 10 files changed, 54 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d78b978..5eccd6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ * `0.9.29` + * ✨ Каждая фотография интерактивного элемента может содержать собственную озвучку * ➕ Добавлен biome для лингинга кода и проверки ошибок * `0.9.28` * ✨ Поддержка зума через колесико мыши diff --git a/src/Models/BackgroundAudio/BackgroundAudioView.ts b/src/Models/BackgroundAudio/BackgroundAudioView.ts index 9d2212f..7e2fa90 100644 --- a/src/Models/BackgroundAudio/BackgroundAudioView.ts +++ b/src/Models/BackgroundAudio/BackgroundAudioView.ts @@ -64,11 +64,11 @@ export class BackgroundAudioView { } public play() { - this.currentAudioPack && this.currentAudioPack.play(true); + this.currentAudioPack?.play(true); } public pause() { - this.currentAudioPack && this.currentAudioPack.pause(); + this.currentAudioPack?.pause(); } public clearSound() { diff --git a/src/Models/FieldItem.ts b/src/Models/FieldItem.ts index f9599f5..2692463 100644 --- a/src/Models/FieldItem.ts +++ b/src/Models/FieldItem.ts @@ -153,9 +153,10 @@ export class FieldItem extends LinkToState { ); if (this.fieldItemInfo.images && this.fieldItemInfo.images.length > 0) { + let audioContent: AudioContent | null = null; if (audioContentUsedByImageContent) { // если в фотографиях есть необходимость в аудио - создаем его - createEmptyAudioContent(); + audioContent = this.createEmptyAudioContent(backgroundPlane); } const imageContent = new ImagesContent( this.fieldItemInfo.images, @@ -165,6 +166,7 @@ export class FieldItem extends LinkToState { this.gui3Dmanager, this.assetsManager, this.scene, + audioContent, ); this.contentList.push(imageContent); navMenuItems.push("Фотографии"); @@ -219,27 +221,28 @@ export class FieldItem extends LinkToState { `На элементе ${this.name} есть audio, хотя оно уже занято изображениями, потому пропускается`, ); } else { - createEmptyAudioContent().setAudioContent(this.fieldItemInfo.audios[0]); + this.createEmptyAudioContent(backgroundPlane).setAudioContent( + this.fieldItemInfo.audios[0], + ); } } this.contentBackground = backgroundPlane; this.changeContent(0); - - function createEmptyAudioContent() { - const audioContent = new AudioContent( - backgroundPlane, - FieldItem.containerSize * 1.6, - FieldItem.containerSize / 2, - () => { - this.onPlayMedia(); - this.videoContent?.pauseVideo(); - }, - this.gui3Dmanager, - this.scene, - ); - this.contentList.push(audioContent); - return audioContent; - } + } + private createEmptyAudioContent(backgroundPlane: Mesh) { + const audioContent = new AudioContent( + backgroundPlane, + FieldItem.containerSize * 1.6, + FieldItem.containerSize / 2, + () => { + this.onPlayMedia(); + this.videoContent?.pauseVideo(); + }, + this.gui3Dmanager, + this.scene, + ); + this.contentList.push(audioContent); + return audioContent; } private changeContent(contentIndex: number) { diff --git a/src/Models/FieldItemContents/AudioContent.ts b/src/Models/FieldItemContents/AudioContent.ts index 269d635..fea1c24 100644 --- a/src/Models/FieldItemContents/AudioContent.ts +++ b/src/Models/FieldItemContents/AudioContent.ts @@ -33,7 +33,7 @@ export class AudioContent implements FieldItemContent { private audio: Sound; private uiLayerPlane: Mesh; - private audioInfo: FieldItemAudioContent | null = null; + private audioInfo?: FieldItemAudioContent | null; constructor( private parent: TransformNode, @@ -184,6 +184,9 @@ export class AudioContent implements FieldItemContent { } private getCurrentPositionText(): string { + if (!this.audioInfo) { + return ""; + } return `${this.durationView(this.getCurrentPosition())}/${this.durationView( this.audioInfo.duration, )}`; diff --git a/src/Models/FieldItemContents/ImagesContent.ts b/src/Models/FieldItemContents/ImagesContent.ts index 272fd0b..3fb025a 100644 --- a/src/Models/FieldItemContents/ImagesContent.ts +++ b/src/Models/FieldItemContents/ImagesContent.ts @@ -17,6 +17,7 @@ import type { GUI3DManager } from "@babylonjs/gui/3D/gui3DManager"; import { CustomHolographicButton } from "../../Stuff/CustomHolographicButton"; import type { FieldItemImageContent } from "../ExcursionModels/FieldItemImageContent"; import { NavigationMenu } from "../NavigationMenu"; +import type { AudioContent } from "./AudioContent"; import type { FieldItemContent } from "./FieldItemContent"; export class ImagesContent implements FieldItemContent { @@ -64,6 +65,7 @@ export class ImagesContent implements FieldItemContent { private gui3DManager: GUI3DManager, private assetsManager: AssetsManager, private scene: Scene, + private parentAudioContent: AudioContent, ) { if (images.length > 1) { this.rightButton = this.createButton(">", contentWidth / 2.5); @@ -148,6 +150,9 @@ export class ImagesContent implements FieldItemContent { } } this.currentImage = index; + if (this.images[index].audio) { + this.parentAudioContent.setAudioContent(this.images[index].audio); + } } private loadPictureResources(index: number, url: string): TextureAssetTask { diff --git a/src/Models/IconBottom.ts b/src/Models/IconBottom.ts index 7afdb9f..873c254 100644 --- a/src/Models/IconBottom.ts +++ b/src/Models/IconBottom.ts @@ -7,7 +7,7 @@ import type { BottomImageConfiguration } from "../Configuration/Configuration"; export class IconBottom { constructor( - private _scene: Scene, + _scene: Scene, config: BottomImageConfiguration, ) { const bottomPlane = MeshBuilder.CreatePlane( diff --git a/src/Models/NavigationMenu.ts b/src/Models/NavigationMenu.ts index 0a2a6b8..b14263f 100644 --- a/src/Models/NavigationMenu.ts +++ b/src/Models/NavigationMenu.ts @@ -3,18 +3,18 @@ import type { TransformNode } from "@babylonjs/core/Meshes/transformNode"; import { TextBlock, TextWrapping } from "@babylonjs/gui/2D/controls/textBlock"; import type { GUI3DManager } from "@babylonjs/gui/3D/gui3DManager"; import { CustomHolographicButton } from "../Stuff/CustomHolographicButton"; -import { ObjectsStackPanelHelper } from "./ObjectsStackPanelHelper"; +import { placeAsHorizontalStack } from "./ObjectsStackPanelHelper"; export class NavigationMenu { private buttons: CustomHolographicButton[] = []; constructor( - private labels: string[], - private menuWitdh: number, + labels: string[], + menuWitdh: number, private positionY: number, - private parent: TransformNode, + parent: TransformNode, private gui3Dmanager: GUI3DManager, - private buttonSizeGetter: (index: number) => { + buttonSizeGetter: (index: number) => { width: number; height: number; }, @@ -26,7 +26,7 @@ export class NavigationMenu { const button = this.createButton(label, parent, i, width, height); this.buttons.push(button); } - ObjectsStackPanelHelper.placeAsHorizontalStack(this.buttons, menuWitdh); + placeAsHorizontalStack(this.buttons, menuWitdh); } public setCurrentIndex(index: number) { diff --git a/src/Models/ObjectsStackPanelHelper.ts b/src/Models/ObjectsStackPanelHelper.ts index 937b19f..140322d 100644 --- a/src/Models/ObjectsStackPanelHelper.ts +++ b/src/Models/ObjectsStackPanelHelper.ts @@ -4,19 +4,17 @@ interface ItemWithPosition { position: Vector3; } -export class ObjectsStackPanelHelper { - public static placeAsHorizontalStack( - objects: ItemWithPosition[], - containerSize: number, - ) { - // Stack emulation - const size = containerSize / (objects.length + 1); - for (let i = 0; i < objects.length; i++) { - const currentButton = objects[i]; - const position = - -containerSize / 2 + // left point - size * (i + 1); // offset - currentButton.position.x = position; - } +export function placeAsHorizontalStack( + objects: ItemWithPosition[], + containerSize: number, +) { + // Stack emulation + const size = containerSize / (objects.length + 1); + for (let i = 0; i < objects.length; i++) { + const currentButton = objects[i]; + const position = + -containerSize / 2 + // left point + size * (i + 1); // offset + currentButton.position.x = position; } } diff --git a/src/StateChangeLoadingScreen.ts b/src/StateChangeLoadingScreen.ts index 5e3d3a5..eea2c7d 100644 --- a/src/StateChangeLoadingScreen.ts +++ b/src/StateChangeLoadingScreen.ts @@ -10,7 +10,7 @@ export class StateChangeLoadingScreen implements ILoadingScreen { private textBlock: TextBlock; - constructor(private gui: AdvancedDynamicTexture) { + constructor(gui: AdvancedDynamicTexture) { this.displayLoadingUI = () => this.displayUIInternal(); this.hideLoadingUI = () => this.hideLoadingUIInternal(); diff --git a/src/Viewer.ts b/src/Viewer.ts index 48a558f..c4f5fc7 100644 --- a/src/Viewer.ts +++ b/src/Viewer.ts @@ -294,7 +294,7 @@ export class Viewer { this.prefetchAudio(targetPicture); await this.drawImage( targetPicture, - () => actionBeforeChange && actionBeforeChange(targetPicture), + () => actionBeforeChange?.(targetPicture), ); this.fullScreenGUI.setFastReturnToFirstStateVisible( From 6a6c447a689b5aded178f4f473c50736efd7e41b Mon Sep 17 00:00:00 2001 From: Maksim Makushchenko Date: Fri, 28 Nov 2025 10:50:35 +0300 Subject: [PATCH 04/10] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B0=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0?= =?UTF-8?q?=20=D1=81=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 3 --- ReleaseNotes.md | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5eccd6f..172c657 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,3 @@ -* `0.9.29` - * ✨ Каждая фотография интерактивного элемента может содержать собственную озвучку - * ➕ Добавлен biome для лингинга кода и проверки ошибок * `0.9.28` * ✨ Поддержка зума через колесико мыши * 🔨 Поддержка вращения камеры при работе с редактором diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 0faa541..874f2be 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,2 +1,2 @@ -* ✨ Поддержка зума через колесико мыши -* 🔨 Поддержка вращения камеры при работе с редактором \ No newline at end of file +* ✨ Каждая фотография интерактивного элемента может содержать собственную озвучку +* ➕ Добавлен biome для лингинга кода и проверки ошибок \ No newline at end of file From 794b7fcf31fe50576b76677c50505485d087f0fb Mon Sep 17 00:00:00 2001 From: Maksim Makushchenko Date: Fri, 28 Nov 2025 15:32:42 +0300 Subject: [PATCH 05/10] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BD=D0=B0=D0=BA=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BE=D0=BA=20=D0=B0=D1=83=D0=B4=D0=B8=D0=BE=20=D0=BF?= =?UTF-8?q?=D1=80=D0=B8=20=D0=B2=D0=BE=D1=81=D0=BF=D1=80=D0=BE=D0=B8=D0=B7?= =?UTF-8?q?=D0=B2=D0=B5=D0=B4=D0=B5=D0=BD=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReleaseNotes.md | 1 + src/Models/BackgroundAudio/BackgroundAudioView.ts | 4 +++- src/Models/FieldItem.ts | 2 -- src/Models/FieldItemContents/AudioContent.ts | 7 +++++++ src/Models/FieldItemContents/ImagesContent.ts | 4 +++- src/Models/FieldItemContents/VideoContent.ts | 5 +---- src/Models/IconBottom.ts | 5 +---- src/Models/PrefetchResourcesManager.ts | 6 +++--- src/TempTimerLogic.ts | 6 +++--- src/Viewer.ts | 8 +++++--- src/index.ts | 2 +- 11 files changed, 28 insertions(+), 22 deletions(-) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 874f2be..29e66c5 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,2 +1,3 @@ * ✨ Каждая фотография интерактивного элемента может содержать собственную озвучку +* 🐛 Исправлено одновременное воспроизведение фонового аудио с аудио из интерактивных элемнетов * ➕ Добавлен biome для лингинга кода и проверки ошибок \ No newline at end of file diff --git a/src/Models/BackgroundAudio/BackgroundAudioView.ts b/src/Models/BackgroundAudio/BackgroundAudioView.ts index 7e2fa90..42370d9 100644 --- a/src/Models/BackgroundAudio/BackgroundAudioView.ts +++ b/src/Models/BackgroundAudio/BackgroundAudioView.ts @@ -12,7 +12,7 @@ export class BackgroundAudioView { private packs: Map = new Map(); private currentAudioPack: AudioContainer | null = null; private eventTrigger: IBackgroundAudioEventTrigger | null = null; - private triggerInterval: any; + private triggerInterval: NodeJS.Timeout; private get isPlay(): boolean { return this.currentAudioPack?.isPlaying() === true; @@ -22,6 +22,7 @@ export class BackgroundAudioView { private scene: Scene, private sceneUrl: string, private fullScreenUI: FullScreenGUI, + private onStartPlaying: () => void, ) { fullScreenUI.onPlayPauseBackgroundAudioClickObservable.add(() => { if (this.currentAudioPack) { @@ -65,6 +66,7 @@ export class BackgroundAudioView { public play() { this.currentAudioPack?.play(true); + this.onStartPlaying(); } public pause() { diff --git a/src/Models/FieldItem.ts b/src/Models/FieldItem.ts index 2692463..8c665c8 100644 --- a/src/Models/FieldItem.ts +++ b/src/Models/FieldItem.ts @@ -176,13 +176,11 @@ export class FieldItem extends LinkToState { this.fieldItemInfo.videos[0], // TODO: handle all videos backgroundPlane, FieldItem.containerSize * 1.6, - FieldItem.containerSize, () => { this.onPlayMedia(); this.audioContent?.pauseAudio(); }, this.gui3Dmanager, - this.assetsManager, this.scene, ); this.contentList.push(videoContent); diff --git a/src/Models/FieldItemContents/AudioContent.ts b/src/Models/FieldItemContents/AudioContent.ts index fea1c24..59398ba 100644 --- a/src/Models/FieldItemContents/AudioContent.ts +++ b/src/Models/FieldItemContents/AudioContent.ts @@ -161,6 +161,10 @@ export class AudioContent implements FieldItemContent { } public setAudioContent(audioContent: FieldItemAudioContent) { + this.setIsVisible(true); + if (audioContent.src === this.audioInfo?.src) { + return; + } this.audioInfo = audioContent; const audio = new Sound( "audio_content", @@ -173,6 +177,9 @@ export class AudioContent implements FieldItemContent { this.playPauseButtonText.text = ExcursionConstants.PlayIcon; this.currentPositionTimer = setInterval(() => { + this.playPauseButtonText.text = this.audio?.isPlaying + ? ExcursionConstants.PauseIcon + : ExcursionConstants.PlayIcon; this.currentPositionText.text = this.getCurrentPositionText(); }, 500); }, diff --git a/src/Models/FieldItemContents/ImagesContent.ts b/src/Models/FieldItemContents/ImagesContent.ts index 3fb025a..d2007e7 100644 --- a/src/Models/FieldItemContents/ImagesContent.ts +++ b/src/Models/FieldItemContents/ImagesContent.ts @@ -151,7 +151,9 @@ export class ImagesContent implements FieldItemContent { } this.currentImage = index; if (this.images[index].audio) { - this.parentAudioContent.setAudioContent(this.images[index].audio); + this.parentAudioContent?.setAudioContent(this.images[index].audio); + } else { + this.parentAudioContent?.setIsVisible(false); } } diff --git a/src/Models/FieldItemContents/VideoContent.ts b/src/Models/FieldItemContents/VideoContent.ts index 4510d1d..4750d85 100644 --- a/src/Models/FieldItemContents/VideoContent.ts +++ b/src/Models/FieldItemContents/VideoContent.ts @@ -7,7 +7,6 @@ import { Color3 } from "@babylonjs/core/Maths/math.color"; import type { Mesh } from "@babylonjs/core/Meshes/mesh"; import { MeshBuilder } from "@babylonjs/core/Meshes/meshBuilder"; import type { TransformNode } from "@babylonjs/core/Meshes/transformNode"; -import type { AssetsManager } from "@babylonjs/core/Misc/assetsManager"; import type { Scene } from "@babylonjs/core/scene"; import { TextWrapping } from "@babylonjs/gui/2D/controls"; import { TextBlock } from "@babylonjs/gui/2D/controls/textBlock"; @@ -32,13 +31,11 @@ export class VideoContent implements FieldItemContent { private playPauseButton: CustomHolographicButton; private playPauseButtonText: TextBlock; constructor( - private videoUrl: string, + videoUrl: string, private parent: TransformNode, private contentWidth: number, - private contentHeight: number, private onPlay: () => void, private gui3Dmanager: GUI3DManager, - private assetsManager: AssetsManager, private scene: Scene, ) { this.loadVideoResources(videoUrl); diff --git a/src/Models/IconBottom.ts b/src/Models/IconBottom.ts index 873c254..7bd1ee4 100644 --- a/src/Models/IconBottom.ts +++ b/src/Models/IconBottom.ts @@ -6,10 +6,7 @@ import type { Scene } from "@babylonjs/core/scene"; import type { BottomImageConfiguration } from "../Configuration/Configuration"; export class IconBottom { - constructor( - _scene: Scene, - config: BottomImageConfiguration, - ) { + constructor(_scene: Scene, config: BottomImageConfiguration) { const bottomPlane = MeshBuilder.CreatePlane( `background_image_plane`, { diff --git a/src/Models/PrefetchResourcesManager.ts b/src/Models/PrefetchResourcesManager.ts index 5952df2..e36341e 100644 --- a/src/Models/PrefetchResourcesManager.ts +++ b/src/Models/PrefetchResourcesManager.ts @@ -10,9 +10,9 @@ export class PrefetchResourcesManager { return; } const preloadLink = window.document.createElement("link"); - preloadLink["rel"] = "prefetch"; - preloadLink["as"] = "fetch"; - preloadLink["href"] = link; + preloadLink.rel = "prefetch"; + preloadLink.as = "fetch"; + preloadLink.href = link; this.linksContainer.appendChild(preloadLink); this.addedLinks.add(link); } diff --git a/src/TempTimerLogic.ts b/src/TempTimerLogic.ts index bd2916b..7c0844c 100644 --- a/src/TempTimerLogic.ts +++ b/src/TempTimerLogic.ts @@ -93,15 +93,15 @@ export class TempTimerLogic { scene, 0, () => { - imageToShow!.imagePlane.animations.push(showAnimation); - scene.beginAnimation(imageToShow!.imagePlane, 0, frameRate, true); + imageToShow.imagePlane.animations.push(showAnimation); + scene.beginAnimation(imageToShow.imagePlane, 0, frameRate, true); }, ); await assetsManager.loadAsync(); onContentCreated(imageToShow); }, async () => { - scene.beginAnimation(imageToShow!.imagePlane, frameRate, 0, true); + scene.beginAnimation(imageToShow.imagePlane, frameRate, 0, true); setTimeout(() => { imageToShow.dispose(); }, 1500); diff --git a/src/Viewer.ts b/src/Viewer.ts index c4f5fc7..6a3f033 100644 --- a/src/Viewer.ts +++ b/src/Viewer.ts @@ -89,6 +89,9 @@ export class Viewer { scene, this.configuration.sceneUrl, this.fullScreenGUI, + () => { + this.links.pauseAllAudios(); + }, ); let spacePressed = false; scene.onKeyboardObservable.add((ev) => { @@ -292,9 +295,8 @@ export class Viewer { this.currentPicture = targetPicture; this.cleanResources(); this.prefetchAudio(targetPicture); - await this.drawImage( - targetPicture, - () => actionBeforeChange?.(targetPicture), + await this.drawImage(targetPicture, () => + actionBeforeChange?.(targetPicture), ); this.fullScreenGUI.setFastReturnToFirstStateVisible( diff --git a/src/index.ts b/src/index.ts index 93b168e..35ea619 100644 --- a/src/index.ts +++ b/src/index.ts @@ -22,7 +22,7 @@ document.addEventListener("DOMContentLoaded", async () => { if (configuration.sceneUrl && !configuration.sceneUrl.endsWith("/")) { configuration.sceneUrl += "/"; } - const tourResponse = await fetch(configuration.sceneUrl + "tour.json"); + const tourResponse = await fetch(`${configuration.sceneUrl}tour.json`); if (tourResponse.status !== 200) { console.warn("Can't get scene description"); return; From fdf3cb8ca8ef793ef7ba7c41a676ca13f3121703 Mon Sep 17 00:00:00 2001 From: Maksim Makushchenko Date: Wed, 17 Dec 2025 13:23:55 +0300 Subject: [PATCH 06/10] =?UTF-8?q?=D0=95=D0=B4=D0=B8=D0=BD=D0=B0=D1=8F=20?= =?UTF-8?q?=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D0=B0=20=D1=81=D0=BA=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D1=8B=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=BF=D1=83=D1=82?= =?UTF-8?q?=D0=B5=D0=B9,=20=D0=BF=D0=BE=D0=BA=D1=80=D1=8B=D1=82=D0=B0?= =?UTF-8?q?=D1=8F=20=D1=82=D0=B5=D1=81=D1=82=D0=B0=D0=BC=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 1430 +++++++++++++++++- package.json | 4 +- scripts/downloadWebXrProfiles.js | 2 +- src/Models/BackgroundAudio/AudioContainer.ts | 3 +- src/TempTimerLogic.ts | 5 +- src/Viewer.ts | 33 +- src/concatUrlFromPathes.test.ts | 30 + src/concatUrlFromPathes.ts | 15 + src/index.ts | 3 +- 9 files changed, 1488 insertions(+), 37 deletions(-) create mode 100644 src/concatUrlFromPathes.test.ts create mode 100644 src/concatUrlFromPathes.ts diff --git a/package-lock.json b/package-lock.json index 0a256ea..9722d09 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "ts-node": "^10.9.2", "tslint": "^6.1.3", "typescript": "^5.3.3", + "vitest": "^4.0.16", "webpack": "^5.89.0", "webpack-cli": "^5.1.4", "webpack-dev-server": "^4.15.1" @@ -445,6 +446,448 @@ "node": ">=10.0.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@fortawesome/fontawesome-common-types": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.1.tgz", @@ -533,10 +976,11 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.9", @@ -554,6 +998,321 @@ "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", "dev": true }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.5.tgz", + "integrity": "sha512-iDGS/h7D8t7tvZ1t6+WPK04KD0MwzLZrG0se1hzBjSi5fyxlsiggoJHwh18PCFNn7tG43OWb6pdZ6Y+rMlmyNQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.5.tgz", + "integrity": "sha512-wrSAViWvZHBMMlWk6EJhvg8/rjxzyEhEdgfMMjREHEq11EtJ6IP6yfcCH57YAEca2Oe3FNCE9DSTgU70EIGmVw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.5.tgz", + "integrity": "sha512-S87zZPBmRO6u1YXQLwpveZm4JfPpAa6oHBX7/ghSiGH3rz/KDgAu1rKdGutV+WUI6tKDMbaBJomhnT30Y2t4VQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.5.tgz", + "integrity": "sha512-YTbnsAaHo6VrAczISxgpTva8EkfQus0VPEVJCEaboHtZRIb6h6j0BNxRBOwnDciFTZLDPW5r+ZBmhL/+YpTZgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.5.tgz", + "integrity": "sha512-1T8eY2J8rKJWzaznV7zedfdhD1BqVs1iqILhmHDq/bqCUZsrMt+j8VCTHhP0vdfbHK3e1IQ7VYx3jlKqwlf+vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.5.tgz", + "integrity": "sha512-sHTiuXyBJApxRn+VFMaw1U+Qsz4kcNlxQ742snICYPrY+DDL8/ZbaC4DVIB7vgZmp3jiDaKA0WpBdP0aqPJoBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.5.tgz", + "integrity": "sha512-dV3T9MyAf0w8zPVLVBptVlzaXxka6xg1f16VAQmjg+4KMSTWDvhimI/Y6mp8oHwNrmnmVl9XxJ/w/mO4uIQONA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.5.tgz", + "integrity": "sha512-wIGYC1x/hyjP+KAu9+ewDI+fi5XSNiUi9Bvg6KGAh2TsNMA3tSEs+Sh6jJ/r4BV/bx/CyWu2ue9kDnIdRyafcQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.5.tgz", + "integrity": "sha512-Y+qVA0D9d0y2FRNiG9oM3Hut/DgODZbU9I8pLLPwAsU0tUKZ49cyV1tzmB/qRbSzGvY8lpgGkJuMyuhH7Ma+Vg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.5.tgz", + "integrity": "sha512-juaC4bEgJsyFVfqhtGLz8mbopaWD+WeSOYr5E16y+1of6KQjc0BpwZLuxkClqY1i8sco+MdyoXPNiCkQou09+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.5.tgz", + "integrity": "sha512-rIEC0hZ17A42iXtHX+EPJVL/CakHo+tT7W0pbzdAGuWOt2jxDFh7A/lRhsNHBcqL4T36+UiAgwO8pbmn3dE8wA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.5.tgz", + "integrity": "sha512-T7l409NhUE552RcAOcmJHj3xyZ2h7vMWzcwQI0hvn5tqHh3oSoclf9WgTl+0QqffWFG8MEVZZP1/OBglKZx52Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.5.tgz", + "integrity": "sha512-7OK5/GhxbnrMcxIFoYfhV/TkknarkYC1hqUw1wU2xUN3TVRLNT5FmBv4KkheSG2xZ6IEbRAhTooTV2+R5Tk0lQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.5.tgz", + "integrity": "sha512-GwuDBE/PsXaTa76lO5eLJTyr2k8QkPipAyOrs4V/KJufHCZBJ495VCGJol35grx9xryk4V+2zd3Ri+3v7NPh+w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.5.tgz", + "integrity": "sha512-IAE1Ziyr1qNfnmiQLHBURAD+eh/zH1pIeJjeShleII7Vj8kyEm2PF77o+lf3WTHDpNJcu4IXJxNO0Zluro8bOw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.5.tgz", + "integrity": "sha512-Pg6E+oP7GvZ4XwgRJBuSXZjcqpIW3yCBhK4BcsANvb47qMvAbCjR6E+1a/U2WXz1JJxp9/4Dno3/iSJLcm5auw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.5.tgz", + "integrity": "sha512-txGtluxDKTxaMDzUduGP0wdfng24y1rygUMnmlUJ88fzCCULCLn7oE5kb2+tRB+MWq1QDZT6ObT5RrR8HFRKqg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.5.tgz", + "integrity": "sha512-3DFiLPnTxiOQV993fMc+KO8zXHTcIjgaInrqlG8zDp1TlhYl6WgrOHuJkJQ6M8zHEcntSJsUp1XFZSY8C1DYbg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.5.tgz", + "integrity": "sha512-nggc/wPpNTgjGg75hu+Q/3i32R00Lq1B6N1DO7MCU340MRKL3WZJMjA9U4K4gzy3dkZPXm9E1Nc81FItBVGRlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.5.tgz", + "integrity": "sha512-U/54pTbdQpPLBdEzCT6NBCFAfSZMvmjr0twhnD9f4EIvlm9wy3jjQ38yQj1AGznrNO65EWQMgm/QUjuIVrYF9w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.5.tgz", + "integrity": "sha512-2NqKgZSuLH9SXBBV2dWNRCZmocgSOx8OJSdpRaEcRlIfX8YrKxUT6z0F1NpvDVhOsl190UFTRh2F2WDWWCYp3A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.5.tgz", + "integrity": "sha512-JRpZUhCfhZ4keB5v0fe02gQJy05GqboPOaxvjugW04RLSYYoB/9t2lx2u/tMs/Na/1NXfY8QYjgRljRpN+MjTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -597,6 +1356,17 @@ "@types/node": "*" } }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, "node_modules/@types/connect": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", @@ -616,11 +1386,19 @@ "@types/node": "*" } }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "dev": true + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" }, "node_modules/@types/express": { "version": "4.17.21", @@ -782,6 +1560,90 @@ "@types/node": "*" } }, + "node_modules/@vitest/expect": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.16.tgz", + "integrity": "sha512-eshqULT2It7McaJkQGLkPjPjNph+uevROGuIMJdG3V+0BSR2w9u6J9Lwu+E8cK5TETlfou8GRijhafIMhXsimA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.16", + "@vitest/utils": "4.0.16", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.16.tgz", + "integrity": "sha512-eNCYNsSty9xJKi/UdVD8Ou16alu7AYiS2fCPRs0b1OdhJiV89buAXQLpTbe+X8V9L6qrs9CqyvU7OaAopJYPsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.16.tgz", + "integrity": "sha512-VWEDm5Wv9xEo80ctjORcTQRJ539EGPB3Pb9ApvVRAY1U/WkHXmmYISqU5E79uCwcW7xYUV38gwZD+RV755fu3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.0.16", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.16.tgz", + "integrity": "sha512-sf6NcrYhYBsSYefxnry+DR8n3UV4xWZwWxYbCJUt2YdvtqzSPR7VfGrY0zsv090DAbjFZsi7ZaMi1KnSRyK1XA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.16", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.16.tgz", + "integrity": "sha512-4jIOWjKP0ZUaEmJm00E0cOBLU+5WE0BpeNr3XN6TEF05ltro6NJqHWxXD0kA8/Zc8Nh23AT8WQxwNG+WeROupw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.16.tgz", + "integrity": "sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.16", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.12.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", @@ -1152,6 +2014,16 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "dev": true }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/babylonjs-gltf2interface": { "version": "7.5.0", "resolved": "https://registry.npmjs.org/babylonjs-gltf2interface/-/babylonjs-gltf2interface-7.5.0.tgz", @@ -1344,6 +2216,16 @@ } ] }, + "node_modules/chai": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.1.tgz", + "integrity": "sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1753,10 +2635,53 @@ } }, "node_modules/es-module-lexer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", - "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==", - "dev": true + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } }, "node_modules/escalade": { "version": "3.1.1", @@ -1838,6 +2763,16 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -1885,6 +2820,16 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/express": { "version": "4.21.0", "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", @@ -2683,6 +3628,16 @@ "node": ">=10" } }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -2847,6 +3802,25 @@ "multicast-dns": "cli.js" } }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -2916,6 +3890,17 @@ "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", "dev": true }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -3075,11 +4060,19 @@ "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", "dev": true }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", @@ -3105,6 +4098,35 @@ "node": ">=8" } }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -3314,6 +4336,48 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rollup": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.5.tgz", + "integrity": "sha512-iTNAbFSlRpcHeeWu73ywU/8KuU/LZmNCSxp6fjQkJBD3ivUb8tpDrXhIxEzA05HlYMEwmtaUnb3RP+YNv162OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.53.5", + "@rollup/rollup-android-arm64": "4.53.5", + "@rollup/rollup-darwin-arm64": "4.53.5", + "@rollup/rollup-darwin-x64": "4.53.5", + "@rollup/rollup-freebsd-arm64": "4.53.5", + "@rollup/rollup-freebsd-x64": "4.53.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.5", + "@rollup/rollup-linux-arm-musleabihf": "4.53.5", + "@rollup/rollup-linux-arm64-gnu": "4.53.5", + "@rollup/rollup-linux-arm64-musl": "4.53.5", + "@rollup/rollup-linux-loong64-gnu": "4.53.5", + "@rollup/rollup-linux-ppc64-gnu": "4.53.5", + "@rollup/rollup-linux-riscv64-gnu": "4.53.5", + "@rollup/rollup-linux-riscv64-musl": "4.53.5", + "@rollup/rollup-linux-s390x-gnu": "4.53.5", + "@rollup/rollup-linux-x64-gnu": "4.53.5", + "@rollup/rollup-linux-x64-musl": "4.53.5", + "@rollup/rollup-openharmony-arm64": "4.53.5", + "@rollup/rollup-win32-arm64-msvc": "4.53.5", + "@rollup/rollup-win32-ia32-msvc": "4.53.5", + "@rollup/rollup-win32-x64-gnu": "4.53.5", + "@rollup/rollup-win32-x64-msvc": "4.53.5", + "fsevents": "~2.3.2" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -3601,6 +4665,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -3627,6 +4698,16 @@ "node": ">= 8" } }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", @@ -3728,6 +4809,13 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -3737,6 +4825,13 @@ "node": ">= 0.8" } }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -3856,6 +4951,81 @@ "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", "dev": true }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -4179,6 +5349,217 @@ "node": ">= 0.8" } }, + "node_modules/vitest": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.16.tgz", + "integrity": "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.0.16", + "@vitest/mocker": "4.0.16", + "@vitest/pretty-format": "4.0.16", + "@vitest/runner": "4.0.16", + "@vitest/snapshot": "4.0.16", + "@vitest/spy": "4.0.16", + "@vitest/utils": "4.0.16", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.16", + "@vitest/browser-preview": "4.0.16", + "@vitest/browser-webdriverio": "4.0.16", + "@vitest/ui": "4.0.16", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/@vitest/mocker": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.16.tgz", + "integrity": "sha512-yb6k4AZxJTB+q9ycAvsoxGn+j/po0UaPgajllBgt1PzoMAAmJGYFdDk0uCcRcxb3BrME34I6u8gHZTQlkqSZpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.0.16", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest/node_modules/vite": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz", + "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, "node_modules/watchpack": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", @@ -4550,6 +5931,23 @@ "node": ">= 8" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wildcard": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", diff --git a/package.json b/package.json index b7b0929..81c66a4 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "start": "webpack-dev-server --mode=development", "build": "webpack --mode=production", "devBuild": "webpack --mode=development", + "test": "vitest", "lint": "biome check ./src --diagnostic-level=error", "lint:fix": "biome check ./src --write" }, @@ -27,8 +28,9 @@ "ts-node": "^10.9.2", "tslint": "^6.1.3", "typescript": "^5.3.3", + "vitest": "^4.0.16", "webpack": "^5.89.0", "webpack-cli": "^5.1.4", "webpack-dev-server": "^4.15.1" } -} +} \ No newline at end of file diff --git a/scripts/downloadWebXrProfiles.js b/scripts/downloadWebXrProfiles.js index 923cff8..6fc69c5 100644 --- a/scripts/downloadWebXrProfiles.js +++ b/scripts/downloadWebXrProfiles.js @@ -7,7 +7,7 @@ const inBuild = (path) => `./build/xrrepo/profiles/${path}`; (async () => { const response = await fetch( - webXRBaseRepositoryUrl + "/profiles/profilesList.json" + `${webXRBaseRepositoryUrl}/profiles/profilesList.json` ); const jsonProfiles = await response.json(); console.log(jsonProfiles); diff --git a/src/Models/BackgroundAudio/AudioContainer.ts b/src/Models/BackgroundAudio/AudioContainer.ts index ecb7385..f954a9d 100644 --- a/src/Models/BackgroundAudio/AudioContainer.ts +++ b/src/Models/BackgroundAudio/AudioContainer.ts @@ -3,6 +3,7 @@ import type { Scene } from "@babylonjs/core/scene"; import { concatUrlPath } from "../../Stuff/UrlHelpers"; import { PlayAudioHelper } from "../..//WorkWithAudio/PlayAudioHelper"; import type { BackgroundAudioInfo } from "../ExcursionModels/BackgroundAudioInfo"; +import { concatUrlFromPathes } from "../../concatUrlFromPathes"; export class AudioContainer { private sounds: Sound[]; @@ -26,7 +27,7 @@ export class AudioContainer { sceneUrl: string, ) { this.sounds = info.audios.map((s, i) => - this.createSound(this.info.id, concatUrlPath(sceneUrl, s), i), + this.createSound(this.info.id, concatUrlFromPathes(sceneUrl, s), i), ); } diff --git a/src/TempTimerLogic.ts b/src/TempTimerLogic.ts index 7c0844c..be015a4 100644 --- a/src/TempTimerLogic.ts +++ b/src/TempTimerLogic.ts @@ -13,11 +13,14 @@ import type { } from "./Models/ExcursionModels/BackgroundAudioInfo"; import { ImageContentItem } from "./Models/ImageContentItem"; import type { PrefetchResourcesManager } from "./Models/PrefetchResourcesManager"; +import { concatUrlFromPathes } from "./concatUrlFromPathes"; /** * Временная обработка логики показа фотографии только на определенный момент времени воспроизводимого аудио-сопровождения * Не заложено в формат экскурсии, наполняется в ручном режиме */ + +// biome-ignore lint/complexity/noStaticOnlyClass: Статичные классы удобны export class TempTimerLogic { public static handleTempTimer( backgroundAudio: BackgroundAudioInfo | undefined, @@ -55,7 +58,7 @@ export class TempTimerLogic { onContentCreated: (content: ImageContentItem) => void, ): IBackgroundAudioEventTrigger { let imageToShow: ImageContentItem | null = null; - const imageLink = sceneUrl + tempTimer.content.image; + const imageLink = concatUrlFromPathes(sceneUrl, tempTimer.content.image); // предзагружаем изображение, чтобы оно показывалось моментально prefetchResourcesManager.addResource(imageLink); diff --git a/src/Viewer.ts b/src/Viewer.ts index 6a3f033..de46898 100644 --- a/src/Viewer.ts +++ b/src/Viewer.ts @@ -40,6 +40,7 @@ import type { WebXRInterface } from "./AsyncModules/AsyncModuleInterfaces"; import { PrefetchResourcesManager } from "./Models/PrefetchResourcesManager"; import { concatUrlPath } from "./Stuff/UrlHelpers"; import { PlayAudioHelper } from "./WorkWithAudio/PlayAudioHelper"; +import { concatUrlFromPathes } from "./concatUrlFromPathes"; export class Viewer { private currentImage: DynamicPhotoDome | null = null; @@ -65,7 +66,7 @@ export class Viewer { freeCamera: FreeCamera; - constructor(private configuration: Configuration) {} + constructor(private configuration: Configuration) { } private backgroundRadius = 500; @@ -301,7 +302,7 @@ export class Viewer { this.fullScreenGUI.setFastReturnToFirstStateVisible( id !== this.viewScene.firstStateId && - this.viewScene.fastReturnToFirstStateEnabled, + this.viewScene.fastReturnToFirstStateEnabled, ); document.title = targetPicture.title || this.viewScene.title; @@ -333,7 +334,7 @@ export class Viewer { const material = this.linkSphereMaterials[link.colorScheme]; const linkToState = this.links.getLink(name, position, material, () => { - let rotateCam = () => {}; + let rotateCam = () => { }; if (link.rotationAfterStepAngleOverridden) { rotateCam = () => { this.rotateCamToAngle(link.rotationAfterStepAngle); @@ -372,7 +373,7 @@ export class Viewer { }); }, async (selectedId) => { - let rotateCam = () => {}; + let rotateCam = () => { }; var overridePair = groupLink.groupStateRotationOverrides.find( (p) => p.stateId === selectedId, ); @@ -395,19 +396,19 @@ export class Viewer { ), fieldItem.imageContent.map((i) => ({ ...i, - imageSrc: this.configuration.sceneUrl + i.imageSrc, + imageSrc: concatUrlFromPathes(this.configuration.sceneUrl, i.imageSrc), audio: i.audio ? { - ...i.audio, - src: this.configuration.sceneUrl + i.audio.src, - } + ...i.audio, + src: concatUrlFromPathes(this.configuration.sceneUrl, i.audio.src), + } : null, })), fieldItem.videos.map((v) => this.configuration.sceneUrl + v), fieldItem.text, fieldItem.audios.map((a) => ({ ...a, - src: this.configuration.sceneUrl + a.src, + src: concatUrlFromPathes(this.configuration.sceneUrl, a.src), })), distanceToLinks, ); @@ -433,7 +434,7 @@ export class Viewer { const imageContent = new ImageContentItem( { ...contentItem, - image: this.configuration.sceneUrl + contentItem.image, + image: concatUrlFromPathes(this.configuration.sceneUrl, contentItem.image), }, this.assetsManager, this.scene, @@ -472,7 +473,7 @@ export class Viewer { state.fieldItems.forEach((fi) => { fi.audios.forEach((a) => { this.prefetchResourcesManager.addResource( - this.configuration.sceneUrl + a.src, + concatUrlFromPathes(this.configuration.sceneUrl, a.src), ); }); }); @@ -488,7 +489,7 @@ export class Viewer { this.scene, ); this.currentImage.setCanvasSize(this.canvas.width, this.canvas.height); - this.scene.onAfterRenderObservable.add((scene, event) => + this.scene.onAfterRenderObservable.add((scene) => this.currentImage.trackImageParts(scene), ); } @@ -499,21 +500,21 @@ export class Viewer { let postAction: (image: HTMLImageElement) => void; if (targetPicture.croppedImageUrl) { const imageRoot = - this.configuration.sceneUrl + targetPicture.croppedImageUrl; + concatUrlFromPathes(this.configuration.sceneUrl, targetPicture.croppedImageUrl); - const metaInfoLocation = imageRoot + "/meta.json"; + const metaInfoLocation = concatUrlFromPathes(imageRoot, "/meta.json"); const meta = (await ( await fetch(metaInfoLocation) ).json()) as CroppedImage; - imageUrl = imageRoot + "/" + meta.lowQualityImage.route; + imageUrl = concatUrlFromPathes(imageRoot, meta.lowQualityImage.route); postAction = (image) => { this.currentImage.setByImage(image, meta); this.currentImage.setImageParts(imageRoot, meta.rectangles); }; } else if (targetPicture.url) { - imageUrl = this.configuration.sceneUrl + targetPicture.url; + imageUrl = concatUrlFromPathes(this.configuration.sceneUrl, targetPicture.url); postAction = (image) => { this.currentImage.setByImage(image); }; diff --git a/src/concatUrlFromPathes.test.ts b/src/concatUrlFromPathes.test.ts new file mode 100644 index 0000000..27f3894 --- /dev/null +++ b/src/concatUrlFromPathes.test.ts @@ -0,0 +1,30 @@ +import { expect, test } from 'vitest' +import { concatUrlFromPathes } from './concatUrlFromPathes' + +test("Простое сложение путей", () => { + expect(concatUrlFromPathes("foo", "bar")).toBe("foo/bar") +}) + +test("Если во втором есть /", () => { + expect(concatUrlFromPathes("foo", "/bar")).toBe("foo/bar") +}) + +test("Если в первом есть /", () => { + expect(concatUrlFromPathes("foo/", "bar")).toBe("foo/bar") +}) + +test("Если в обоих есть /", () => { + expect(concatUrlFromPathes("foo/", "/bar")).toBe("foo/bar") +}) + +test("Стартовый / не обрабатывается", () => { + expect(concatUrlFromPathes("/foo", "bar")).toBe("/foo/bar") +}) + +test("Конечный / не обрабатывается", () => { + expect(concatUrlFromPathes("foo", "bar/")).toBe("foo/bar/") +}) + +test("Много путей на всякий случай", () => { + expect(concatUrlFromPathes("foo", "bar/" , "one", "more")).toBe("foo/bar/one/more") +}) \ No newline at end of file diff --git a/src/concatUrlFromPathes.ts b/src/concatUrlFromPathes.ts new file mode 100644 index 0000000..c761293 --- /dev/null +++ b/src/concatUrlFromPathes.ts @@ -0,0 +1,15 @@ +export function concatUrlFromPathes(...pathes: string[]) { + return pathes.map((path, i) => { + if (i > 0) { + while (path.startsWith('/')) { + path = path.substring(1, path.length); + } + } + if (i < pathes.length - 1) { + while (path.endsWith('/')) { + path = path.substring(0, path.length - 1); + } + } + return path; + }).join("/"); +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 35ea619..4a8ac65 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,4 @@ +import { concatUrlFromPathes } from "./concatUrlFromPathes"; import { BuildConfiguration } from "./Configuration/BuildConfiguration"; import type { Configuration } from "./Configuration/Configuration"; import type { Excursion } from "./Models/ExcursionModels/Excursion"; @@ -22,7 +23,7 @@ document.addEventListener("DOMContentLoaded", async () => { if (configuration.sceneUrl && !configuration.sceneUrl.endsWith("/")) { configuration.sceneUrl += "/"; } - const tourResponse = await fetch(`${configuration.sceneUrl}tour.json`); + const tourResponse = await fetch(concatUrlFromPathes(configuration.sceneUrl, "tour.json")); if (tourResponse.status !== 200) { console.warn("Can't get scene description"); return; From 7c49197857769b8a0fff250f03ea0ddcd29186b8 Mon Sep 17 00:00:00 2001 From: Maksim Makushchenko Date: Wed, 17 Dec 2025 13:31:53 +0300 Subject: [PATCH 07/10] =?UTF-8?q?=D0=9F=D0=BE=D0=BB=D0=BD=D1=8B=D0=B9=20?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D1=85=D0=BE=D0=B4=20=D0=BD=D0=B0=20?= =?UTF-8?q?=D0=BA=D0=BE=D1=80=D1=80=D0=B5=D0=BA=D1=82=D0=BD=D1=83=D1=8E=20?= =?UTF-8?q?=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D1=8E=20=D1=81=D0=BA=D0=BB?= =?UTF-8?q?=D0=B5=D0=B8=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F=20=D1=81=D1=82=D1=80?= =?UTF-8?q?=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Models/BackgroundAudio/AudioContainer.ts | 3 +-- src/Stuff/UrlHelpers.ts | 16 ---------------- src/{ => Stuff}/concatUrlFromPathes.test.ts | 4 ++++ src/{ => Stuff}/concatUrlFromPathes.ts | 0 src/TempTimerLogic.ts | 2 +- src/Viewer.ts | 5 ++--- src/index.ts | 2 +- 7 files changed, 9 insertions(+), 23 deletions(-) delete mode 100644 src/Stuff/UrlHelpers.ts rename src/{ => Stuff}/concatUrlFromPathes.test.ts (87%) rename src/{ => Stuff}/concatUrlFromPathes.ts (100%) diff --git a/src/Models/BackgroundAudio/AudioContainer.ts b/src/Models/BackgroundAudio/AudioContainer.ts index f954a9d..840dc8e 100644 --- a/src/Models/BackgroundAudio/AudioContainer.ts +++ b/src/Models/BackgroundAudio/AudioContainer.ts @@ -1,9 +1,8 @@ import { Sound } from "@babylonjs/core/Audio/sound"; import type { Scene } from "@babylonjs/core/scene"; -import { concatUrlPath } from "../../Stuff/UrlHelpers"; import { PlayAudioHelper } from "../..//WorkWithAudio/PlayAudioHelper"; import type { BackgroundAudioInfo } from "../ExcursionModels/BackgroundAudioInfo"; -import { concatUrlFromPathes } from "../../concatUrlFromPathes"; +import { concatUrlFromPathes } from "../../Stuff/concatUrlFromPathes"; export class AudioContainer { private sounds: Sound[]; diff --git a/src/Stuff/UrlHelpers.ts b/src/Stuff/UrlHelpers.ts deleted file mode 100644 index 195528a..0000000 --- a/src/Stuff/UrlHelpers.ts +++ /dev/null @@ -1,16 +0,0 @@ -export function concatUrlPath(baseUrl: string, ...parts: string[]): string { - if (baseUrl.endsWith("/")) { - baseUrl = baseUrl.substring(0, baseUrl.lastIndexOf("/")); - } - let resultUrl = baseUrl; - for (let part of parts) { - if (part.endsWith("/")) { - part = part.substring(0, part.lastIndexOf("/")); - } - if (part.startsWith("/")) { - part = part.substring(1, part.length); - } - resultUrl += `/${part}`; - } - return resultUrl; -} diff --git a/src/concatUrlFromPathes.test.ts b/src/Stuff/concatUrlFromPathes.test.ts similarity index 87% rename from src/concatUrlFromPathes.test.ts rename to src/Stuff/concatUrlFromPathes.test.ts index 27f3894..c6792e5 100644 --- a/src/concatUrlFromPathes.test.ts +++ b/src/Stuff/concatUrlFromPathes.test.ts @@ -25,6 +25,10 @@ test("Конечный / не обрабатывается", () => { expect(concatUrlFromPathes("foo", "bar/")).toBe("foo/bar/") }) +test("Множественные / тоже убираются", () => { + expect(concatUrlFromPathes("foo///", "////bar")).toBe("foo/bar") +}) + test("Много путей на всякий случай", () => { expect(concatUrlFromPathes("foo", "bar/" , "one", "more")).toBe("foo/bar/one/more") }) \ No newline at end of file diff --git a/src/concatUrlFromPathes.ts b/src/Stuff/concatUrlFromPathes.ts similarity index 100% rename from src/concatUrlFromPathes.ts rename to src/Stuff/concatUrlFromPathes.ts diff --git a/src/TempTimerLogic.ts b/src/TempTimerLogic.ts index be015a4..7f337d0 100644 --- a/src/TempTimerLogic.ts +++ b/src/TempTimerLogic.ts @@ -13,7 +13,7 @@ import type { } from "./Models/ExcursionModels/BackgroundAudioInfo"; import { ImageContentItem } from "./Models/ImageContentItem"; import type { PrefetchResourcesManager } from "./Models/PrefetchResourcesManager"; -import { concatUrlFromPathes } from "./concatUrlFromPathes"; +import { concatUrlFromPathes } from "./Stuff/concatUrlFromPathes"; /** * Временная обработка логики показа фотографии только на определенный момент времени воспроизводимого аудио-сопровождения diff --git a/src/Viewer.ts b/src/Viewer.ts index de46898..23a3665 100644 --- a/src/Viewer.ts +++ b/src/Viewer.ts @@ -38,9 +38,8 @@ import "@babylonjs/core/Culling/ray"; // нужно для работы клик import { KeyboardEventTypes, PointerEventTypes } from "@babylonjs/core"; import type { WebXRInterface } from "./AsyncModules/AsyncModuleInterfaces"; import { PrefetchResourcesManager } from "./Models/PrefetchResourcesManager"; -import { concatUrlPath } from "./Stuff/UrlHelpers"; import { PlayAudioHelper } from "./WorkWithAudio/PlayAudioHelper"; -import { concatUrlFromPathes } from "./concatUrlFromPathes"; +import { concatUrlFromPathes } from "./Stuff/concatUrlFromPathes"; export class Viewer { private currentImage: DynamicPhotoDome | null = null; @@ -467,7 +466,7 @@ export class Viewer { )?.audios || []; audios.forEach((a) => { this.prefetchResourcesManager.addResource( - concatUrlPath(this.configuration.sceneUrl, a), + concatUrlFromPathes(this.configuration.sceneUrl, a), ); }); state.fieldItems.forEach((fi) => { diff --git a/src/index.ts b/src/index.ts index 4a8ac65..220f4ab 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -import { concatUrlFromPathes } from "./concatUrlFromPathes"; +import { concatUrlFromPathes } from "./Stuff/concatUrlFromPathes"; import { BuildConfiguration } from "./Configuration/BuildConfiguration"; import type { Configuration } from "./Configuration/Configuration"; import type { Excursion } from "./Models/ExcursionModels/Excursion"; From f5e0d3810ebfd2e7e9b2049823569ecc430be6fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=9C=D0=B0=D0=BA?= =?UTF-8?q?=D1=83=D1=89=D0=B5=D0=BD=D0=BA=D0=BE?= Date: Thu, 18 Dec 2025 09:01:19 +0300 Subject: [PATCH 08/10] =?UTF-8?q?=D0=A3=D0=B1=D1=80=D0=B0=D0=BD=D0=BE=20?= =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20?= =?UTF-8?q?/=20=D0=B2=20=D0=B1=D0=B0=D0=B7=D0=BE=D0=B2=D1=8B=D0=B9=20?= =?UTF-8?q?=D0=B0=D0=B4=D1=80=D0=B5=D1=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 25 ++++++++++++++++++++----- src/index.ts | 7 +++---- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9722d09..836f103 100644 --- a/package-lock.json +++ b/package-lock.json @@ -193,12 +193,14 @@ "node_modules/@babylonjs/core": { "version": "7.27.0", "resolved": "https://registry.npmjs.org/@babylonjs/core/-/core-7.27.0.tgz", - "integrity": "sha512-/GyZe1b0y/LfxAxX+i20Wtkr76fqah+OJZi2F5pgF/MSpm2KfJnSObeWqZDi01CAYM9zLXTfPiMupDdhUv54vw==" + "integrity": "sha512-/GyZe1b0y/LfxAxX+i20Wtkr76fqah+OJZi2F5pgF/MSpm2KfJnSObeWqZDi01CAYM9zLXTfPiMupDdhUv54vw==", + "peer": true }, "node_modules/@babylonjs/gui": { "version": "7.27.0", "resolved": "https://registry.npmjs.org/@babylonjs/gui/-/gui-7.27.0.tgz", "integrity": "sha512-j3C8VZOcs9J6jNeF9WQTYJegLb+Vdjz7sFius7D5piz1aWsqS/MxehAjcHPzyJn6PS/7KNF+b3IdOoV7worElw==", + "peer": true, "peerDependencies": { "@babylonjs/core": "^7.0.0" } @@ -239,6 +241,7 @@ "version": "7.27.0", "resolved": "https://registry.npmjs.org/@babylonjs/loaders/-/loaders-7.27.0.tgz", "integrity": "sha512-xBlVQ/m+UWhaMEn6q1j3ktu/GcrIxHEc9ybGOkOHst4Fk+5ebvFymjbscaQ20MEWa42Eue4x55rntDGUnCjYsw==", + "peer": true, "peerDependencies": { "@babylonjs/core": "^7.0.0", "babylonjs-gltf2interface": "^7.0.0" @@ -248,6 +251,7 @@ "version": "7.27.0", "resolved": "https://registry.npmjs.org/@babylonjs/materials/-/materials-7.27.0.tgz", "integrity": "sha512-GbvOD/57UuI6u+6vdTwSIv/GlSdz0+EaR0CuEAvoopAIMiyRZAwrZgXYrXvsUo059cigh9t+wLDUMRDEjTjBnw==", + "peer": true, "peerDependencies": { "@babylonjs/core": "^7.0.0" } @@ -1456,6 +1460,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.4.tgz", "integrity": "sha512-6I0fMH8Aoy2lOejL3s4LhyIYX34DPwY8bl5xlNjBvUEk8OHrcuzsFt+Ied4LvJihbtXPM+8zUqdydfIti86v9g==", "dev": true, + "peer": true, "dependencies": { "undici-types": "~5.26.4" } @@ -1472,8 +1477,7 @@ "node_modules/@types/prop-types": { "version": "15.7.12", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", - "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==", - "peer": true + "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==" }, "node_modules/@types/qs": { "version": "6.9.11", @@ -1864,6 +1868,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1894,6 +1899,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -2140,6 +2146,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001565", "electron-to-chromium": "^1.4.601", @@ -2438,8 +2445,7 @@ "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "peer": true + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/debug": { "version": "2.6.9", @@ -4888,6 +4894,7 @@ "resolved": "https://registry.npmjs.org/terser/-/terser-5.26.0.tgz", "integrity": "sha512-dytTGoE2oHgbNV9nTzgBEPaqAWvcJNl66VZ0BkJqlvp71IjO8CxdBx/ykCNb47cLnCmCvRZ6ZR0tLkqvZCdVBQ==", "dev": true, + "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -5009,6 +5016,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -5248,6 +5256,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", "dev": true, + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -5478,6 +5487,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -5491,6 +5501,7 @@ "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -5587,6 +5598,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz", "integrity": "sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==", "dev": true, + "peer": true, "dependencies": { "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", @@ -5633,6 +5645,7 @@ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", "dev": true, + "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^2.1.1", @@ -5710,6 +5723,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dev": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -5822,6 +5836,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dev": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", diff --git a/src/index.ts b/src/index.ts index 220f4ab..51fe0d0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,10 +20,9 @@ document.addEventListener("DOMContentLoaded", async () => { viewer.createScene(configuration.minFOV, configuration.maxFOV); (document as any).viewer = viewer; try { - if (configuration.sceneUrl && !configuration.sceneUrl.endsWith("/")) { - configuration.sceneUrl += "/"; - } - const tourResponse = await fetch(concatUrlFromPathes(configuration.sceneUrl, "tour.json")); + const tourResponse = await fetch( + concatUrlFromPathes(configuration.sceneUrl ?? "", "tour.json"), + ); if (tourResponse.status !== 200) { console.warn("Can't get scene description"); return; From f1850caa65ca9a0689ca2ecdf6ce7926f47b5590 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=9C=D0=B0=D0=BA?= =?UTF-8?q?=D1=83=D1=89=D0=B5=D0=BD=D0=BA=D0=BE?= Date: Thu, 18 Dec 2025 10:41:09 +0300 Subject: [PATCH 09/10] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D0=B0?= =?UTF-8?q?=20=D1=81=D0=BA=D0=BB=D0=B5=D0=B8=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F?= =?UTF-8?q?=20=D1=81=20=D0=BF=D1=83=D1=81=D1=82=D1=8B=D0=BC=D0=B8=20=D1=81?= =?UTF-8?q?=D1=82=D1=80=D0=BE=D0=BA=D0=B0=D0=BC=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Stuff/concatUrlFromPathes.test.ts | 12 +++++++++++ src/Stuff/concatUrlFromPathes.ts | 31 +++++++++++++++------------ 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/Stuff/concatUrlFromPathes.test.ts b/src/Stuff/concatUrlFromPathes.test.ts index c6792e5..f1a4937 100644 --- a/src/Stuff/concatUrlFromPathes.test.ts +++ b/src/Stuff/concatUrlFromPathes.test.ts @@ -31,4 +31,16 @@ test("Множественные / тоже убираются", () => { test("Много путей на всякий случай", () => { expect(concatUrlFromPathes("foo", "bar/" , "one", "more")).toBe("foo/bar/one/more") +}) + +test("Пустой путь в начале не добавляет /", () => { + expect(concatUrlFromPathes("", "tour.json")).toBe("tour.json") +}) + +test("Пустота в конце не добавляет /", () => { + expect(concatUrlFromPathes("some/base", "")).toBe("some/base") +}) + +test("Пустота в середине не добавляет /", () => { + expect(concatUrlFromPathes("some/base", "", "", "some", "end")).toBe("some/base/some/end") }) \ No newline at end of file diff --git a/src/Stuff/concatUrlFromPathes.ts b/src/Stuff/concatUrlFromPathes.ts index c761293..3853677 100644 --- a/src/Stuff/concatUrlFromPathes.ts +++ b/src/Stuff/concatUrlFromPathes.ts @@ -1,15 +1,18 @@ export function concatUrlFromPathes(...pathes: string[]) { - return pathes.map((path, i) => { - if (i > 0) { - while (path.startsWith('/')) { - path = path.substring(1, path.length); - } - } - if (i < pathes.length - 1) { - while (path.endsWith('/')) { - path = path.substring(0, path.length - 1); - } - } - return path; - }).join("/"); -} \ No newline at end of file + return pathes + .map((path, i) => { + if (i > 0) { + while (path.startsWith("/")) { + path = path.substring(1, path.length); + } + } + if (i < pathes.length - 1) { + while (path.endsWith("/")) { + path = path.substring(0, path.length - 1); + } + } + return path; + }) + .filter((s) => !!s) + .join("/"); +} From d1e610422f406896008a570a9415fc5e908b99b1 Mon Sep 17 00:00:00 2001 From: Maksim Makushchenko Date: Fri, 19 Dec 2025 08:35:52 +0300 Subject: [PATCH 10/10] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20changelog=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=BF=D1=83=D0=B1=D0=BB=D0=B8=D0=BA=D0=B0=D1=86=D0=B8=D0=B8=20?= =?UTF-8?q?0.10.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 172c657..2b30554 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +* `0.10.0` + * ✨ Каждая фотография интерактивного элемента может содержать собственную озвучку + * 🐛 Исправлено одновременное воспроизведение фонового аудио с аудио из интерактивных элемнетов + * ➕ Добавлен biome для лингинга кода и проверки ошибок * `0.9.28` * ✨ Поддержка зума через колесико мыши * 🔨 Поддержка вращения камеры при работе с редактором