From ebfd010b048039d574f800aad51cf5f5b775b0ef Mon Sep 17 00:00:00 2001 From: HeDo Date: Fri, 7 Aug 2026 01:45:39 +0200 Subject: [PATCH 01/16] Added toast notifications to gsplats viewer --- webapp/js/features/viewers/splat/Splat.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/webapp/js/features/viewers/splat/Splat.vue b/webapp/js/features/viewers/splat/Splat.vue index 2eadafb..648a61e 100644 --- a/webapp/js/features/viewers/splat/Splat.vue +++ b/webapp/js/features/viewers/splat/Splat.vue @@ -3,6 +3,7 @@ +

Loading Gaussian Splat...

From 7fc0750240946a33b8be87a3d24c5a5923962ef3 Mon Sep 17 00:00:00 2001 From: HeDo Date: Fri, 7 Aug 2026 01:55:58 +0200 Subject: [PATCH 02/16] Added hangfire and scalar links to the admin menu --- webapp/js/layout/Header.vue | 2 ++ 1 file changed, 2 insertions(+) diff --git a/webapp/js/layout/Header.vue b/webapp/js/layout/Header.vue index 2007367..d21dd8f 100644 --- a/webapp/js/layout/Header.vue +++ b/webapp/js/layout/Header.vue @@ -304,6 +304,8 @@ export default { if (this.isAdmin) { items.push({ label: 'Tasks', icon: 'fa-solid fa-list-check fixed-icon', command: () => this.adminTasks() }); items.push({ label: 'Configuration', icon: 'fa-solid fa-gear fixed-icon', command: () => this.adminConfig() }); + items.push({ label: 'Hangfire', icon: 'fa-solid fa-fire fixed-icon', url: '/hangfire', target: '_blank' }); + items.push({ label: 'API Docs', icon: 'fa-solid fa-code fixed-icon', url: '/scalar/v1', target: '_blank' }); } items.push({ separator: true }); From 7e573c12ae9605495755be9bfed1bb857de3d846 Mon Sep 17 00:00:00 2001 From: HeDo Date: Fri, 7 Aug 2026 02:14:10 +0200 Subject: [PATCH 03/16] Added modified time in human format --- webapp/js/composables/useTaskFormatting.js | 11 ++------- .../features/dataset/explorer/TableView.vue | 20 +++++++++++++--- webapp/js/libs/utils.js | 23 +++++++++++++++++++ 3 files changed, 42 insertions(+), 12 deletions(-) diff --git a/webapp/js/composables/useTaskFormatting.js b/webapp/js/composables/useTaskFormatting.js index af6d8b1..f239776 100644 --- a/webapp/js/composables/useTaskFormatting.js +++ b/webapp/js/composables/useTaskFormatting.js @@ -14,6 +14,7 @@ * Pure formatting (severity/icon/date/duration) stays client-side. */ import reg from '@/libs/api/sharedRegistry'; +import { formatTimeAgo } from '@/libs/utils'; export default { data() { @@ -109,15 +110,7 @@ export default { getRelativeTime(dateString) { if (!dateString) return ''; - const diffMs = Date.now() - new Date(dateString).getTime(); - const minutes = Math.floor(diffMs / 60000); - const hours = Math.floor(diffMs / 3600000); - const days = Math.floor(diffMs / 86400000); - if (minutes < 1) return 'Just now'; - if (minutes < 60) return `${minutes} minutes ago`; - if (hours < 24) return `${hours} hours ago`; - if (days < 30) return `${days} days ago`; - return 'Over a month ago'; + return formatTimeAgo(new Date(dateString).getTime()); }, // Elapsed time for a task still in flight. diff --git a/webapp/js/features/dataset/explorer/TableView.vue b/webapp/js/features/dataset/explorer/TableView.vue index a2f22c4..d08bece 100644 --- a/webapp/js/features/dataset/explorer/TableView.vue +++ b/webapp/js/features/dataset/explorer/TableView.vue @@ -76,7 +76,10 @@ @@ -47,6 +101,28 @@ import ddb from 'ddb'; import Message from '@/components/Message'; import TabViewLoader from '@/features/viewers/TabViewLoader'; +import Window from '@/components/Window.vue'; +import Button from 'primevue/button'; +import { createEarthControls } from './earthControls'; + +const SETTINGS_KEY = 'unified-viewer-settings'; + +const DEFAULTS = { + ambientIntensity: 1.5, + directionalIntensity: 1.8, + hemisphereIntensity: 0.6, + flightSpeed: 1 +}; + +function loadSettings() { + try { + const raw = localStorage.getItem(SETTINGS_KEY); + if (raw) return { ...DEFAULTS, ...JSON.parse(raw) }; + } catch (e) { + // Ignore parse errors and fall back to the defaults. + } + return { ...DEFAULTS }; +} /** * UnifiedViewer - a single georeferenced Giro3D scene that can render any of the build @@ -68,10 +144,11 @@ import TabViewLoader from '@/features/viewers/TabViewLoader'; */ export default { components: { - Message, TabViewLoader + Message, TabViewLoader, Window, Button }, props: ['uri'], data: function () { + const saved = loadSettings(); return { error: "", loading: false, @@ -81,6 +158,16 @@ export default { ready: false, activeTool: null, measureCount: 0, + showSettings: false, + // Lighting and flight speed, restored from localStorage when present. + ambientIntensity: saved.ambientIntensity, + directionalIntensity: saved.directionalIntensity, + hemisphereIntensity: saved.hemisphereIntensity, + flightSpeed: saved.flightSpeed, + // Potree-style navigation: 'orbit' (default), 'flight' or 'earth'. Only offered for + // 3D content in a flat scene - globe mode has its own ground-following controls. + navMode: 'orbit', + navModesAvailable: false, // Reactive metadata for the layer panel; the heavy Giro3D objects are kept // out of the reactive tree (see created()). layers: [] @@ -99,6 +186,17 @@ export default { this.shapes = []; this.layerObjects = {}; this._layerId = 0; + // Scene lights, kept so the settings panel can retune them live. + this._lights = { ambient: null, hemisphere: null, sun: null, fill: null }; + // The three navigation controls. Orbit is built with the scene; flight and earth are + // created on first use so non-3D entries never pay for them. + this._orbitControls = null; + this._flightControls = null; + this._earthControls = null; + // Distance from camera to pivot at framing time, used to rebuild an orbit pivot after + // free-flying, and the scene size that sets the flight speed baseline. + this._focusDistance = 0; + this._sceneSize = 0; // Globe-mode (georeferenced 3D Tiles) state: the Giro3D Globe entity and the // requestAnimationFrame handle driving GlobeControls.update() (see setupGlobe). this._globe = null; @@ -179,7 +277,8 @@ export default { const [ THREE, controls, Instance, GMap, Extent, CoordinateSystem, ColorLayer, TiledImageSource, VectorTileSource, PointCloud, COPCSource, Tiles3D, DrawTool, - XYZ, OSM, olStyle, olProj, ColorMap, ColorMapMode, lasConfig, Globe, GlobeControls + XYZ, OSM, olStyle, olProj, ColorMap, ColorMapMode, lasConfig, Globe, GlobeControls, + FirstPersonControls ] = await Promise.all([ import(/* webpackChunkName: "giro3d" */ 'three'), import(/* webpackChunkName: "giro3d" */ 'three/examples/jsm/controls/MapControls.js'), @@ -202,7 +301,8 @@ export default { import(/* webpackChunkName: "giro3d" */ '@giro3d/giro3d/core/ColorMapMode.js'), import(/* webpackChunkName: "giro3d" */ '@giro3d/giro3d/sources/las/config.js'), import(/* webpackChunkName: "giro3d" */ '@giro3d/giro3d/entities/Globe.js'), - import(/* webpackChunkName: "giro3d" */ '@giro3d/giro3d/controls/GlobeControls.js') + import(/* webpackChunkName: "giro3d" */ '@giro3d/giro3d/controls/GlobeControls.js'), + import(/* webpackChunkName: "giro3d" */ '@giro3d/giro3d/controls/FirstPersonControls.js') ]); // Offline-first: serve the laz-perf WebAssembly decoder from our own origin (webpack @@ -231,7 +331,8 @@ export default { ColorMap: ColorMap.default, ColorMapMode: ColorMapMode.default, Globe: Globe.default, - GlobeControls: GlobeControls.default + GlobeControls: GlobeControls.default, + FirstPersonControls: FirstPersonControls.default }; }, @@ -241,7 +342,7 @@ export default { // CRS. Every entry type (raster, vector, point cloud, model) uses a flat scene with // MapControls and an OSM basemap underneath - there is no globe mode. setupInstance: function (crs) { - const { THREE, Instance, MapControls } = this.libs; + const { Instance, MapControls } = this.libs; const instance = new Instance({ target: this.$refs.view, @@ -254,20 +355,40 @@ export default { camera.up.set(0, 0, 1); // Giro3D scenes are Z-up // Lighting for 3D entities (models / point clouds). Map layers are unlit. - const ambient = new THREE.AmbientLight(0xffffff, 1.2); - instance.scene.add(ambient); - const sun = new THREE.DirectionalLight(0xffffff, 1.4); - sun.position.set(1, 1, 2); - instance.scene.add(sun); - const fill = new THREE.DirectionalLight(0xffffff, 0.5); - fill.position.set(-1, -1, 1); - instance.scene.add(fill); + this.addLights(instance, { fill: true }); const controls = new MapControls(camera, instance.domElement); controls.enableDamping = true; controls.dampingFactor = 0.2; instance.view.setControls(controls); + this._orbitControls = controls; this.controls = controls; + this.navMode = 'orbit'; + }, + + // Adds the scene lighting. A hemisphere light provides the sky/ground fill that keeps + // unlit-facing surfaces readable - without it 3D Tiles meshes render noticeably darker + // than the same model in the Nexus viewer. Every light is kept so the settings panel can + // retune it live. + addLights: function (instance, { fill }) { + const { THREE } = this.libs; + const lights = this._lights; + + lights.ambient = new THREE.AmbientLight(0xffffff, this.ambientIntensity); + instance.scene.add(lights.ambient); + + lights.hemisphere = new THREE.HemisphereLight(0xffffff, 0x444444, this.hemisphereIntensity); + instance.scene.add(lights.hemisphere); + + lights.sun = new THREE.DirectionalLight(0xffffff, this.directionalIntensity); + lights.sun.position.set(1, 1, 2); + instance.scene.add(lights.sun); + + if (fill) { + lights.fill = new THREE.DirectionalLight(0xffffff, this.directionalIntensity * 0.35); + lights.fill.position.set(-1, -1, 1); + instance.scene.add(lights.fill); + } }, // Lazily creates the DrawTool the first time a measurement is started. @@ -423,6 +544,7 @@ export default { // Flat local-space scene (no globe): the DroneDB 3D Tiles output is rendered in its own // frame with MapControls, consistent with every other type. this.setupInstance(this.libs.CoordinateSystem.epsg3857); + this.navModesAvailable = true; const tileset = new this.libs.Tiles3D({ url: tilesetUrl, errorTarget: 8, ktx2DecoderPath: '/wasm/basis/' }); @@ -480,6 +602,7 @@ export default { } else { // Local/engineering tileset: flat scene, like the model path. this.setupInstance(this.libs.CoordinateSystem.epsg3857); + this.navModesAvailable = true; } const tileset = new this.libs.Tiles3D({ url: tilesetUrl, errorTarget: 8, ktx2DecoderPath: '/wasm/basis/' }); @@ -554,7 +677,7 @@ export default { // WGS84 ellipsoid, plus GlobeControls. Mirrors the giro3d simple-globe example. Unlike // MapControls, GlobeControls must be driven by a manual requestAnimationFrame loop. setupGlobe: function () { - const { THREE, Instance, Globe, GlobeControls, ColorLayer, TiledImageSource, OSM, CoordinateSystem } = this.libs; + const { Instance, Globe, GlobeControls, ColorLayer, TiledImageSource, OSM, CoordinateSystem } = this.libs; const instance = new Instance({ target: this.$refs.view, @@ -567,10 +690,7 @@ export default { const camera = instance.view.camera; camera.up.set(0, 0, 1); // Z is the Earth rotation axis in ECEF. - instance.scene.add(new THREE.AmbientLight(0xffffff, 1.4)); - const sun = new THREE.DirectionalLight(0xffffff, 1.6); - sun.position.set(1, 1, 1); - instance.scene.add(sun); + this.addLights(instance, { fill: false }); // OSM basemap on the ellipsoid (best-effort: a basemap failure must not hide the data). try { @@ -668,6 +788,7 @@ export default { // but no longer responds to drag/zoom. const crsRegistered = this.registerCrs(crs); this.setupInstance(crs); + this.navModesAvailable = true; if (this.instance.renderingOptions) { this.instance.renderingOptions.enableEDL = true; } @@ -775,6 +896,8 @@ export default { this.instance.view.minNearPlane = Math.max(dist / 1000, 0.1); this.controls.target.set(cx, cy, 0); this.controls.update(); + this._focusDistance = camera.position.distanceTo(this.controls.target); + this._sceneSize = span; this.instance.notifyChange(); }, @@ -797,9 +920,150 @@ export default { camera.position.set(center.x + maxDim * 1.2, center.y - maxDim * 1.2, center.z + maxDim * 0.9); if (this.controls.target && this.controls.target.copy) this.controls.target.copy(center); if (this.controls.update) this.controls.update(); + this._focusDistance = camera.position.distanceTo(center); + this._sceneSize = maxDim; + this.applyFlightSpeed(); + this.instance.notifyChange(); + }, + + // --- Navigation modes ------------------------------------------------------------ + + // Switches between the Potree-style navigation modes. Orbit and earth are MapControls + // variants driven by Giro3D's view; flight is Giro3D's FirstPersonControls, which drives + // itself from the instance update loop and so must not be registered as a view control. + switchNavMode: function (mode) { + if (!this.instance || mode === this.navMode) return; + + const previous = this.controls; + const controls = this.ensureControls(mode); + if (!controls) return; + + if (previous && previous !== controls) previous.enabled = false; + controls.enabled = true; + + if (mode === 'flight') { + // FirstPersonControls listens to 'after-camera-update' itself. + this.instance.view.setControls(null); + controls.reset(); + this.instance.domElement.focus(); + } else { + this.adoptPivot(controls, previous); + this.instance.view.setControls(controls); + controls.update(); + } + + this.controls = controls; + this.navMode = mode; this.instance.notifyChange(); }, + // Returns the control for a mode, building flight and earth controls on first use so + // entries that never leave orbit mode do not pay for them. + ensureControls: function (mode) { + if (mode === 'orbit') return this._orbitControls; + + if (mode === 'earth') { + if (!this._earthControls) { + this._earthControls = createEarthControls( + this.libs, this.instance.view.camera, this.instance.domElement, + () => this.instance.notifyChange()); + this._earthControls.enabled = false; + } + return this._earthControls; + } + + if (mode === 'flight') { + if (!this._flightControls) { + // The canvas must be focusable for the arrow-key handlers to receive events. + if (!this.instance.domElement.hasAttribute('tabindex')) { + this.instance.domElement.tabIndex = 0; + } + this._flightControls = new this.libs.FirstPersonControls(this.instance, { + focusOnClick: true, + focusOnMouseOver: true, + moveSpeed: this.moveSpeedForScene() + }); + } + return this._flightControls; + } + + return null; + }, + + // Gives an orbit-style control its pivot when it becomes active: carried over from the + // previous control when there was one, or - coming back from free flight, which has no + // pivot - placed in front of the camera at the distance the scene was framed with. + adoptPivot: function (controls, previous) { + if (!controls.target) return; + + if (previous && previous.target) { + controls.target.copy(previous.target); + return; + } + + if (!this._focusDistance) return; + const { THREE } = this.libs; + const camera = this.instance.view.camera; + const forward = camera.getWorldDirection(new THREE.Vector3()); + controls.target.copy(camera.position).addScaledVector(forward, this._focusDistance); + }, + + // Flight speed in m/s, scaled to the scene so crossing the data takes a few seconds + // whether it is a 10 m model or a 10 km point cloud. + moveSpeedForScene: function () { + return Math.max((this._sceneSize || 100) / 5, 0.5) * this.flightSpeed; + }, + + applyFlightSpeed: function () { + if (this._flightControls) { + this._flightControls.options.moveSpeed = this.moveSpeedForScene(); + } + }, + + updateFlightSpeed: function () { + this.applyFlightSpeed(); + this.persistSettings(); + }, + + // --- Display settings ------------------------------------------------------------ + + toggleSettings: function () { + this.showSettings = !this.showSettings; + }, + + updateLighting: function () { + const { ambient, hemisphere, sun, fill } = this._lights; + if (ambient) ambient.intensity = this.ambientIntensity; + if (hemisphere) hemisphere.intensity = this.hemisphereIntensity; + if (sun) sun.intensity = this.directionalIntensity; + if (fill) fill.intensity = this.directionalIntensity * 0.35; + this.persistSettings(); + if (this.instance) this.instance.notifyChange(); + }, + + resetSettings: function () { + this.ambientIntensity = DEFAULTS.ambientIntensity; + this.directionalIntensity = DEFAULTS.directionalIntensity; + this.hemisphereIntensity = DEFAULTS.hemisphereIntensity; + this.flightSpeed = DEFAULTS.flightSpeed; + this.updateLighting(); + this.applyFlightSpeed(); + try { localStorage.removeItem(SETTINGS_KEY); } catch (e) { /* ignore */ } + }, + + persistSettings: function () { + try { + localStorage.setItem(SETTINGS_KEY, JSON.stringify({ + ambientIntensity: this.ambientIntensity, + directionalIntensity: this.directionalIntensity, + hemisphereIntensity: this.hemisphereIntensity, + flightSpeed: this.flightSpeed + })); + } catch (e) { + // Ignore storage errors (private mode, quota). + } + }, + // A simple OpenLayers style for MVT features (points, lines, polygons). vectorStyle: function () { const { Style, Fill, Stroke, Circle } = this.libs.olStyle; @@ -923,10 +1187,23 @@ export default { try { this.drawTool.dispose(); } catch (e) { /* ignore */ } this.drawTool = null; } - if (this.controls) { - this.controls.dispose(); - this.controls = null; + // FirstPersonControls has no dispose(); disabling it stops it responding to input and + // its listeners die with the Giro3D DOM element below. GlobeControls, MapControls and + // the earth controls all dispose properly, and this.controls is always one of them, so + // it is covered by the list rather than disposed separately. + if (this._flightControls) { + this._flightControls.enabled = false; + this._flightControls = null; } + [this._orbitControls, this._earthControls, this.controls].forEach(c => { + if (c && typeof c.dispose === 'function') { + try { c.dispose(); } catch (e) { /* ignore */ } + } + }); + this.controls = null; + this._orbitControls = null; + this._earthControls = null; + this._lights = { ambient: null, hemisphere: null, sun: null, fill: null }; // Giro3D disposes its renderer, scene, entities, layers and canvas. if (this.instance) { try { this.instance.dispose(); } catch (e) { /* ignore */ } @@ -1078,4 +1355,85 @@ export default { text-overflow: ellipsis; white-space: nowrap; } + +#unified-viewer .nav-toolbar { + position: absolute; + bottom: var(--ddb-spacing-md); + left: var(--ddb-spacing-md); + display: flex; + gap: 0.25rem; + z-index: 100; + background: var(--ddb-overlay-bg); + border: var(--ddb-border-width) solid rgba(255, 255, 255, 0.25); + border-radius: var(--ddb-border-radius, 4px); + padding: 0.25rem; +} + +#unified-viewer .nav-toolbar button { + width: 2.25rem; + height: 2.25rem; + border-radius: 4px; + background: transparent; + border: none; + color: var(--ddb-text-on-dark); + font-size: var(--ddb-font-size-base); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: background 0.2s; +} + +#unified-viewer .nav-toolbar button:hover { + background: rgba(255, 255, 255, 0.15); +} + +#unified-viewer .nav-toolbar button.active { + background: var(--ddb-primary, #2978b4); +} + +#unified-viewer .btn-settings { + position: absolute; + bottom: var(--ddb-spacing-md); + right: var(--ddb-spacing-md); + width: 2.25rem; + height: 2.25rem; + border-radius: 50%; + background: var(--ddb-overlay-bg); + border: var(--ddb-border-width) solid rgba(255, 255, 255, 0.25); + color: var(--ddb-text-on-dark); + font-size: var(--ddb-font-size-base); + cursor: pointer; + z-index: 100; + display: flex; + align-items: center; + justify-content: center; + padding: 0; + transition: background 0.2s, border-color 0.2s; +} + +#unified-viewer .btn-settings:hover { + background: rgba(0, 0, 0, 0.85); + border-color: rgba(255, 255, 255, 0.5); +} + +#unified-viewer .settings-content { + padding: 0.25rem 0; +} + +#unified-viewer .settings-content .form-group { + margin-bottom: 0.75rem; +} + +#unified-viewer .settings-content .form-group label { + display: block; + margin-bottom: 0.25rem; + font-size: 0.75rem; + color: var(--ddb-viewer-label-color); +} + +#unified-viewer .settings-content .form-group input[type="range"] { + width: 100%; + cursor: pointer; +} diff --git a/webapp/js/features/viewers/unified/earthControls.js b/webapp/js/features/viewers/unified/earthControls.js new file mode 100644 index 0000000..d503f60 --- /dev/null +++ b/webapp/js/features/viewers/unified/earthControls.js @@ -0,0 +1,66 @@ +/** + * createEarthControls - Potree-style "Earth" navigation, built on three.js MapControls. + * + * Differs from plain orbit navigation in two ways: + * - the camera is constrained to stay above the ground plane (no rolling under the terrain); + * - double-clicking re-anchors the orbit pivot on the clicked ground point, so the user + * zooms and rotates around whatever they are looking at. + * + * MapControls and three are passed in because the unified viewer lazy-loads them, so this + * module cannot import them at load time. + * + * @param libs - the viewer's loaded library bundle ({ THREE, MapControls }). + * @param camera - the camera to control. + * @param domElement - the element to listen to. + * @param onChange - called after the pivot is re-anchored, to request a redraw. + * @returns a MapControls instance with the ground constraint applied. + */ +export function createEarthControls(libs, camera, domElement, onChange) { + const { THREE, MapControls } = libs; + + const controls = new MapControls(camera, domElement); + controls.enableDamping = true; + controls.dampingFactor = 0.2; + // Keep the camera above the ground plane. Giro3D scenes are Z-up and OrbitControls measures + // the polar angle from camera.up, so 90 degrees is exactly horizon level. + controls.maxPolarAngle = Math.PI / 2 - 0.01; + controls.screenSpacePanning = false; + + const raycaster = new THREE.Raycaster(); + const plane = new THREE.Plane(); + const normal = new THREE.Vector3(0, 0, 1); + const ndc = new THREE.Vector2(); + const hit = new THREE.Vector3(); + + const onDoubleClick = event => { + if (!controls.enabled) return; + + const rect = domElement.getBoundingClientRect(); + if (!rect.width || !rect.height) return; + ndc.set( + ((event.clientX - rect.left) / rect.width) * 2 - 1, + -((event.clientY - rect.top) / rect.height) * 2 + 1 + ); + + raycaster.setFromCamera(ndc, camera); + // Ground plane through the current pivot: z = target.z. + plane.set(normal, -controls.target.z); + if (!raycaster.ray.intersectPlane(plane, hit)) return; + + controls.target.copy(hit); + controls.update(); + if (onChange) onChange(); + }; + + domElement.addEventListener('dblclick', onDoubleClick); + + const disposeControls = controls.dispose.bind(controls); + controls.dispose = () => { + domElement.removeEventListener('dblclick', onDoubleClick); + disposeControls(); + }; + + return controls; +} + +export default createEarthControls; From f4b0a6134bfb52ca508c6963c1233795fb8f5e39 Mon Sep 17 00:00:00 2001 From: HeDo Date: Fri, 7 Aug 2026 02:28:53 +0200 Subject: [PATCH 05/16] Updated browser data --- package-lock.json | 88 +++++++++++++++-------------------------------- 1 file changed, 28 insertions(+), 60 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7c164e5..7bbba00 100644 --- a/package-lock.json +++ b/package-lock.json @@ -121,7 +121,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -1791,7 +1790,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -1815,7 +1813,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -3622,7 +3619,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.1.tgz", "integrity": "sha512-hj9YIJimBCipHVfHKRMnvmHg+wfhKc0o4mTtXh9pKBjC8TLJzz0nzGmLi5UJsYAUgSvXFHgb0V2oY10DUFtImw==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.18.0" } @@ -4152,7 +4148,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4197,7 +4192,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -4367,9 +4361,9 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", - "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", + "version": "2.11.12", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.12.tgz", + "integrity": "sha512-r7WnVImvVCeFpf2DOXfy41aPWzeNg3H/A2X4dKmy1QL0MSyyk/e7z8ihJ3N6Nn2PsdhkVlqnEfnUE4a05P2aTA==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.cjs" @@ -4468,7 +4462,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -4530,9 +4523,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001774", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001774.tgz", - "integrity": "sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==", + "version": "1.0.30001807", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001807.tgz", + "integrity": "sha512-daRXJ9EB/rdRgu7kV+TTl1YUKtlsMWblPl2sLnpg9DZae16QCegol6A1SmCE31Lm9mXC1sRWGt/krouH+/dl7Q==", "funding": [ { "type": "opencollective", @@ -5222,7 +5215,6 @@ "integrity": "sha512-1y+7C+vi12bUK1IpZeaV3gsH9fHLBmPvYmPx42pvT/E9yG0IC8g3PUZZgp0+JLJl7ZDK0flc2gc+Aw9dpCvIsQ==", "dev": true, "license": "MIT", - "peer": true, "workspaces": [ "packages/*" ], @@ -5969,7 +5961,6 @@ "integrity": "sha512-UVIHeVhxmxedbWPCfgS55Jg2rDfwf2BCKeylcPSqazLz5w3Kri7Q4xdBJubsr/+VUzFLh0VjIvh13RaDA2/Xug==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "webidl-conversions": "^7.0.0", "whatwg-mimetype": "^3.0.0" @@ -6877,7 +6868,6 @@ "resolved": "https://registry.npmjs.org/ol/-/ol-10.9.0.tgz", "integrity": "sha512-svbbgVQUmEHaKpLQ8kRySojs59Brvgl2zYIrqG9eQNXGfsbi55rQasZIDpwpQzDL6OlzrUb0H4hQaiX9wDoGmA==", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@types/rbush": "4.0.0", "earcut": "^3.0.0", @@ -7212,7 +7202,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -7354,7 +7343,6 @@ "resolved": "https://registry.npmjs.org/proj4/-/proj4-2.20.9.tgz", "integrity": "sha512-GLBGqXaTcdWnppre3o1sMmy4DcMGSGq/ng+9k2MTNddarRK6SveINqlqYzi3xEXuy06ljY1TTrC6H9C4f360IQ==", "license": "MIT", - "peer": true, "dependencies": { "mgrs": "1.0.0", "wkt-parser": "^1.5.5" @@ -7746,7 +7734,6 @@ "resolved": "https://registry.npmjs.org/sass/-/sass-1.97.3.tgz", "integrity": "sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==", "license": "MIT", - "peer": true, "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", @@ -8122,8 +8109,7 @@ "version": "0.180.0", "resolved": "https://registry.npmjs.org/three/-/three-0.180.0.tgz", "integrity": "sha512-o+qycAMZrh+TsE01GqWUxUIKR1AL0S8pq7zDkYOQw8GqfX8b8VoCKYUoHbhiX5j+7hr8XsuHDVU6+gkQJQKg9w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/tiny-lr": { "version": "1.1.1", @@ -8203,7 +8189,6 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -8450,7 +8435,6 @@ "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -8589,7 +8573,6 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -8688,7 +8671,6 @@ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.29.tgz", "integrity": "sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA==", "license": "MIT", - "peer": true, "dependencies": { "@vue/compiler-dom": "3.5.29", "@vue/compiler-sfc": "3.5.29", @@ -8711,6 +8693,7 @@ "integrity": "sha512-Gk6gRDj0n/fkRa3C3l0bBheoBckUq/Rs0F/TvMWIS6nzzx67amAViMe9CkNgsP2tXyQONvGiHQESHwFtZ3aYDA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "debug": "^4.4.0", "eslint-scope": "^8.2.0 || ^9.0.0", @@ -8735,6 +8718,7 @@ "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@types/esrecurse": "^4.3.1", "@types/estree": "^1.0.8", @@ -8754,6 +8738,7 @@ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "engines": { "node": ">=4.0" } @@ -8764,6 +8749,7 @@ "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", + "peer": true, "bin": { "semver": "bin/semver.js" }, @@ -8899,7 +8885,6 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.3.tgz", "integrity": "sha512-LLBBA4oLmT7sZdHiYE/PeVuifOxYyE2uL/V+9VQP7YSYdJU7bSf7H8bZRRxW8kEPMkmVjnrXmoR3oejIdX0xbg==", "license": "MIT", - "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -8948,7 +8933,6 @@ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", "license": "MIT", - "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^2.1.1", @@ -9275,7 +9259,6 @@ "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", - "peer": true, "requires": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -10321,15 +10304,13 @@ "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", "dev": true, - "peer": true, "requires": {} }, "@csstools/css-tokenizer": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", - "dev": true, - "peer": true + "dev": true }, "@discoveryjs/json-ext": { "version": "0.5.7", @@ -11317,7 +11298,6 @@ "version": "25.3.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.1.tgz", "integrity": "sha512-hj9YIJimBCipHVfHKRMnvmHg+wfhKc0o4mTtXh9pKBjC8TLJzz0nzGmLi5UJsYAUgSvXFHgb0V2oY10DUFtImw==", - "peer": true, "requires": { "undici-types": "~7.18.0" } @@ -11707,8 +11687,7 @@ "acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "peer": true + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==" }, "acorn-import-phases": { "version": "1.0.4", @@ -11733,7 +11712,6 @@ "version": "8.18.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", - "peer": true, "requires": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -11844,9 +11822,9 @@ "dev": true }, "baseline-browser-mapping": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", - "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==" + "version": "2.11.12", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.12.tgz", + "integrity": "sha512-r7WnVImvVCeFpf2DOXfy41aPWzeNg3H/A2X4dKmy1QL0MSyyk/e7z8ihJ3N6Nn2PsdhkVlqnEfnUE4a05P2aTA==" }, "big.js": { "version": "5.2.2", @@ -11898,7 +11876,6 @@ "version": "4.28.1", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", - "peer": true, "requires": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -11945,9 +11922,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001774", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001774.tgz", - "integrity": "sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==" + "version": "1.0.30001807", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001807.tgz", + "integrity": "sha512-daRXJ9EB/rdRgu7kV+TTl1YUKtlsMWblPl2sLnpg9DZae16QCegol6A1SmCE31Lm9mXC1sRWGt/krouH+/dl7Q==" }, "chai": { "version": "5.3.3", @@ -12397,7 +12374,6 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.5.0.tgz", "integrity": "sha512-1y+7C+vi12bUK1IpZeaV3gsH9fHLBmPvYmPx42pvT/E9yG0IC8g3PUZZgp0+JLJl7ZDK0flc2gc+Aw9dpCvIsQ==", "dev": true, - "peer": true, "requires": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", @@ -12877,7 +12853,6 @@ "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-17.6.3.tgz", "integrity": "sha512-UVIHeVhxmxedbWPCfgS55Jg2rDfwf2BCKeylcPSqazLz5w3Kri7Q4xdBJubsr/+VUzFLh0VjIvh13RaDA2/Xug==", "dev": true, - "peer": true, "requires": { "webidl-conversions": "^7.0.0", "whatwg-mimetype": "^3.0.0" @@ -13499,7 +13474,6 @@ "version": "10.9.0", "resolved": "https://registry.npmjs.org/ol/-/ol-10.9.0.tgz", "integrity": "sha512-svbbgVQUmEHaKpLQ8kRySojs59Brvgl2zYIrqG9eQNXGfsbi55rQasZIDpwpQzDL6OlzrUb0H4hQaiX9wDoGmA==", - "peer": true, "requires": { "@types/rbush": "4.0.0", "earcut": "^3.0.0", @@ -13718,7 +13692,6 @@ "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "peer": true, "requires": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -13809,7 +13782,6 @@ "version": "2.20.9", "resolved": "https://registry.npmjs.org/proj4/-/proj4-2.20.9.tgz", "integrity": "sha512-GLBGqXaTcdWnppre3o1sMmy4DcMGSGq/ng+9k2MTNddarRK6SveINqlqYzi3xEXuy06ljY1TTrC6H9C4f360IQ==", - "peer": true, "requires": { "mgrs": "1.0.0", "wkt-parser": "^1.5.5" @@ -14071,7 +14043,6 @@ "version": "1.97.3", "resolved": "https://registry.npmjs.org/sass/-/sass-1.97.3.tgz", "integrity": "sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==", - "peer": true, "requires": { "@parcel/watcher": "^2.4.1", "chokidar": "^4.0.0", @@ -14292,8 +14263,7 @@ "three": { "version": "0.180.0", "resolved": "https://registry.npmjs.org/three/-/three-0.180.0.tgz", - "integrity": "sha512-o+qycAMZrh+TsE01GqWUxUIKR1AL0S8pq7zDkYOQw8GqfX8b8VoCKYUoHbhiX5j+7hr8XsuHDVU6+gkQJQKg9w==", - "peer": true + "integrity": "sha512-o+qycAMZrh+TsE01GqWUxUIKR1AL0S8pq7zDkYOQw8GqfX8b8VoCKYUoHbhiX5j+7hr8XsuHDVU6+gkQJQKg9w==" }, "tiny-lr": { "version": "1.1.1", @@ -14353,8 +14323,7 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "peer": true + "dev": true } } }, @@ -14510,7 +14479,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", "dev": true, - "peer": true, "requires": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -14539,8 +14507,7 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "peer": true + "dev": true } } }, @@ -14608,7 +14575,6 @@ "version": "3.5.29", "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.29.tgz", "integrity": "sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA==", - "peer": true, "requires": { "@vue/compiler-dom": "3.5.29", "@vue/compiler-sfc": "3.5.29", @@ -14622,6 +14588,7 @@ "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.4.1.tgz", "integrity": "sha512-Gk6gRDj0n/fkRa3C3l0bBheoBckUq/Rs0F/TvMWIS6nzzx67amAViMe9CkNgsP2tXyQONvGiHQESHwFtZ3aYDA==", "dev": true, + "peer": true, "requires": { "debug": "^4.4.0", "eslint-scope": "^8.2.0 || ^9.0.0", @@ -14636,6 +14603,7 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", "dev": true, + "peer": true, "requires": { "@types/esrecurse": "^4.3.1", "@types/estree": "^1.0.8", @@ -14647,13 +14615,15 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true + "dev": true, + "peer": true }, "semver": { "version": "7.8.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "dev": true + "dev": true, + "peer": true } } }, @@ -14749,7 +14719,6 @@ "version": "5.105.3", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.3.tgz", "integrity": "sha512-LLBBA4oLmT7sZdHiYE/PeVuifOxYyE2uL/V+9VQP7YSYdJU7bSf7H8bZRRxW8kEPMkmVjnrXmoR3oejIdX0xbg==", - "peer": true, "requires": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -14782,7 +14751,6 @@ "version": "5.1.4", "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", - "peer": true, "requires": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^2.1.1", From 86849cc728db08bd8efebb0e51b6de57ea7646a5 Mon Sep 17 00:00:00 2001 From: HeDo Date: Fri, 7 Aug 2026 02:46:15 +0200 Subject: [PATCH 06/16] Added missing import --- webapp/js/features/viewers/splat/Splat.vue | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/webapp/js/features/viewers/splat/Splat.vue b/webapp/js/features/viewers/splat/Splat.vue index 648a61e..3126e7e 100644 --- a/webapp/js/features/viewers/splat/Splat.vue +++ b/webapp/js/features/viewers/splat/Splat.vue @@ -167,6 +167,7 @@ import Message from '@/components/Message'; import TabViewLoader from '@/features/viewers/TabViewLoader'; import Window from '@/components/Window.vue'; import Button from 'primevue/button'; +import Toast from 'primevue/toast'; import CameraManager from './CameraManager.vue'; // Immersive navigation keys, in the 2x3 layout shown in the on-screen overlay: @@ -190,7 +191,7 @@ const NAV_KEYS = [ */ export default { components: { - Message, TabViewLoader, Window, Button, CameraManager + Message, TabViewLoader, Window, Button, CameraManager, Toast }, props: ['uri'], data: function () { From 9e221df90074ad295df807d854bacc4978aff31c Mon Sep 17 00:00:00 2001 From: HeDo Date: Fri, 7 Aug 2026 02:52:52 +0200 Subject: [PATCH 07/16] Improved lighting defaults --- webapp/js/features/viewers/unified/UnifiedViewer.vue | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/webapp/js/features/viewers/unified/UnifiedViewer.vue b/webapp/js/features/viewers/unified/UnifiedViewer.vue index ba15be5..2bbfc59 100644 --- a/webapp/js/features/viewers/unified/UnifiedViewer.vue +++ b/webapp/js/features/viewers/unified/UnifiedViewer.vue @@ -68,19 +68,19 @@
-
-
-
@@ -108,9 +108,9 @@ import { createEarthControls } from './earthControls'; const SETTINGS_KEY = 'unified-viewer-settings'; const DEFAULTS = { - ambientIntensity: 1.5, + ambientIntensity: 3, directionalIntensity: 1.8, - hemisphereIntensity: 0.6, + hemisphereIntensity: 3, flightSpeed: 1 }; From 5f368b5b8054b0c2778fe92f0b38c89013cc4883 Mon Sep 17 00:00:00 2001 From: HeDo Date: Fri, 7 Aug 2026 03:00:04 +0200 Subject: [PATCH 08/16] Improved separation --- webapp/js/layout/Header.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/webapp/js/layout/Header.vue b/webapp/js/layout/Header.vue index d21dd8f..648e0b2 100644 --- a/webapp/js/layout/Header.vue +++ b/webapp/js/layout/Header.vue @@ -303,6 +303,7 @@ export default { } if (this.isAdmin) { items.push({ label: 'Tasks', icon: 'fa-solid fa-list-check fixed-icon', command: () => this.adminTasks() }); + items.push({ separator: true }); items.push({ label: 'Configuration', icon: 'fa-solid fa-gear fixed-icon', command: () => this.adminConfig() }); items.push({ label: 'Hangfire', icon: 'fa-solid fa-fire fixed-icon', url: '/hangfire', target: '_blank' }); items.push({ label: 'API Docs', icon: 'fa-solid fa-code fixed-icon', url: '/scalar/v1', target: '_blank' }); From f575a23281f5e0ea48e884fd82cbb7397005c23f Mon Sep 17 00:00:00 2001 From: HeDo Date: Fri, 7 Aug 2026 03:00:25 +0200 Subject: [PATCH 09/16] Added human readable dates to the datasets list --- webapp/js/features/datasets/Datasets.vue | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/webapp/js/features/datasets/Datasets.vue b/webapp/js/features/datasets/Datasets.vue index c2968ed..fc470d6 100644 --- a/webapp/js/features/datasets/Datasets.vue +++ b/webapp/js/features/datasets/Datasets.vue @@ -100,7 +100,12 @@ - + @@ -172,7 +177,7 @@