mirror of
https://github.com/n30nex/CartoLite-Server.git
synced 2026-10-23 10:42:32 +00:00
Add explicit sound preview and streamline map chrome
This commit is contained in:
1 parent
6e3f6cf367
commit
401a404b8e
7 files changed
+77
-1
No files matched your search
@@ -11,3 +11,5 @@ Target Canada 0.15.0, Worldwide 0.6.0 and Canada Android 1.1.0.
|
||||
|
||||
No public API changes. Existing topology and packet/audio clocks remain authoritative.
|
||||
Builds, tests and browser artifacts run in GitHub Actions only.
|
||||
|
||||
The app supplies compact MapLibre canvas/gesture/attribution styling rather than shipping unused provider-control icons. The existing total byte limits remain unchanged.
|
||||
@@ -322,6 +322,18 @@ export class RouteSonifier {
|
||||
return notes.length;
|
||||
}
|
||||
|
||||
/** Explicit preview gesture; does not enable or save live sound. */
|
||||
async preview(): Promise<void> {
|
||||
if (!this.supported() || this.paused) return;
|
||||
const context = !this.context || this.context.state === 'closed' ? this.createContext() : this.context;
|
||||
this.unlockMobileOutput(context);
|
||||
if (context.state !== 'running') await context.resume();
|
||||
if (context.state !== 'running') return;
|
||||
this.setMasterLevel(MASTER_LEVEL * this.volume);
|
||||
const scene = SCENES[this.scene];
|
||||
this.schedule({ frequency: 440, startMS: 0, durationMS: Math.min(420,scene.maxDurationMS), brightness: Math.max(scene.minBrightness,1800*scene.brightnessScale), pan: 0, variation: 0, scene: this.scene, character: 'map' }, .8);
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.enabled = false;
|
||||
this.stopActive();
|
||||
|
||||
@@ -50,3 +50,12 @@
|
||||
.graph-focus select { min-height: 36px; background: var(--field-bg); color: var(--ink-strong); border: 0; }
|
||||
.graph-focus small:empty { display: none; }
|
||||
@media(max-width:900px) { .graph-focus { top: 106px; left: 8px; } }
|
||||
|
||||
:root[data-cinematic] .controls > :is(.layers-disclosure,.find-control,.sound-control,.popover-control), :root[data-cinematic] .display-disclosure { position:static; }
|
||||
:root[data-cinematic] #layers-panel { position:absolute; top:auto; right:0; bottom:calc(100% + 10px); width:min(370px,calc(100vw - 24px)); max-height:calc(100dvh - 150px); }
|
||||
:root[data-cinematic] .maplibregl-ctrl-bottom-right { opacity:1; }
|
||||
.gpu-packet-canvas { z-index:2; }
|
||||
.sound-preview-actions { display:flex; gap:8px; }
|
||||
:root[data-cinematic] .director-enabled .node-inspector-sheet { visibility:visible; pointer-events:auto; }
|
||||
@media(max-width:900px),(pointer:coarse) { :root[data-cinematic] #layers-panel { width:100%; max-height:calc(100dvh - 185px); } }
|
||||
@media(max-height:520px) and (orientation:landscape) { :root[data-cinematic] #layers-panel { max-height:calc(100dvh - 95px); } }
|
||||
+3
-1
@@ -1,3 +1,4 @@
|
||||
import { mountSoundPreview } from './soundPreview';
|
||||
import { requestedNode } from './selection';
|
||||
import { mountCinematicDock, mountLayerCombinations } from './cinematicChrome';
|
||||
import { browserStorage } from './browserStorage';
|
||||
@@ -6,7 +7,7 @@ import { mountDisplayControls } from './displayControls';
|
||||
import { loadBasemapConfiguration } from './basemap';
|
||||
import { attachMapNotice } from './mapNotice';
|
||||
import { DEFAULT_DISPLAY, DISPLAY_EVENT, displayPreferences, initializeDisplay, updateDisplay, applyDisplayChrome } from './displayPreferences';
|
||||
import 'maplibre-gl/dist/maplibre-gl.css';
|
||||
import './maplibre-base.css';
|
||||
import './styles.css';
|
||||
import { fetchState, LiveFeed } from './api';
|
||||
import { RouteSonifier, type SoundScene, type SoundStatus } from './audio';
|
||||
@@ -252,6 +253,7 @@ async function start(): Promise<void> {
|
||||
animator = liveAnimator;
|
||||
const routeSonifier = new RouteSonifier(liveAnimator.projection, packetCanvas);
|
||||
sonifier = routeSonifier;
|
||||
mountSoundPreview(routeSonifier, soundPanel);
|
||||
soundVolume.value = String(Math.round(routeSonifier.getVolume() * 100));
|
||||
soundVolumeOutput.value = `${soundVolume.value}%`;
|
||||
syncSoundScene(soundScene, routeSonifier.getScene());
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/* CartoLite supplies its own controls and inspector. Only MapLibre's canvas,
|
||||
gestures and attribution need library chrome (MapLibre GL JS 6.4.1 DOM). */
|
||||
.maplibregl-map { position:relative; overflow:hidden; font:12px/20px system-ui,sans-serif; -webkit-tap-highlight-color:transparent; }
|
||||
.maplibregl-canvas { position:absolute; left:0; top:0; }
|
||||
.maplibregl-map:fullscreen { width:100%; height:100%; }
|
||||
.maplibregl-interactive { cursor:grab; user-select:none; }
|
||||
.maplibregl-interactive:active { cursor:grabbing; }
|
||||
.maplibregl-track-pointer { cursor:pointer; }
|
||||
.maplibregl-touch-zoom-rotate, .maplibregl-touch-zoom-rotate .maplibregl-canvas { touch-action:pan-x pan-y; }
|
||||
.maplibregl-touch-drag-pan, .maplibregl-touch-drag-pan .maplibregl-canvas { touch-action:pinch-zoom; }
|
||||
.maplibregl-touch-zoom-rotate.maplibregl-touch-drag-pan, .maplibregl-touch-zoom-rotate.maplibregl-touch-drag-pan .maplibregl-canvas { touch-action:none; }
|
||||
.maplibregl-canvas:focus-visible { outline:2px solid var(--focus-ring); outline-offset:-2px; }
|
||||
.maplibregl-ctrl-bottom-right,.maplibregl-ctrl-bottom-left,.maplibregl-ctrl-top-right,.maplibregl-ctrl-top-left { position:absolute; pointer-events:none; z-index:3; }
|
||||
.maplibregl-ctrl-bottom-right { right:0; bottom:0; }
|
||||
.maplibregl-ctrl-bottom-left { left:0; bottom:0; }
|
||||
.maplibregl-ctrl-top-right { right:0; top:0; }
|
||||
.maplibregl-ctrl-top-left { left:0; top:0; }
|
||||
.maplibregl-ctrl { clear:both; pointer-events:auto; transform:translate(0,0); }
|
||||
.maplibregl-ctrl-attrib { margin:0 6px 4px; padding:3px 8px; border-radius:6px; color:var(--ink); background:var(--panel-solid); font-size:10px; line-height:18px; max-width:calc(100vw - 12px); }
|
||||
.maplibregl-ctrl-attrib a { color:inherit; text-decoration:none; }
|
||||
.maplibregl-ctrl-attrib a:hover { text-decoration:underline; }
|
||||
.maplibregl-ctrl-attrib-button { display:none; border:1px solid currentColor; border-radius:50%; background:var(--panel-solid); color:var(--ink); cursor:pointer; width:44px; height:44px; filter:none !important; }
|
||||
.maplibregl-ctrl-attrib-button::before { content:'i'; font:bold 16px Georgia,serif; }
|
||||
.maplibregl-compact { padding:0; min-height:44px; }
|
||||
.maplibregl-compact .maplibregl-ctrl-attrib-button { display:block; float:right; }
|
||||
.maplibregl-compact .maplibregl-ctrl-attrib-inner { display:none; }
|
||||
.maplibregl-compact-show .maplibregl-ctrl-attrib-inner { display:block; padding:8px 48px 8px 10px; }
|
||||
.maplibregl-ctrl-attrib.maplibregl-attrib-empty { display:none; }
|
||||
.maplibregl-boxzoom { position:absolute; top:0; left:0; width:0; height:0; border:2px dashed var(--focus-ring); background:#72ffe118; }
|
||||
.maplibregl-crosshair,.maplibregl-crosshair .maplibregl-interactive { cursor:crosshair; }
|
||||
.maplibregl-popup { position:absolute; display:flex; top:0; left:0; pointer-events:none; }
|
||||
.maplibregl-popup-content { position:relative; background:var(--panel-solid); border:1px solid var(--panel-border); border-radius:12px; pointer-events:auto; }
|
||||
.maplibregl-popup-close-button { position:absolute; right:0; top:0; width:44px; height:44px; border:0; background:transparent; color:var(--ink); }
|
||||
.maplibregl-popup-anchor-top,.maplibregl-popup-anchor-top-left,.maplibregl-popup-anchor-top-right { flex-direction:column; }
|
||||
.maplibregl-popup-anchor-bottom,.maplibregl-popup-anchor-bottom-left,.maplibregl-popup-anchor-bottom-right { flex-direction:column-reverse; }
|
||||
.maplibregl-popup-anchor-left { flex-direction:row; }
|
||||
.maplibregl-popup-anchor-right { flex-direction:row-reverse; }
|
||||
.maplibregl-popup-tip { display:none; }
|
||||
@media(forced-colors:active) { .maplibregl-ctrl-attrib { border:1px solid CanvasText; } }
|
||||
@@ -1,3 +1,4 @@
|
||||
import { mountSoundPreview } from '../soundPreview';
|
||||
import { requestedNode, rememberNode, replaceInspector } from '../selection';
|
||||
import { mountCinematicDock } from '../cinematicChrome';
|
||||
import { populateSoundScenes, syncSoundScene, SOUND_SCENES } from '../soundScenes';
|
||||
@@ -107,6 +108,7 @@ async function start(): Promise<void> {
|
||||
renderer = graph;
|
||||
const routeSonifier = new RouteSonifier(graph, stage);
|
||||
sonifier = routeSonifier;
|
||||
mountSoundPreview(routeSonifier, soundPanel);
|
||||
configureSound(routeSonifier);
|
||||
graph.setRouteWindow(settings.routeWindow);
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { RouteSonifier } from './audio';
|
||||
export function mountSoundPreview(sonifier: RouteSonifier, parent: HTMLElement): void {
|
||||
const controls = document.createElement('div'); controls.className = 'sound-preview-actions';
|
||||
const preview = document.createElement('button'); preview.type='button'; preview.textContent='Preview voice'; preview.className='control-button';
|
||||
preview.disabled=!sonifier.supported();
|
||||
preview.addEventListener('click', async () => { preview.disabled=true; try { await sonifier.preview(); } catch { preview.textContent='Preview unavailable'; } finally { window.setTimeout(()=>{preview.disabled=!sonifier.supported();},700); } });
|
||||
const mute=document.createElement('button'); mute.type='button';mute.textContent='Mute';mute.className='control-button';
|
||||
mute.addEventListener('click',()=>{void sonifier.setEnabled(false);});
|
||||
controls.append(preview,mute);parent.append(controls);
|
||||
}
|
||||
Reference in new issue
Block a user