Merge pull request #1472 from bilawalsidhu/authoring-panel

Rename the Scenes panel to Authoring and lay it out in steps
This commit is contained in:
Said Turksever authored and GitHub committed 2026-10-30 17:00:57 +00:00
commit a725178e0f
23 files changed
+725 -137

No files matched your search

+4
View File
@@ -0,0 +1,4 @@
- Rename the Scenes panel to Authoring and lay it out in steps (Scene, Shots,
Preview, Save & share) with larger, higher-contrast text, numbered shots, a
tooltip on every action and buttons that are disabled when they would do
nothing (asturksever, #1472).
+24 -11
View File
@@ -544,7 +544,7 @@ Native `<select>` controls declare a dark colour scheme and explicit option
colours, so the browser-painted option list matches the panel it drops out of.
The scheme is declared on the controls rather than on the document root, so
hand-tuned scrollbar and text-input skins are unchanged. Applies to the HUD
layout, Scenes, CCTV camera, Radio filter and Draw colour menus.
layout, Authoring, CCTV camera, Radio filter and Draw colour menus.
Vessel snapshot completeness is separate from freshness. A current snapshot with
rejected or duplicate records shows PARTIAL with accepted/received counts; stale
@@ -615,7 +615,7 @@ from revealing its UI. Ambiguous requests to open settings ask whether the user
means Voice Settings or Provider Settings before opening either panel. Named
Voice Settings applies directly; Provider Settings opens the existing local
provider dialog when available. Visual Presets targets the visual controls tray,
while Scenes targets saved scenes. Selecting Preview does not enable a disabled
while Scenes (the Authoring panel) targets saved scenes. Selecting Preview does not enable a disabled
Preview preference. Unverified speech captions use the localized Recent
speech label; ordinary user captions retain YOU. Caption attribution does not
control tool dispatch.
@@ -748,7 +748,7 @@ Nepal scene ownership follows the parameters prepared by the public layer manage
Animated scene cards sample their optional scale, opacity and leader callbacks once per projected frame. A failed callback suppresses that frame and can recover on the next one; ordinary layers keep the shared projection path without animation evaluation.
Nepal Flood Incident is accessed through Scenes. Its flood and locator components remain registered for scene playback but are hidden from the Data Layers menu. Closing the event stops the active scene, including pending loads and media. The default catalog does not install a separate reconstruction scene; existing saved scenes remain untouched.
Nepal Flood Incident is accessed through Authoring. Its flood and locator components remain registered for scene playback but are hidden from the Data Layers menu. Closing the event stops the active scene, including pending loads and media. The default catalog does not install a separate reconstruction scene; existing saved scenes remain untouched.
Double-click a shot name to edit it inline. Enter or moving focus saves a nonempty name, Escape cancels, and saved names persist with the scene project.
@@ -762,7 +762,7 @@ Persistent media-led Nepal shots preserve their completed upstream flood path du
Repeated map-stack requests retain the live imagery layer when its provider is unchanged. Scene handoffs keep loaded tiles while real source changes, switch cancellation, and tile-error fallbacks retain their existing behavior.
Nepal Flood Incident is available in Scenes, with a 25-shot sequence, geographic labels, an event panel, scene scrubbing and shot playback. Comparison shots use Esri beneath Vantor imagery. Other Nepal shots use Google 3D when its tileset is available and fall back to Esri otherwise; without an ion token, the existing map stack uses Re:Earth terrain. Saved shots retain their authored camera and map preferences across keyed and keyless runtimes. The bundled pack contains historical 2021 and post-event 2026 imagery, not an isolated before/after measurement; the animated flood path is schematic, not a hazard model. Witness media opens or embeds original sources without bundled clips. See `public/events/bhote-koshi-2026/README.md` for provenance and non-commercial data terms.
Nepal Flood Incident is available in Authoring, with a 25-shot sequence, geographic labels, an event panel, scene scrubbing and shot playback. Comparison shots use Esri beneath Vantor imagery. Other Nepal shots use Google 3D when its tileset is available and fall back to Esri otherwise; without an ion token, the existing map stack uses Re:Earth terrain. Saved shots retain their authored camera and map preferences across keyed and keyless runtimes. The bundled pack contains historical 2021 and post-event 2026 imagery, not an isolated before/after measurement; the animated flood path is schematic, not a hazard model. Witness media opens or embeds original sources without bundled clips. See `public/events/bhote-koshi-2026/README.md` for provenance and non-commercial data terms.
Transit snapshot and selected-history reads use an explicit source interface. The standalone source selects the existing routes; rendering, playback and selection retain their existing owners. A portable request service owns registered-feed admission, bounded decoding, cache/revalidation, backoff and history; development and preview use the same thin adapter. Compatibility exports and source attribution are preserved.
@@ -1190,6 +1190,19 @@ Shot selection updates the highlight without replacing the row, preserving
native double-click rename. Replacing rows revokes their old listeners. Disposal stops controls immediately;
late file and failed-action completions cannot update removed presentation.
The panel is titled Authoring and is laid out in four labelled steps: SCENE
(picker on its own row, NEW SCENE, DELETE), SHOTS (+ CAPTURE SHOT, UPDATE SHOT,
the numbered shot list and a select/rename hint), PREVIEW (PLAY, STOP, NEXT) and
SAVE & SHARE (EXPORT, IMPORT, RUN LOG, then the EDIT DETAILS / SHARE SCENE row).
Every button in the step rows has a tooltip, and the status line is a polite
live region. Outside playback a button that would do nothing is disabled:
DELETE and CAPTURE SHOT without a scene, UPDATE SHOT without a selected shot,
PLAY and NEXT when no scene in the project has a shot (both continue into the
following scenes). During playback the authoring and file actions stay disabled
and STOP is enabled, as before; RUN LOG follows the last run, and EDIT DETAILS /
SHARE SCENE keep their own checks. PLAY uses the `scene|PLAY` catalog entry,
apart from the radio's PLAY.
Director clock listeners, interaction observers and scene release failures no
longer leave the director inconsistent, and a phase error is kept when cleanup
also fails.
@@ -1535,7 +1548,7 @@ collapse can still be undone.
## Phone layout
At 720px and narrower, `src/ui/mobileShell.js` keeps the globe full screen and
shows a bottom tab bar: Layers (Data Layers, with Scenes collapsed below),
shows a bottom tab bar: Layers (Data Layers, with Authoring collapsed below),
Cameras (CCTV and Street Level), Context (Global Context, plus Weather and
Recent Imagery when shown), Search (Location) and More (Visual Presets and Map
Source above Display). A tab opens one bottom sheet; the same tab, the backdrop
@@ -1566,7 +1579,7 @@ panel the user changed inside the sheet stays as they left it. Escape closes the
sheet before a panel's own Escape handling, and the dock's hover auto-close does
not apply inside a sheet.
Cockpit, recording and clean view close the sheet and hide the tab bar; scene
playback keeps both so its STOP control in the Scenes panel stays reachable. The
playback keeps both so its STOP control in the Authoring panel stays reachable. The
right rail neither hides collapsed siblings nor applies Cyber's one-panel
accordion at these widths, since the sheet chooses what shows. The Cesium and Google attribution stays visible above the
voice control, and above an open sheet. The page draws under the notch and
@@ -3012,7 +3025,7 @@ down | auth-failed` (plus the unchanged `missing-key`/`unsupported`) with
> sibling is also removed from the accessibility tree, and focus transfers to
> the expanded utility if a layout transition hides the focused launcher. The
> shared map Display, Radio detail,
> ordinary Global Context, and Scenes surfaces stay hidden in Cockpit. Data
> ordinary Global Context, and Authoring surfaces stay hidden in Cockpit. Data
> Layers remains available. Its Cockpit-only stack paints
> above the curved speed and altitude rulers, matching the existing Context,
> Signals, Display, and Radio surface ordering without changing map-view
@@ -4837,7 +4850,7 @@ silently demoting every later lookup for the session.
- Installations are viewport-bounded OSM map features (`military=airfield|naval_base|range|barracks|base` and `landuse=military`), capped to a 10° non-dateline request and 700 upstream features. The proxy caches five minutes and serves a one-hour stale fallback. Empty, stale, unavailable, and zoom-too-wide states remain visibly distinct.
- With a selected subject, Contacts hands the installation layer the subject position every refresh. The layer loads a window around the subject (see Installations and map-source guidance), because a follow or Cockpit camera never fires `moveEnd` and often looks at the horizon: a first load from a globe view used to leave the row at "?" for the whole track. The row and the Cockpit scope read `WITHIN 100 KM` instead of `CURRENT VIEWPORT ONLY`, and an empty window reads "none mapped within 100 km; not a complete 250 km survey". `scripts/qa-context-arrivals.mjs` flies synthetic aircraft over Camp Mabry and Fort Cavazos from a wide start, presses CONTACTS, Cockpit and SEARCH NEARBY SITES, and records a clip.
- The selected-only visual is one static, unfilled blue circle. It marks the 250 km proximity context window only; it is not coverage or a radar/weapon envelope. Missing broadcasts and unmapped sites are explicitly not evidence of absence.
- The right rail's collapsed Display, CCTV, and Context controls use the same compact sizing language as the left rail's collapsed Data Layers and Scenes controls. These compact-state widths do not constrain expanded panel or child-content widths.
- The right rail's collapsed Display, CCTV, and Context controls use the same compact sizing language as the left rail's collapsed Data Layers and Authoring controls. These compact-state widths do not constrain expanded panel or child-content widths.
### Motion & Symbology Correctness (June 10, 2026)
@@ -5610,7 +5623,7 @@ apply to `public/` assets or built-preview serving.
- The desktop right rail (`#right-context-rail`) owns `DISPLAY`, `CCTV`, its active parameter controls, and `GLOBAL CONTEXT` as one fixed responsive stack in that order. Its compact buttons use the same 176 px width as the left accordion and one consistent 50 px height, share the left stack's 52 px edge inset and measured top baseline across HUD variants, then constrain themselves against visible HUD/chrome rectangles and the remaining vertical corridor. `DISPLAY` is no longer draggable and legacy saved coordinates are ignored.
- The right rail is labeled **DISPLAY** (formerly "MOVE") and groups, in order, HUD, DETECT, Bloom, Sharpen, 3D, Clean-UI (HUD + DETECT promoted to the top). Its expanded controls retain the same compact 176 px width as the right-side tabs instead of growing to the wider Context detail-card width. It starts expanded on first run and respects the user's later `v6` collapse choice. Collapses/expands with directional chevrons (`◀` collapsed, `▶` expanded).
- Display and Context use matching 330 px expanded widths and matching compact tab dimensions. The parameter panel is part of Display's expanded content. DISPLAY may remain open beside one contextual panel; CCTV and Context are mutually exclusive. In Tactical HUD, expanding CCTV or Context hides the other contextual launcher while DISPLAY remains independently available. The most recently opened right-rail panel owns the constrained lane even when it appears later in DOM order; passive restoration and automatic disclosure do not replace that explicit owner. Minimal and other HUD layouts retain the collapsed launchers; when their active panel exceeds the measured corridor, the rail reserves sibling heights and gaps and scrolls the active panel internally.
- At 720px and below both panel stacks (`#left-panel-stack`, `#right-context-rail`) go edge-to-edge and scroll vertically. Every hosted panel's decorative `.panel-glow` is pinned to its panel box there (`inset: 0` in `src/ui/styles/responsive.css`): on desktop the glow overhangs the panel by 18–20 px as harmless paint, but inside a scroll container that overhang is scrollable overflow on both axes and drew a horizontal scrollbar band plus a vertical scrollbar that scrolled nothing but glow whenever CCTV, Context, Data Layers or Scenes was expanded. The glow's blur is ink overflow and never scrolls, so the look is unchanged. Do not fix this by clipping the stacks: `overflow-x: clip` would not remove the vertical scrollbar. Context draws no glow at all (`display: none`), so it is not part of that pin. Pinned by `src/ui/panelRails.test.mjs` against every `(max-width: 720px)` block in the flattened stylesheet.
- At 720px and below both panel stacks (`#left-panel-stack`, `#right-context-rail`) go edge-to-edge and scroll vertically. Every hosted panel's decorative `.panel-glow` is pinned to its panel box there (`inset: 0` in `src/ui/styles/responsive.css`): on desktop the glow overhangs the panel by 18–20 px as harmless paint, but inside a scroll container that overhang is scrollable overflow on both axes and drew a horizontal scrollbar band plus a vertical scrollbar that scrolled nothing but glow whenever CCTV, Context, Data Layers or Authoring was expanded. The glow's blur is ink overflow and never scrolls, so the look is unchanged. Do not fix this by clipping the stacks: `overflow-x: clip` would not remove the vertical scrollbar. Context draws no glow at all (`display: none`), so it is not part of that pin. Pinned by `src/ui/panelRails.test.mjs` against every `(max-width: 720px)` block in the flattened stylesheet.
- `STYLE PRESETS` and `LOCATIONS` start collapsed, expand on intentional hover/click, and auto-collapse after hover leave delay.
- Collapsed mini-status indicators show active style and active location/landmark.
- Detection mode is user-controlled and should persist when switching styles. Since 2026-08-22 it also STARTS on — Dense @ 75% for every style on a first run, Normal included — as a `GLOBAL_POST_DEFAULTS` baseline that does NOT set `_detectionUserOverridden`. Exception (unchanged): selecting a military style (CRT/NVG/FLIR) auto-enables the same Dense preset, but only until the user manually changes detection this session (`_detectionUserOverridden` gate), after which style switches never touch it.
@@ -5675,8 +5688,8 @@ apply to `public/` assets or built-preview serving.
be enabled, and a civilian or military aircraft must be tracked. The visible
Cockpit actions and the `C` shortcut use the same gate, so ordinary standalone
flight-layer selection cannot enter a context-dependent cockpit.
- **Aircraft cockpit view:** selecting a commercial or military aircraft reveals a `COCKPIT` action (`C`). Cockpit mode temporarily releases Cesium's orbit-follow transform and drives a first-person camera from the tracked aircraft's existing smoothed display position and course. Its concave helmet-visor HUD shows callsign, UTC time, coordinates, curved roll/pitch guides, and a seven-division heading tape. Ambient commercial and military AIR contacts use a Cockpit near/far band selected by the shared Display 3D mode: Proximity admits at 150 km and retains to 185 km; All admits at 400 km and retains to 450 km. The shared 3D toggle now applies in Cockpit: Off keeps in-range contacts as rotating 2D aircraft silhouettes, while On lets a ready admitted glTF take over without a drawing gap. The Cockpit model cap remains 60 and can only lower the map budget; in-range contacts that are capped or still loading remain 2D silhouettes instead of degrading to out-of-range dots. Contacts outside the selected band use small rotation-free cyan-white pips for civilian aircraft and amber pips for military. The pilot's own airframe is not drawn in first person, and exiting clears the Cockpit band and restores normal map silhouettes/models. The normal left accordion remains available for Layers and Scenes and keeps the same 26vh HUD-aligned anchor used in map mode, independent of whether the bottom-left Contact card is expanded or collapsed; the right-rail CCTV and Context chooser are hidden to avoid duplicating or overlapping the cockpit presentation. When the intelligence HUD is enabled, its classification, scene summary, collection/orbit metadata, coordinates, and imaging-status text remain visible as reduced peripheral cockpit telemetry; the optical center and flight instruments stay clear, and turning the HUD off still hides it. Ground speed, exact heading, and rendered altitude form one compact lower-center instrument cluster, keeping the horizon and peripheral view open. A second live altitude tape hugs the inside-right visor rim: its rail and nine moving ticks are derived from the same responsive keyhole radius, remain 20 px inside the circular edge even on wide displays, fade deeply at both vertical ends, and move continuously behind a fixed current-altitude pointer; its interval tightens automatically near the surface and it is suppressed on narrow screens. Cockpit mode also has a weather-backed transparent volumetric-cloud pass derived from the supplied FBM/domain-warped R&D shader. It renders at no more than 520×320, uses 24 ray steps/three FBM octaves at 12 FPS, is clipped to the visor, fails clear when Open-Meteo is unavailable, and stops its animation completely on cockpit exit. It does not restore the prior CPU weather canvases, precipitation, scene fog, or any map-mode effect. When Contacts is active for the tracked aircraft, the compact `CONTACT` rail adds the 250 km subject window, four cohort counts, nearest observed/mapped example with relative bearing and distance, freshness, explicit uncertainty, and Previous and Next controls plus its own collapse control. The mirrored right rail is a three-page **cockpit briefing carousel**: source-backed live signals, location-matched regional headlines, and physical-region/current-weather context from bundled Natural Earth polygons and Open-Meteo. It is manual-first: Previous, Next, and direct page controls are always available, and the visible `CYCLE OFF` / `CYCLE ON` control starts or stops the nine-second page cycle. The cycle pauses on hover or keyboard focus and stops while collapsed, hidden, or outside cockpit mode; live signal data continues refreshing either way. Empty news matches and unavailable news use compact text states rather than reserving an empty media frame; partial local data remains explicit. Article links open their original publisher, and no headline is treated as verified risk intelligence. On desktop both cockpit rails use the same width and share a bottom-aligned safe baseline in opposite corners above the peripheral MGRS/GSD/time telemetry, leaving both that text and the lower-center instrument cluster readable. They collapse independently to slim tabs without stopping live data updates. Narrow screens use separated top/bottom fallbacks. Empty-space globe clicks are inert while cockpit owns the camera; `C`, `Escape`, or `EXIT COCKPIT` explicitly exits and restores the same tracked entity and standard follow camera. Unknown feeds remain unknown, selection loss still exits safely without inventing a replacement track, and the cockpit never presents the summary as threat scoring or an all-clear. This is a desktop first-person presentation, not a WebXR session.
- **Cockpit left-panel clearance:** the Cockpit Contact card and peripheral HUD participate in the adaptive left accordion's live obstacle measurements, including live viewport-height changes. Expanding Layers or Scenes keeps the active panel in the available upper-left corridor with internal scrolling; it does not cover the Contact card, lower Cockpit controls, or Cesium credit line. Outside Cockpit the hidden card does not alter the normal corridor.
- **Aircraft cockpit view:** selecting a commercial or military aircraft reveals a `COCKPIT` action (`C`). Cockpit mode temporarily releases Cesium's orbit-follow transform and drives a first-person camera from the tracked aircraft's existing smoothed display position and course. Its concave helmet-visor HUD shows callsign, UTC time, coordinates, curved roll/pitch guides, and a seven-division heading tape. Ambient commercial and military AIR contacts use a Cockpit near/far band selected by the shared Display 3D mode: Proximity admits at 150 km and retains to 185 km; All admits at 400 km and retains to 450 km. The shared 3D toggle now applies in Cockpit: Off keeps in-range contacts as rotating 2D aircraft silhouettes, while On lets a ready admitted glTF take over without a drawing gap. The Cockpit model cap remains 60 and can only lower the map budget; in-range contacts that are capped or still loading remain 2D silhouettes instead of degrading to out-of-range dots. Contacts outside the selected band use small rotation-free cyan-white pips for civilian aircraft and amber pips for military. The pilot's own airframe is not drawn in first person, and exiting clears the Cockpit band and restores normal map silhouettes/models. The normal left accordion remains available for Layers and Authoring and keeps the same 26vh HUD-aligned anchor used in map mode, independent of whether the bottom-left Contact card is expanded or collapsed; the right-rail CCTV and Context chooser are hidden to avoid duplicating or overlapping the cockpit presentation. When the intelligence HUD is enabled, its classification, scene summary, collection/orbit metadata, coordinates, and imaging-status text remain visible as reduced peripheral cockpit telemetry; the optical center and flight instruments stay clear, and turning the HUD off still hides it. Ground speed, exact heading, and rendered altitude form one compact lower-center instrument cluster, keeping the horizon and peripheral view open. A second live altitude tape hugs the inside-right visor rim: its rail and nine moving ticks are derived from the same responsive keyhole radius, remain 20 px inside the circular edge even on wide displays, fade deeply at both vertical ends, and move continuously behind a fixed current-altitude pointer; its interval tightens automatically near the surface and it is suppressed on narrow screens. Cockpit mode also has a weather-backed transparent volumetric-cloud pass derived from the supplied FBM/domain-warped R&D shader. It renders at no more than 520×320, uses 24 ray steps/three FBM octaves at 12 FPS, is clipped to the visor, fails clear when Open-Meteo is unavailable, and stops its animation completely on cockpit exit. It does not restore the prior CPU weather canvases, precipitation, scene fog, or any map-mode effect. When Contacts is active for the tracked aircraft, the compact `CONTACT` rail adds the 250 km subject window, four cohort counts, nearest observed/mapped example with relative bearing and distance, freshness, explicit uncertainty, and Previous and Next controls plus its own collapse control. The mirrored right rail is a three-page **cockpit briefing carousel**: source-backed live signals, location-matched regional headlines, and physical-region/current-weather context from bundled Natural Earth polygons and Open-Meteo. It is manual-first: Previous, Next, and direct page controls are always available, and the visible `CYCLE OFF` / `CYCLE ON` control starts or stops the nine-second page cycle. The cycle pauses on hover or keyboard focus and stops while collapsed, hidden, or outside cockpit mode; live signal data continues refreshing either way. Empty news matches and unavailable news use compact text states rather than reserving an empty media frame; partial local data remains explicit. Article links open their original publisher, and no headline is treated as verified risk intelligence. On desktop both cockpit rails use the same width and share a bottom-aligned safe baseline in opposite corners above the peripheral MGRS/GSD/time telemetry, leaving both that text and the lower-center instrument cluster readable. They collapse independently to slim tabs without stopping live data updates. Narrow screens use separated top/bottom fallbacks. Empty-space globe clicks are inert while cockpit owns the camera; `C`, `Escape`, or `EXIT COCKPIT` explicitly exits and restores the same tracked entity and standard follow camera. Unknown feeds remain unknown, selection loss still exits safely without inventing a replacement track, and the cockpit never presents the summary as threat scoring or an all-clear. This is a desktop first-person presentation, not a WebXR session.
- **Cockpit left-panel clearance:** the Cockpit Contact card and peripheral HUD participate in the adaptive left accordion's live obstacle measurements, including live viewport-height changes. Expanding Layers or Authoring keeps the active panel in the available upper-left corridor with internal scrolling; it does not cover the Contact card, lower Cockpit controls, or Cesium credit line. Outside Cockpit the hidden card does not alter the normal corridor.
- **Cockpit Context scope:** the 250 km radius applies to the air/sea proximity cohorts. Installation counts come only from the currently loaded viewport and are labeled `CURRENT VIEWPORT ONLY` in the cockpit as well as the normal Context panel; neither surface presents them as a complete 250 km installation survey.
- **Cockpit camera anchor:** first-person mode does not write feed-boundary corrections directly into the camera. A cockpit-only inertial anchor advances from the selected aircraft's displayed course and speed, then converges toward the authoritative delayed track with correction capped below forward motion. The displayed kinematics are derived from the same consecutive fix segment as the rendered position, with raw feed speed/course used only as fallback; a transient zero/missing feed speed therefore cannot freeze a visibly moving aircraft after layer enable or a map/cockpit handoff. Rendered altitude continues to come from that interpolated track position. Late ADS-B fixes and short render stalls can remove drift without accelerating or reversing the view. Camera placement runs before scene update/culling at a bounded 20 Hz so a moving cockpit does not force Photoreal 3D Tiles to retraverse on every display frame; textual instruments update at 10 Hz and context/layout work at 4 Hz. Every far Cockpit contact pip shares one stable Cesium texture-atlas entry and skips unused screen-projected course calculations, while only in-range 2D aircraft silhouettes pay the screen-projected rotation cost; ambient glTF collections are hidden/retained rather than synchronously destroyed at cockpit entry, and context rails lay out only on explicit content/state changes and viewport resize. The deliberate 15/30-second layer interpolation delays and per-Cesium-frame position caches remain unchanged.
- **Cockpit route, vision, and view controls:** visible on-screen `COCKPIT`, `RESET`, and `EXIT COCKPIT` controls replace reliance on the `C` shortcut. RESET uses the same canonical globe route as the map and voice actions, exits Cockpit, and releases its camera ownership rather than exposing the hidden map-style top action. When the tracked commercial flight has a plausible ADSBDB route, the top of the right briefing rail shows a compact `FROM → TO` airport strip and the visor shows a centered estimated-destination chevron with its relative bearing; its destination label rounds to whole degrees in 000–359, wrapping rounded north to 000° while the chevron continues to use the relative bearing independently of label rounding; absent or implausible route data hides the strip and cue rather than guessing. The cockpit-local vision control is an interactive `PREV / CURRENT / NEXT` carousel over `NORMAL`, `CRT`, `NVG`, `FLIR`, `ANIME`, and `NOIR`; its previous/next actions wrap, and activating the current value advances to the next style. Cockpit enters on the active map preset, so a Cyber FLIR map opens Cockpit on FLIR while Normal remains a distinct option instead of producing a second FLIR entry. Cockpit choices temporarily activate the existing Cesium post-process stages, while exiting Cockpit through either EXIT or RESET restores the pre-entry map style. The regional-news page uses a free Google News RSS locality query first, with the existing GDELT query retained only as a fail-soft fallback; linked headlines remain reporting, not verified incidents or risk intelligence.
+2 -2
View File
@@ -1,6 +1,6 @@
# Director authoring and file sharing
Open Scenes and select a scene and shot. Existing capture, update, playback and
Open Authoring and select a scene and shot. Existing capture, update, playback and
whole-project export controls remain available. Authored scene content, source
links, assets and contributor credit are preserved.
@@ -42,7 +42,7 @@ field definitions. This is a basic draft editor, not a node graph or path editor
SHARE SCENE exports only the selected scene. Download scene JSON preserves its
camera, timing, layers, anchors, manifests, interactions and attribution, but
contains no asset bytes. The recipient needs the referenced source registrations
and files. Existing EXPORT PRESETS continues to export the entire project.
and files. EXPORT (formerly EXPORT PRESETS) continues to export the entire project.
For a bundle, select the declared pack files or a folder, then Download asset
bundle. Folder paths match a manifest path either directly or after removing the
+1 -1
View File
@@ -1,6 +1,6 @@
# Scene document format
Scenes exports a UTF-8 JSON project. Version 5 adds [data packs](DIRECTOR-DATA-PACKS.md)
Authoring exports a UTF-8 JSON project. Version 5 adds [data packs](DIRECTOR-DATA-PACKS.md)
and is the write format. Versions 1, 2, 3, 4 and unversioned legacy files migrate on
import without converting their existing shots into authored moves. Unknown versions or unsupported fields are rejected with
a field path before the current project, selection or saved bytes change.
+33
View File
@@ -106,6 +106,39 @@ try {
),
),
);
// A long rename keeps its editor visible inside the shot row.
await page.click('.scene-shot-label', { count: 2 });
await page.waitForSelector('.scene-shot-rename', { visible: true });
await page.type(
'.scene-shot-rename',
' with a much longer title that runs well past the shot row',
);
await page.keyboard.press('End');
check(
'A long rename keeps the editor visible inside its shot row',
await page.evaluate(() => {
// The label clips with an ellipsis, which hides an editor that
// overflows it, so its content must fit its width.
const input = document.querySelector('.scene-shot-rename');
const label = input?.closest('.scene-shot-label');
const box = input?.getBoundingClientRect();
const style = input && getComputedStyle(input);
return (
!!box &&
box.width > 120 &&
box.height > 10 &&
label.scrollWidth <= label.clientWidth + 1 &&
style.visibility === 'visible' &&
style.borderTopWidth !== '0px' &&
document.activeElement === input
);
}),
);
await page.keyboard.press('Escape');
await page.waitForFunction(
() => !document.querySelector('.scene-shot-rename'),
);
await page.click('#scene-capture-btn');
await page.waitForFunction(
() => document.querySelectorAll('.scene-shot-row').length === 2,
+24 -7
View File
@@ -837,13 +837,7 @@
"SAVE CAL": "KAL. SPEICHERN",
"RESET CAL": "KAL. ZURÜCKSETZEN",
"SCENE SUMMARY": "SZENEN-ÜBERSICHT",
"SCENES": "SZENEN",
"Scene recipe": "Szenenrezept",
"NEW": "NEU",
"CAPTURE SHOT": "AUFNAHME SPEICHERN",
"UPDATE SHOT": "AUFNAHME AKTUALISIEREN",
"START": "START",
"EXPORT PRESETS": "PRESETS EXPORTIEREN",
"IMPORT": "IMPORTIEREN",
"RUN LOG": "PROTOKOLL",
"POWER UP": "STARTKLAR",
@@ -2926,5 +2920,28 @@
"Malformed cyclone snapshot": "Fehlerhafter Wirbelsturm-Snapshot",
"STALE · Roads: {source} · Flow: cached TomTom tiles": "VERALTET · Straßen: {source} · Verkehrsfluss: TomTom-Kacheln aus dem Cache",
"routing busy — too many routes at once": "Routing-Dienst ausgelastet – zu viele Routen gleichzeitig",
"route proxy error": "Fehler des Routen-Proxys"
"route proxy error": "Fehler des Routen-Proxys",
"scene|PLAY": "START",
"AUTHORING": "ERSTELLUNG",
"Create and edit scenes": "Szenen erstellen und bearbeiten",
"SCENE": "SZENE",
"Scene": "Szene",
"NEW SCENE": "NEUE SZENE",
"Start a new, empty scene": "Neue, leere Szene beginnen",
"DELETE": "LÖSCHEN",
"Delete this scene and all of its shots": "Diese Szene mit allen Aufnahmen löschen",
"SHOTS": "AUFNAHMEN",
"+ CAPTURE SHOT": "+ AUFNAHME SPEICHERN",
"Add the current view, style and layers as the next shot": "Aktuelle Ansicht, Stil und Ebenen als nächste Aufnahme hinzufügen",
"Replace the selected shot with the current view": "Ausgewählte Aufnahme durch die aktuelle Ansicht ersetzen",
"Click a shot to select it, double-click to rename.": "Aufnahme anklicken zum Auswählen, Doppelklick zum Umbenennen.",
"Play from this scene through the rest of the project": "Ab dieser Szene bis zum Ende des Projekts abspielen",
"Stop playback (Esc)": "Wiedergabe stoppen (Esc)",
"Play the shot after the selected one, moving on to the next scene when needed": "Aufnahme nach der ausgewählten abspielen, bei Bedarf in der nächsten Szene",
"SAVE & SHARE": "SPEICHERN & TEILEN",
"Download every scene as a JSON file you can import later": "Alle Szenen als JSON-Datei zum späteren Import herunterladen",
"Load scenes from an exported JSON file": "Szenen aus einer exportierten JSON-Datei laden",
"Download the timing log of the last play": "Zeitprotokoll der letzten Wiedergabe herunterladen",
"Edit anchors, data packs and shot details": "Anker, Datenpakete und Aufnahmedetails bearbeiten",
"Download this scene as JSON or an asset bundle": "Diese Szene als JSON oder Asset-Bundle herunterladen"
}
+24 -7
View File
@@ -752,14 +752,8 @@
"The globe already flies keyless. Every key below switches on another real feed — paste one and it's saved into this app's local configuration, then the server restarts itself. Server-side keys stay on this machine; Google Maps and Cesium ion run in the browser and must be provider-restricted. Keys you configured elsewhere are shown but never touched.": "El globo ya funciona sin claves. Cada clave de abajo activa otra fuente real — pega una y queda guardada en la configuración local de esta app, y luego el servidor se reinicia solo. Las claves del servidor se quedan en esta máquina; Google Maps y Cesium ion funcionan en el navegador y sus claves deben restringirse en la consola del proveedor. Las claves que configuraste en otro lugar se muestran, pero nunca se modifican.",
"ESC to close": "ESC para cerrar",
"The Google Maps key buys the photorealistic planet — everything else stacks on top.": "La clave de Google Maps activa el planeta fotorrealista; todo lo demás se superpone.",
"SCENES": "ESCENAS",
"Scene recipe": "Receta de escena",
"NEW": "NUEVA",
"DEL": "BORRAR",
"CAPTURE SHOT": "CAPTURAR PLANO",
"UPDATE SHOT": "ACTUALIZAR PLANO",
"START": "INICIAR",
"EXPORT PRESETS": "EXPORTAR PREAJUSTES",
"IMPORT": "IMPORTAR",
"RUN LOG": "REGISTRO DE EJECUCIÓN",
"Starting live contacts…": "Iniciando contactos en vivo…",
@@ -2996,5 +2990,28 @@
"Preview is off.": "La vista previa está desactivada.",
"Automatic stop is unavailable for this voice connection.": "La parada automática no está disponible para esta conexión de voz.",
"routing busy — too many routes at once": "servicio de rutas ocupado: demasiadas rutas a la vez",
"route proxy error": "error del proxy de rutas"
"route proxy error": "error del proxy de rutas",
"AUTHORING": "CREACIÓN",
"Create and edit scenes": "Crea y edita escenas",
"SCENE": "ESCENA",
"Scene": "Escena",
"NEW SCENE": "NUEVA ESCENA",
"Start a new, empty scene": "Empieza una escena nueva y vacía",
"DELETE": "ELIMINAR",
"Delete this scene and all of its shots": "Elimina esta escena y todos sus planos",
"SHOTS": "PLANOS",
"+ CAPTURE SHOT": "+ CAPTURAR PLANO",
"Add the current view, style and layers as the next shot": "Añade la vista, el estilo y las capas actuales como el siguiente plano",
"Replace the selected shot with the current view": "Sustituye el plano seleccionado por la vista actual",
"Click a shot to select it, double-click to rename.": "Haz clic en un plano para seleccionarlo y doble clic para cambiarle el nombre.",
"scene|PLAY": "REPROD.",
"Stop playback (Esc)": "Detén la reproducción (Esc)",
"SAVE & SHARE": "GUARDAR Y COMPARTIR",
"Download every scene as a JSON file you can import later": "Descarga todas las escenas como un archivo JSON que puedes importar más tarde",
"Load scenes from an exported JSON file": "Carga escenas desde un archivo JSON exportado",
"Download the timing log of the last play": "Descarga el registro de tiempos de la última reproducción",
"Play from this scene through the rest of the project": "Reproduce desde esta escena hasta el resto del proyecto",
"Play the shot after the selected one, moving on to the next scene when needed": "Reproduce el plano posterior al seleccionado y pasa a la siguiente escena si hace falta",
"Edit anchors, data packs and shot details": "Edita anclas, paquetes de datos y detalles de los planos",
"Download this scene as JSON or an asset bundle": "Descarga esta escena como JSON o como paquete de recursos"
}
+24 -7
View File
@@ -2959,13 +2959,7 @@
"ALL": "TOUTES",
"FLAT": "CLASSIQUES",
"SINCE": "DEPUIS",
"SCENES": "SCÈNES",
"Scene recipe": "Recette de scène",
"NEW": "NOUV.",
"CAPTURE SHOT": "CAPTURER UN PLAN",
"UPDATE SHOT": "MAJ DU PLAN",
"START": "LANCER",
"EXPORT PRESETS": "EXPORTER LES PRÉRÉGLAGES",
"IMPORT": "IMPORTER",
"RUN LOG": "JOURNAL",
"POWER UP": "ALLUMER",
@@ -2996,5 +2990,28 @@
"ESC to dismiss": "ESC pour fermer",
"Tip: the GEV MIC button in the dock lets you talk to the map.": "Astuce : le bouton GEV MIC du dock permet de parler à la carte.",
"Malformed cyclone snapshot": "Instantané cyclonique mal formé",
"Cyclone request timed out": "Délai dépassé pour la requête cyclonique"
"Cyclone request timed out": "Délai dépassé pour la requête cyclonique",
"scene|PLAY": "LIRE",
"AUTHORING": "CRÉATION",
"Create and edit scenes": "Créer et modifier des scènes",
"SCENE": "SCÈNE",
"Scene": "Scène",
"NEW SCENE": "NOUVELLE SCÈNE",
"Start a new, empty scene": "Commencer une nouvelle scène vide",
"DELETE": "SUPPRIMER",
"Delete this scene and all of its shots": "Supprimer cette scène et tous ses plans",
"SHOTS": "PLANS",
"+ CAPTURE SHOT": "+ CAPTURER UN PLAN",
"Add the current view, style and layers as the next shot": "Ajouter la vue, le style et les couches actuels comme plan suivant",
"Replace the selected shot with the current view": "Remplacer le plan sélectionné par la vue actuelle",
"Click a shot to select it, double-click to rename.": "Cliquez sur un plan pour le sélectionner, double-cliquez pour le renommer.",
"Play from this scene through the rest of the project": "Lire de cette scène jusqu'à la fin du projet",
"Stop playback (Esc)": "Arrêter la lecture (Esc)",
"Play the shot after the selected one, moving on to the next scene when needed": "Lire le plan qui suit la sélection, en passant à la scène suivante si besoin",
"SAVE & SHARE": "ENREGISTRER ET PARTAGER",
"Download every scene as a JSON file you can import later": "Télécharger toutes les scènes dans un fichier JSON à importer plus tard",
"Load scenes from an exported JSON file": "Charger des scènes depuis un fichier JSON exporté",
"Download the timing log of the last play": "Télécharger le journal de minutage de la dernière lecture",
"Edit anchors, data packs and shot details": "Modifier les ancres, les paquets de données et les détails des plans",
"Download this scene as JSON or an asset bundle": "Télécharger cette scène en JSON ou en archive de ressources"
}
+24 -7
View File
@@ -2959,13 +2959,7 @@
"ALL": "TUTTE",
"FLAT": "PIATTE",
"SINCE": "DAL",
"SCENES": "SCENE",
"Scene recipe": "Ricetta della scena",
"NEW": "NUOVA",
"CAPTURE SHOT": "CATTURA SCATTO",
"UPDATE SHOT": "AGGIORNA SCATTO",
"START": "AVVIA",
"EXPORT PRESETS": "ESPORTA PRESET",
"IMPORT": "IMPORTA",
"RUN LOG": "LOG ESECUZIONE",
"POWER UP": "ACCENDI",
@@ -2996,5 +2990,28 @@
"ESC to dismiss": "ESC per chiudere",
"Tip: the GEV MIC button in the dock lets you talk to the map.": "Suggerimento: il pulsante GEV MIC nel dock ti permette di parlare con la mappa.",
"Malformed cyclone snapshot": "Snapshot dei cicloni non valido",
"Cyclone request timed out": "La richiesta dei cicloni è andata in timeout"
"Cyclone request timed out": "La richiesta dei cicloni è andata in timeout",
"scene|PLAY": "AVVIA",
"AUTHORING": "CREAZIONE",
"Create and edit scenes": "Crea e modifica le scene",
"SCENE": "SCENA",
"Scene": "Scena",
"NEW SCENE": "NUOVA SCENA",
"Start a new, empty scene": "Inizia una nuova scena vuota",
"DELETE": "ELIMINA",
"Delete this scene and all of its shots": "Elimina questa scena e tutti i suoi scatti",
"SHOTS": "SCATTI",
"+ CAPTURE SHOT": "+ CATTURA SCATTO",
"Add the current view, style and layers as the next shot": "Aggiungi la vista, lo stile e i livelli attuali come scatto successivo",
"Replace the selected shot with the current view": "Sostituisci lo scatto selezionato con la vista attuale",
"Click a shot to select it, double-click to rename.": "Fai clic su uno scatto per selezionarlo, doppio clic per rinominarlo.",
"Play from this scene through the rest of the project": "Riproduci da questa scena fino alla fine del progetto",
"Stop playback (Esc)": "Interrompi la riproduzione (Esc)",
"Play the shot after the selected one, moving on to the next scene when needed": "Riproduci lo scatto dopo quello selezionato, passando alla scena successiva se serve",
"SAVE & SHARE": "SALVA E CONDIVIDI",
"Download every scene as a JSON file you can import later": "Scarica tutte le scene in un file JSON da importare in seguito",
"Load scenes from an exported JSON file": "Carica le scene da un file JSON esportato",
"Download the timing log of the last play": "Scarica il log dei tempi dell'ultima riproduzione",
"Edit anchors, data packs and shot details": "Modifica ancore, pacchetti dati e dettagli degli scatti",
"Download this scene as JSON or an asset bundle": "Scarica questa scena come JSON o pacchetto di risorse"
}
+24 -7
View File
@@ -2817,13 +2817,7 @@
"ALL": "すべて",
"FLAT": "平面",
"SINCE": "期間",
"SCENES": "シーン",
"Scene recipe": "シーンレシピ",
"NEW": "新規",
"CAPTURE SHOT": "ショット取得",
"UPDATE SHOT": "ショット更新",
"START": "開始",
"EXPORT PRESETS": "エクスポート",
"IMPORT": "インポート",
"RUN LOG": "実行ログ",
"POWER UP": "パワーアップ",
@@ -2856,5 +2850,28 @@
"routing busy — too many routes at once": "ルート検索サービスが混雑しています。同時に要求されたルートが多すぎます",
"route proxy error": "ルート検索プロキシでエラーが発生しました",
"Malformed cyclone snapshot": "サイクロンのスナップショットの形式が不正です",
"Cyclone request timed out": "サイクロンのリクエストがタイムアウトしました"
"Cyclone request timed out": "サイクロンのリクエストがタイムアウトしました",
"scene|PLAY": "再生",
"AUTHORING": "作成",
"Create and edit scenes": "シーンを作成・編集",
"SCENE": "シーン",
"Scene": "シーン",
"NEW SCENE": "新規シーン",
"Start a new, empty scene": "空の新しいシーンを作成",
"DELETE": "削除",
"Delete this scene and all of its shots": "このシーンとすべてのショットを削除",
"SHOTS": "ショット",
"+ CAPTURE SHOT": "+ ショット取得",
"Add the current view, style and layers as the next shot": "現在の視点・スタイル・レイヤーを次のショットとして追加",
"Replace the selected shot with the current view": "選択中のショットを現在の視点で置き換え",
"Click a shot to select it, double-click to rename.": "ショットをクリックして選択、ダブルクリックで名前を変更します。",
"Play from this scene through the rest of the project": "このシーンからプロジェクトの最後まで再生",
"Stop playback (Esc)": "再生を停止(Esc)",
"Play the shot after the selected one, moving on to the next scene when needed": "選択中の次のショットを再生(必要に応じて次のシーンへ進む)",
"SAVE & SHARE": "保存と共有",
"Download every scene as a JSON file you can import later": "すべてのシーンを、後でインポートできるJSONファイルとしてダウンロード",
"Load scenes from an exported JSON file": "書き出したJSONファイルからシーンを読み込む",
"Download the timing log of the last play": "前回の再生のタイミングログをダウンロード",
"Edit anchors, data packs and shot details": "アンカー、データパック、ショットの詳細を編集",
"Download this scene as JSON or an asset bundle": "このシーンをJSONまたはアセットバンドルとしてダウンロード"
}
+24 -7
View File
@@ -2961,13 +2961,7 @@
"ALL": "TODAS",
"FLAT": "PLANAS",
"SINCE": "DESDE",
"SCENES": "CENAS",
"Scene recipe": "Receita de cena",
"NEW": "NOVA",
"CAPTURE SHOT": "CAPTURAR PLANO",
"UPDATE SHOT": "ATUALIZAR PLANO",
"START": "INICIAR",
"EXPORT PRESETS": "EXPORTAR PREDEF.",
"IMPORT": "IMPORTAR",
"RUN LOG": "REGISTRO",
"POWER UP": "ATIVAR",
@@ -2996,5 +2990,28 @@
"Begin with a clean globe": "Comece com um globo limpo",
"Don't show this again": "Não mostrar novamente",
"ESC to dismiss": "ESC para fechar",
"Tip: the GEV MIC button in the dock lets you talk to the map.": "Dica: o botão GEV MIC no dock permite falar com o mapa."
"Tip: the GEV MIC button in the dock lets you talk to the map.": "Dica: o botão GEV MIC no dock permite falar com o mapa.",
"scene|PLAY": "INICIAR",
"AUTHORING": "CRIAÇÃO",
"Create and edit scenes": "Criar e editar cenas",
"SCENE": "CENA",
"Scene": "Cena",
"NEW SCENE": "NOVA CENA",
"Start a new, empty scene": "Começar uma nova cena vazia",
"DELETE": "EXCLUIR",
"Delete this scene and all of its shots": "Excluir esta cena e todos os seus planos",
"SHOTS": "PLANOS",
"+ CAPTURE SHOT": "+ CAPTURAR PLANO",
"Add the current view, style and layers as the next shot": "Adicionar a vista, o estilo e as camadas atuais como próximo plano",
"Replace the selected shot with the current view": "Substituir o plano selecionado pela vista atual",
"Click a shot to select it, double-click to rename.": "Clique em um plano para selecioná-lo e clique duas vezes para renomeá-lo.",
"Play from this scene through the rest of the project": "Reproduzir desta cena até o fim do projeto",
"Stop playback (Esc)": "Parar a reprodução (Esc)",
"Play the shot after the selected one, moving on to the next scene when needed": "Reproduzir o plano após o selecionado, passando à próxima cena se necessário",
"SAVE & SHARE": "SALVAR E COMPARTILHAR",
"Download every scene as a JSON file you can import later": "Baixar todas as cenas em um arquivo JSON para importar depois",
"Load scenes from an exported JSON file": "Carregar cenas de um arquivo JSON exportado",
"Download the timing log of the last play": "Baixar o registro de tempos da última reprodução",
"Edit anchors, data packs and shot details": "Editar âncoras, pacotes de dados e detalhes dos planos",
"Download this scene as JSON or an asset bundle": "Baixar esta cena como JSON ou pacote de recursos"
}
+24 -7
View File
@@ -795,21 +795,15 @@
"East offset from catalog position (m) — click to type": "Смещение на восток от позиции каталога (м) — нажмите для ввода",
"SAVE CAL": "СОХРАНИТЬ КАЛИБР.",
"RESET CAL": "СБРОСИТЬ КАЛИБР.",
"NEW": "НОВАЯ",
"DEL": "УДАЛ.",
"CAPTURE SHOT": "СДЕЛАТЬ КАДР",
"UPDATE SHOT": "ОБНОВИТЬ КАДР",
"START": "СТАРТ",
"EXPORT PRESETS": "ЭКСПОРТ ПРЕСЕТОВ",
"IMPORT": "ИМПОРТ",
"RUN LOG": "ЖУРНАЛ ЗАПУСКОВ",
"CALIBRATION": "КАЛИБРОВКА",
"Camera pose — click a value to type": "Поза камеры — щёлкните по значению, чтобы ввести",
"SCENE SUMMARY": "СВОДКА ПО СЦЕНЕ",
"CCTV camera": "Камера CCTV",
"Scene recipe": "Рецепт сцены",
"DATA LAYERS": "СЛОИ ДАННЫХ",
"SCENES": "СЦЕНЫ",
"Close key setup": "Закрыть настройку ключей",
"SAVE KEYS": "СОХРАНИТЬ КЛЮЧИ",
"Power up the globe": "Подключите потоки данных",
@@ -3066,5 +3060,28 @@
"Preview is off.": "Предпросмотр отключён.",
"Automatic stop is unavailable for this voice connection.": "Автоматическая остановка недоступна для этого голосового подключения.",
"routing busy — too many routes at once": "сервис маршрутов занят: слишком много маршрутов одновременно",
"route proxy error": "ошибка прокси маршрутов"
"route proxy error": "ошибка прокси маршрутов",
"AUTHORING": "РЕДАКТОР",
"Create and edit scenes": "Создание и редактирование сцен",
"SCENE": "СЦЕНА",
"Scene": "Сцена",
"NEW SCENE": "НОВАЯ СЦЕНА",
"Start a new, empty scene": "Создать новую пустую сцену",
"DELETE": "УДАЛИТЬ",
"Delete this scene and all of its shots": "Удалить эту сцену и все её кадры",
"SHOTS": "КАДРЫ",
"+ CAPTURE SHOT": "+ СДЕЛАТЬ КАДР",
"Add the current view, style and layers as the next shot": "Добавить текущий вид, стиль и слои следующим кадром",
"Replace the selected shot with the current view": "Заменить выбранный кадр текущим видом",
"Click a shot to select it, double-click to rename.": "Щёлкните кадр, чтобы выбрать его, дважды — чтобы переименовать.",
"scene|PLAY": "ПУСК",
"Stop playback (Esc)": "Остановить воспроизведение (Esc)",
"SAVE & SHARE": "СОХРАНИТЬ И ПОДЕЛИТЬСЯ",
"Download every scene as a JSON file you can import later": "Скачать все сцены файлом JSON для последующего импорта",
"Load scenes from an exported JSON file": "Загрузить сцены из экспортированного файла JSON",
"Download the timing log of the last play": "Скачать журнал таймингов последнего воспроизведения",
"Play from this scene through the rest of the project": "Воспроизвести с этой сцены и далее по всему проекту",
"Play the shot after the selected one, moving on to the next scene when needed": "Воспроизвести кадр после выбранного, при необходимости перейдя к следующей сцене",
"Edit anchors, data packs and shot details": "Изменить якоря, пакеты данных и параметры кадров",
"Download this scene as JSON or an asset bundle": "Скачать эту сцену в формате JSON или архивом ресурсов"
}
+24 -7
View File
@@ -3029,13 +3029,7 @@
"ALL": "УСІ",
"FLAT": "ПЛОСКІ",
"SINCE": "З ДАТИ",
"SCENES": "СЦЕНИ",
"Scene recipe": "Рецепт сцени",
"NEW": "НОВА",
"CAPTURE SHOT": "ЗНЯТИ КАДР",
"UPDATE SHOT": "ОНОВИТИ КАДР",
"START": "СТАРТ",
"EXPORT PRESETS": "ЕКСПОРТУВАТИ ПРЕСЕТИ",
"IMPORT": "ІМПОРТУВАТИ",
"RUN LOG": "ЖУРНАЛ ВІДТВОРЕННЯ",
"POWER UP": "ЗАПУСК",
@@ -3066,5 +3060,28 @@
"ESC to dismiss": "ESC — приховати",
"Tip: the GEV MIC button in the dock lets you talk to the map.": "Порада: кнопка МІК у доку дає змогу говорити з картою.",
"Malformed cyclone snapshot": "Некоректний знімок циклонів",
"Cyclone request timed out": "Минув час очікування запиту циклонів"
"Cyclone request timed out": "Минув час очікування запиту циклонів",
"scene|PLAY": "СТАРТ",
"AUTHORING": "РЕДАКТОР",
"Create and edit scenes": "Створення й редагування сцен",
"SCENE": "СЦЕНА",
"Scene": "Сцена",
"NEW SCENE": "НОВА СЦЕНА",
"Start a new, empty scene": "Створити нову порожню сцену",
"DELETE": "ВИДАЛИТИ",
"Delete this scene and all of its shots": "Видалити цю сцену з усіма кадрами",
"SHOTS": "КАДРИ",
"+ CAPTURE SHOT": "+ ЗНЯТИ КАДР",
"Add the current view, style and layers as the next shot": "Додати поточний вигляд, стиль і шари як наступний кадр",
"Replace the selected shot with the current view": "Замінити вибраний кадр поточним виглядом",
"Click a shot to select it, double-click to rename.": "Натисніть кадр, щоб вибрати його, двічі — щоб перейменувати.",
"Play from this scene through the rest of the project": "Відтворити від цієї сцени до кінця проєкту",
"Stop playback (Esc)": "Зупинити відтворення (Esc)",
"Play the shot after the selected one, moving on to the next scene when needed": "Відтворити кадр після вибраного, за потреби перейшовши до наступної сцени",
"SAVE & SHARE": "ЗБЕРЕГТИ Й ПОДІЛИТИСЯ",
"Download every scene as a JSON file you can import later": "Завантажити всі сцени файлом JSON для подальшого імпорту",
"Load scenes from an exported JSON file": "Завантажити сцени з експортованого файлу JSON",
"Download the timing log of the last play": "Завантажити журнал таймінгів останнього відтворення",
"Edit anchors, data packs and shot details": "Змінити якорі, пакети даних і параметри кадрів",
"Download this scene as JSON or an asset bundle": "Завантажити цю сцену у форматі JSON або архівом ресурсів"
}
+24 -7
View File
@@ -2712,13 +2712,7 @@
"ALL": "全部",
"FLAT": "平面",
"SINCE": "起始时间",
"SCENES": "场景",
"Scene recipe": "场景方案",
"NEW": "新建",
"CAPTURE SHOT": "保存镜头",
"UPDATE SHOT": "更新镜头",
"START": "启动",
"EXPORT PRESETS": "导出预设",
"IMPORT": "导入",
"RUN LOG": "运行日志",
"POWER UP": "增强",
@@ -2856,5 +2850,28 @@
},
"S2 Underground publishes these KMZ products to GitHub. New uploads are detected within seconds and replace the previous edition automatically. Click a marker for its report.": "S2 Underground 在 GitHub 发布这些 KMZ 产品。新上传的内容会在数秒内被检测到,并自动替换旧版本。点击标记查看报告。",
"routing busy — too many routes at once": "路线服务繁忙:同时请求的路线过多",
"route proxy error": "路线代理出错"
"route proxy error": "路线代理出错",
"AUTHORING": "创作",
"Create and edit scenes": "创建和编辑场景",
"SCENE": "场景",
"Scene": "场景",
"NEW SCENE": "新建场景",
"Start a new, empty scene": "新建一个空场景",
"DELETE": "删除",
"Delete this scene and all of its shots": "删除此场景及其所有镜头",
"SHOTS": "镜头",
"+ CAPTURE SHOT": "+ 保存镜头",
"Add the current view, style and layers as the next shot": "将当前视角、样式和图层添加为下一个镜头",
"Replace the selected shot with the current view": "用当前视角替换所选镜头",
"Click a shot to select it, double-click to rename.": "单击镜头即可选中,双击可重命名。",
"scene|PLAY": "播放",
"Stop playback (Esc)": "停止播放(Esc)",
"SAVE & SHARE": "保存与分享",
"Download every scene as a JSON file you can import later": "将所有场景下载为 JSON 文件,以便日后导入",
"Load scenes from an exported JSON file": "从导出的 JSON 文件加载场景",
"Download the timing log of the last play": "下载上次播放的计时日志",
"Play from this scene through the rest of the project": "从此场景开始播放项目中的其余场景",
"Play the shot after the selected one, moving on to the next scene when needed": "播放所选镜头的下一个镜头,必要时进入下一个场景",
"Edit anchors, data packs and shot details": "编辑锚点、数据包和镜头详情",
"Download this scene as JSON or an asset bundle": "将此场景下载为 JSON 或资源包"
}
+24 -7
View File
@@ -2700,13 +2700,7 @@
"ALL": "全部",
"FLAT": "平面",
"SINCE": "起始時間",
"SCENES": "場景",
"Scene recipe": "場景方案",
"NEW": "新建",
"CAPTURE SHOT": "保存鏡頭",
"UPDATE SHOT": "更新鏡頭",
"START": "啟動",
"EXPORT PRESETS": "導出預設",
"IMPORT": "導入",
"RUN LOG": "運行日誌",
"POWER UP": "增強",
@@ -2856,5 +2850,28 @@
},
"S2 Underground publishes these KMZ products to GitHub. New uploads are detected within seconds and replace the previous edition automatically. Click a marker for its report.": "S2 Underground 在 GitHub 發布這些 KMZ 產品。新上傳的內容會在數秒內被檢測到,並自動替換舊版本。點擊標記查看報告。",
"routing busy — too many routes at once": "路線服務繁忙:同時請求的路線過多",
"route proxy error": "路線代理出錯"
"route proxy error": "路線代理出錯",
"AUTHORING": "創作",
"Create and edit scenes": "創建和編輯場景",
"SCENE": "場景",
"Scene": "場景",
"NEW SCENE": "新建場景",
"Start a new, empty scene": "新建一個空場景",
"DELETE": "刪除",
"Delete this scene and all of its shots": "刪除此場景及其所有鏡頭",
"SHOTS": "鏡頭",
"+ CAPTURE SHOT": "+ 保存鏡頭",
"Add the current view, style and layers as the next shot": "將當前視角、樣式和圖層添加為下一個鏡頭",
"Replace the selected shot with the current view": "用當前視角替換所選鏡頭",
"Click a shot to select it, double-click to rename.": "單擊鏡頭即可選中,雙擊可重命名。",
"scene|PLAY": "播放",
"Stop playback (Esc)": "停止播放(Esc)",
"SAVE & SHARE": "保存與分享",
"Download every scene as a JSON file you can import later": "將所有場景下載為 JSON 文件,以便日後導入",
"Load scenes from an exported JSON file": "從導出的 JSON 文件加載場景",
"Download the timing log of the last play": "下載上次播放的計時日誌",
"Play from this scene through the rest of the project": "從此場景開始播放項目中的其餘場景",
"Play the shot after the selected one, moving on to the next scene when needed": "播放所選鏡頭的下一個鏡頭,必要時進入下一個場景",
"Edit anchors, data packs and shot details": "編輯錨點、數據包和鏡頭詳情",
"Download this scene as JSON or an asset bundle": "將此場景下載為 JSON 或資源包"
}
+38 -8
View File
@@ -21,6 +21,9 @@ export class SceneControls {
this.rowRemovers = [];
this.playbackKeyRemover = null;
if (!elements.select) return;
// The radio's PLAY means "listen"; this one plays the scene.
const playLabel = t('PLAY', { context: 'scene' });
if (elements.start) elements.start.textContent = playLabel;
if (subscribe)
this.unsubscribe = subscribe((notification) =>
this.present(notification),
@@ -94,7 +97,21 @@ export class SceneControls {
this.renderShotList();
if (type === 'selection-changed')
presentSceneSelection(this.elements.shots, state.selectedShotId);
if (initial || type === 'buttons-changed') this.setButtons(state.running);
if (
initial ||
[
'buttons-changed',
'selection-changed',
'scene-options-changed',
'scene-created',
'scene-deleted',
'shots-changed',
'shot-captured',
'shot-deleted',
'project-imported',
].includes(type)
)
this.setButtons(state.running);
if (initial || type === 'progress-changed')
this.setProgress(state.progress);
if (initial || type === 'status-changed') this.updateStatus(state.status);
@@ -183,11 +200,9 @@ export class SceneControls {
this.listen(element, type, callback, this.rowRemovers),
select: (id) => {
this.run('selectShot', id);
if (!this.destroyed)
presentSceneSelection(
this.elements.shots,
this.read().selectedShotId,
);
if (this.destroyed) return;
presentSceneSelection(this.elements.shots, this.read().selectedShotId);
this.setButtons(this.read().running);
},
rename: (sceneId, shotId, title) =>
this.run('renameShot', sceneId, shotId, title),
@@ -197,8 +212,22 @@ export class SceneControls {
}
setButtons(running) {
if (!this.destroyed)
presentSceneButtons(this.elements, running, this.read().hasRun);
if (this.destroyed) return;
const state = this.read();
const scene = state.scenes?.find(
(item) => item.id === state.selectedSceneId,
);
const shots = scene?.shots ?? [];
presentSceneButtons(this.elements, running, state.hasRun, {
hasScene: !!scene,
// PLAY and NEXT run on into the following scenes.
playableShots: (state.scenes ?? []).reduce(
(total, item) => total + (item.shots?.length ?? 0),
0,
),
hasSelectedShot: shots.some((shot) => shot.id === state.selectedShotId),
});
this.elements.panel?.classList.toggle('has-shots', shots.length > 0);
}
setProgress(progress) {
@@ -247,6 +276,7 @@ export class SceneControls {
])
remove();
if (this.elements.shots) this.elements.shots.textContent = '';
this.elements.panel?.classList.toggle('has-shots', false);
presentSceneRuntime(this.elements.runtime, '');
this.setPlaybackActive(false);
}
+181
View File
@@ -3,6 +3,8 @@ import assert from 'node:assert/strict';
import { createStateChannel } from '../app/stateChannel.js';
import { SceneControls } from './sceneControls.js';
import { SceneDirector } from '../scenes/director.js';
import { mountSceneSharing } from './sceneSharing.js';
import { setLocale } from '../i18n/index.js';
function fixture() {
class Element extends EventTarget {
@@ -169,6 +171,46 @@ test('Scene controls render the supplied selection and running/download states',
}
});
test('Authoring disables idle actions that would do nothing', () => {
const f = fixture();
try {
assert.equal(f.elements.start.textContent, 'PLAY');
assert.equal(f.elements.start.disabled, false);
assert.equal(f.elements.update.disabled, false);
assert.equal(f.elements.panel.classList.contains('has-shots'), true);
f.state.selectedShotId = null;
f.owner.setButtons(false);
assert.equal(f.elements.update.disabled, true);
assert.equal(f.elements.start.disabled, false);
// PLAY and NEXT run on into the following scenes, so an empty selected
// scene beside one with shots keeps them available.
f.state.scenes[0].shots = [];
f.state.scenes.push({ id: 'scene-b', title: 'B', shots: [{ id: 'b1' }] });
f.owner.setButtons(false);
assert.equal(f.elements.start.disabled, false);
assert.equal(f.elements.next.disabled, false);
assert.equal(f.elements.update.disabled, true);
f.state.scenes.pop();
f.owner.setButtons(false);
assert.equal(f.elements.start.disabled, true);
assert.equal(f.elements.next.disabled, true);
assert.equal(f.elements.capture.disabled, false);
assert.equal(f.elements.panel.classList.contains('has-shots'), false);
f.state.scenes = [];
f.state.selectedSceneId = null;
f.owner.setButtons(false);
assert.equal(f.elements.delete.disabled, true);
assert.equal(f.elements.capture.disabled, true);
assert.equal(f.elements.new.disabled, false);
} finally {
f.restore();
}
});
test('Scene controls dispatch explicit actions and revoke replaced shot-row listeners', () => {
const f = fixture();
try {
@@ -435,3 +477,142 @@ test('Scene controls consume current state, preserve rows on progress, and unsub
f.restore();
}
});
test('Authoring refreshes idle buttons from published changes', () => {
const f = fixture();
try {
f.owner.destroy();
const channel = createStateChannel(() => f.state);
const owner = new SceneControls({
read: () => f.state,
actions: f.actions,
elements: f.elements,
subscribe: (listener) => channel.subscribe(listener),
});
const disabled = (name) => f.elements[name].disabled;
assert.equal(disabled('start'), false);
assert.equal(f.elements.panel.classList.contains('has-shots'), true);
f.state.selectedShotId = null;
channel.publish({ type: 'selection-changed' });
assert.equal(disabled('update'), true);
f.state.scenes[0].shots = [];
channel.publish({ type: 'shot-deleted' });
assert.equal(disabled('start'), true);
assert.equal(disabled('next'), true);
assert.equal(f.elements.panel.classList.contains('has-shots'), false);
// A run that ends hands the buttons back with the idle rules applied.
f.state.running = true;
channel.publish({ type: 'buttons-changed' });
assert.equal(disabled('capture'), true);
assert.equal(disabled('stop'), false);
f.state.running = false;
channel.publish({ type: 'buttons-changed' });
assert.equal(disabled('capture'), false);
assert.equal(disabled('start'), true);
f.state.scenes = [];
f.state.selectedSceneId = null;
channel.publish({ type: 'scene-deleted' });
assert.equal(disabled('delete'), true);
assert.equal(disabled('capture'), true);
assert.equal(disabled('new'), false);
f.state.scenes = [{ id: 'scene-b', title: 'B', shots: [{ id: 's' }] }];
f.state.selectedSceneId = 'scene-b';
f.state.selectedShotId = 's';
channel.publish({ type: 'project-imported' });
assert.equal(disabled('start'), false);
assert.equal(f.elements.panel.classList.contains('has-shots'), true);
owner.destroy();
assert.equal(f.elements.panel.classList.contains('has-shots'), false);
channel.destroy();
} finally {
f.restore();
}
});
test('Authoring PLAY uses the scene context, not the radio PLAY', (context) => {
context.after(() => setLocale('en'));
setLocale('es');
const f = fixture();
try {
assert.equal(f.elements.start.textContent, 'REPROD.');
} finally {
f.restore();
}
});
test('The sharing row sits under SAVE & SHARE, after the file row', () => {
const saved = globalThis.document;
const node = (className = '') => {
const element = {
className,
children: [],
dataset: {},
parent: null,
addEventListener() {},
removeEventListener() {},
append(...items) {
for (const item of items) {
item.parent = element;
element.children.push(item);
}
},
appendChild(item) {
element.append(item);
},
after(item) {
const siblings = element.parent.children;
item.parent = element.parent;
siblings.splice(siblings.indexOf(element) + 1, 0, item);
},
remove() {
const siblings = element.parent?.children;
if (siblings) siblings.splice(siblings.indexOf(element), 1);
},
querySelector(selector) {
const name = selector.slice(1);
const walk = (at) => {
for (const child of at.children) {
if (child.className.split(' ').includes(name)) return child;
const found = walk(child);
if (found) return found;
}
return null;
};
return walk(element);
},
};
return element;
};
globalThis.document = { createElement: () => node() };
try {
const panel = node();
const inner = node('scene-panel-inner');
const fileRow = node('scene-controls scene-file-controls');
const status = node('scene-status');
panel.append(inner);
inner.append(fileRow, status);
const dispose = mountSceneSharing({ edit() {}, share() {} }, panel);
const bar = inner.children[1];
assert.equal(bar.className, 'scene-controls');
assert.equal(inner.children[2], status);
assert.ok(bar.children.every((button) => button.title));
dispose();
assert.deepEqual(inner.children, [fileRow, status]);
// Without the file row, the bar still lands inside the panel.
const plain = node();
const plainInner = node('scene-panel-inner');
plain.append(plainInner);
mountSceneSharing({ edit() {}, share() {} }, plain)();
mountSceneSharing({ edit() {}, share() {} }, plain);
assert.equal(plainInner.children.length, 1);
} finally {
globalThis.document = saved;
}
});
+21 -2
View File
@@ -171,7 +171,26 @@ export function presentSceneSelection(element, selectedShotId) {
}
}
export function presentSceneButtons(elements, running, hasRun) {
/**
* While a scene plays, the authoring and file actions here are off and STOP
* is on. Otherwise a button is off when it would do nothing: DELETE and
* CAPTURE SHOT without a scene, UPDATE SHOT without a selected shot, PLAY and
* NEXT when no scene in the project has a shot (both continue into the
* following scenes).
*/
export function presentSceneButtons(
elements,
running,
hasRun,
{ hasScene = true, playableShots = 1, hasSelectedShot = true } = {},
) {
const idleOff = {
delete: !hasScene,
capture: !hasScene,
update: !hasSelectedShot,
start: playableShots === 0,
next: playableShots === 0,
};
for (const name of [
'start',
'next',
@@ -183,7 +202,7 @@ export function presentSceneButtons(elements, running, hasRun) {
'export',
'import',
]) {
if (elements[name]) elements[name].disabled = running;
if (elements[name]) elements[name].disabled = running || !!idleOff[name];
}
if (elements.stop) elements.stop.disabled = !running;
if (elements.download) elements.download.disabled = !hasRun;
+16 -5
View File
@@ -181,17 +181,28 @@ export function mountSceneSharing(
bar.dataset.directorAuthoring = '';
bar.className = 'scene-controls';
const entries = [
[t('EDIT DETAILS'), edit],
[t('SHARE SCENE'), share],
[t('EDIT DETAILS'), t('Edit anchors, data packs and shot details'), edit],
[
t('SHARE SCENE'),
t('Download this scene as JSON or an asset bundle'),
share,
],
];
const removers = [];
for (const [text, fn] of entries) {
const b = createActionButton({ label: text, className: 'scene-btn' });
for (const [text, title, fn] of entries) {
const b = createActionButton({
label: text,
title,
className: 'scene-btn',
});
b.addEventListener('click', fn);
removers.push(() => b.removeEventListener('click', fn));
bar.append(b);
}
(panel.querySelector?.('.scene-panel-inner') || panel).appendChild(bar);
// Beside EXPORT / IMPORT, under SAVE & SHARE.
const fileRow = panel.querySelector?.('.scene-file-controls');
if (fileRow) fileRow.after(bar);
else (panel.querySelector?.('.scene-panel-inner') || panel).appendChild(bar);
return () => {
for (const remove of removers) remove();
bar.remove();
+122 -24
View File
@@ -1,6 +1,8 @@
/* ── Scene Director Panel ───────────────────── */
#scene-panel {
--panel-expanded-width: 360px;
/* Secondary text in Authoring: dimmer than labels, but legible on the globe. */
--scene-quiet: rgba(232, 234, 237, 0.62);
position: fixed;
top: calc(var(--left-stack-top) + (var(--left-stack-gap) * 2));
left: var(--left-stack-x);
@@ -150,15 +152,15 @@
flex-wrap: wrap;
}
/* The scene's name takes its own row; NEW SCENE / DELETE share the next. */
#scene-select {
flex: 1;
flex: 1 1 100%;
min-width: 0;
min-height: 32px;
font-size: 12px;
}
/* A translated scene name needs the width: the select takes its own row,
NEW / DEL share the next one. */
html:not([lang='en']) #scene-select {
flex: 1 1 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -168,8 +170,7 @@ html:not([lang='en']) #scene-select {
}
/* A translated action longer than its button wraps inside the button instead
of running past it. English keeps its one-line buttons ("EXPORT PRESETS"
included). */
of running past it. English keeps its one-line buttons. */
html:not([lang='en']) #scene-panel .scene-controls .scene-btn {
white-space: normal;
overflow-wrap: break-word;
@@ -180,6 +181,70 @@ html:not([lang='en']) #scene-panel .scene-controls .scene-btn {
min-width: 72px;
}
#scene-panel .scene-btn {
font-size: 10px;
padding: 8px 10px;
}
/* Authoring's steps: SCENE, SHOTS, PREVIEW, SAVE & SHARE. */
.scene-step-heading {
display: flex;
align-items: center;
gap: 8px;
margin: 10px 0 6px;
font-family: var(--font-mono);
font-size: 10px;
letter-spacing: 1.6px;
color: var(--scene-quiet);
}
.scene-step-heading::after {
content: '';
flex: 1;
height: 1px;
background: rgba(255, 255, 255, 0.08);
}
.panel-header + .scene-step-heading {
margin-top: 2px;
}
/* One primary action per step: CAPTURE SHOT and PLAY. The panel id keeps
this above Cyber's shared button surface. */
#scene-panel .scene-btn-primary:not(:disabled) {
color: var(--accent);
border-color: color-mix(in srgb, var(--accent) 55%, transparent);
background: color-mix(in srgb, var(--accent) 10%, transparent);
}
/* The file row is housekeeping; it stays quieter than the steps above. */
#scene-panel .scene-file-controls .scene-btn {
font-size: 9px;
/* Room for one long translated word (IMPORTIEREN, ІМПОРТУВАТИ) per line. */
padding: 8px 4px;
letter-spacing: 0.6px;
background: transparent;
}
/* Keep the shared hover on the restyled actions. */
#scene-panel .scene-btn-primary:hover:not(:disabled),
#scene-panel .scene-file-controls .scene-btn:hover:not(:disabled) {
color: var(--accent);
border-color: var(--accent);
background: color-mix(in srgb, var(--accent) 16%, transparent);
}
.scene-shot-hint {
margin: 0 0 8px;
font-size: 11px;
line-height: 1.4;
color: var(--scene-quiet);
}
#scene-panel:not(.has-shots) .scene-shot-hint {
display: none;
}
#scene-start-btn,
#scene-stop-btn,
#scene-next-btn {
@@ -190,12 +255,14 @@ html:not([lang='en']) #scene-panel .scene-controls .scene-btn {
display: flex;
flex-direction: column;
gap: 6px;
max-height: 160px;
min-height: 96px;
max-height: 260px;
/* Two shot rows stay visible; the panel scrolls when space is short. */
min-height: 128px;
flex: 1;
overflow-y: auto;
padding: 4px 2px 6px;
margin-bottom: 8px;
margin-bottom: 4px;
counter-reset: scene-shot;
}
.scene-shot-list::-webkit-scrollbar {
@@ -210,19 +277,21 @@ html:not([lang='en']) #scene-panel .scene-controls .scene-btn {
.scene-shot-empty {
border: 1px dashed rgba(255, 255, 255, 0.14);
border-radius: 8px;
padding: 10px 8px;
padding: 16px 12px;
text-align: center;
font-family: var(--font-mono);
font-size: 9px;
letter-spacing: 1px;
color: var(--text-dim);
font-size: 11px;
letter-spacing: 0.3px;
line-height: 1.5;
color: var(--scene-quiet);
}
.scene-shot-row {
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 9px;
padding: 7px 8px;
padding: 8px 10px;
background: rgba(255, 255, 255, 0.03);
counter-increment: scene-shot;
transition:
border-color var(--transition-fast),
background var(--transition-fast);
@@ -243,12 +312,38 @@ html:not([lang='en']) #scene-panel .scene-controls .scene-btn {
.scene-shot-label {
min-width: 0;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
cursor: pointer;
font-family: var(--font-mono);
font-size: 10px;
letter-spacing: 1px;
font-size: 12px;
letter-spacing: 0.4px;
color: var(--text-primary);
}
/* While renaming, the label holds only the editor at full width. */
.scene-shot-label:has(.scene-shot-rename) {
overflow: visible;
white-space: normal;
}
.scene-shot-label:has(.scene-shot-rename)::before {
content: none;
}
/* Shots are numbered 01, 02… in playback order. */
.scene-shot-label::before {
content: counter(scene-shot, decimal-leading-zero);
margin-right: 8px;
font-size: 10px;
color: var(--scene-quiet);
}
.scene-shot-row.active .scene-shot-label::before {
color: var(--accent);
}
.scene-shot-rename {
width: 100%;
min-width: 0;
@@ -260,11 +355,11 @@ html:not([lang='en']) #scene-panel .scene-controls .scene-btn {
}
.scene-shot-meta {
margin-top: 4px;
margin-top: 5px;
font-family: var(--font-mono);
font-size: 8px;
letter-spacing: 0.8px;
color: var(--text-dim);
font-size: 10px;
letter-spacing: 0.4px;
color: var(--scene-quiet);
}
.scene-shot-actions {
@@ -275,7 +370,7 @@ html:not([lang='en']) #scene-panel .scene-controls .scene-btn {
.scene-progress {
margin-top: 8px;
width: 100%;
height: 18px;
height: 14px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.06);
border: 1px solid rgba(255, 255, 255, 0.1);
@@ -305,9 +400,10 @@ html:not([lang='en']) #scene-panel .scene-controls .scene-btn {
#scene-status {
margin-top: 8px;
font-family: var(--font-mono);
font-size: 10px;
letter-spacing: 1px;
color: var(--text-secondary);
font-size: 11px;
letter-spacing: 0.4px;
line-height: 1.4;
color: var(--scene-quiet);
}
#scene-runtime {
@@ -357,6 +453,8 @@ html:not([lang='en']) #scene-panel .scene-controls .scene-btn {
#cctv-panel.collapsed .cctv-summary-label,
#cctv-panel.collapsed #cctv-summary,
#scene-panel.collapsed .scene-controls,
#scene-panel.collapsed .scene-step-heading,
#scene-panel.collapsed .scene-shot-hint,
#scene-panel.collapsed .scene-shot-list,
#scene-panel.collapsed .scene-progress,
#scene-panel.collapsed #scene-status,
+19 -14
View File
@@ -147,35 +147,40 @@
<div class="panel-glow"></div>
<div class="scene-panel-inner">
<div class="panel-header">
<span class="panel-title" data-i18n>SCENES</span>
<span class="panel-title" title="Create and edit scenes" data-i18n data-i18n-title>AUTHORING</span>
<span class="panel-divider"></span>
<button class="panel-collapse-btn" data-collapse-target="scene-panel" title="Collapse panel" data-i18n-title>+</button>
</div>
<p class="scene-step-heading" data-i18n>SCENE</p>
<div class="scene-controls">
<select class="gev-field" id="scene-select" aria-label="Scene recipe" data-i18n-aria-label></select>
<button id="scene-new-btn" type="button" class="gev-action-button scene-btn" data-i18n>NEW</button>
<button id="scene-delete-btn" type="button" class="gev-action-button gev-action-button--danger scene-btn scene-btn-danger" data-i18n>DEL</button>
<select class="gev-field" id="scene-select" aria-label="Scene" data-i18n-aria-label></select>
<button id="scene-new-btn" type="button" class="gev-action-button scene-btn" title="Start a new, empty scene" data-i18n data-i18n-title>NEW SCENE</button>
<button id="scene-delete-btn" type="button" class="gev-action-button gev-action-button--danger scene-btn scene-btn-danger" title="Delete this scene and all of its shots" data-i18n data-i18n-title>DELETE</button>
</div>
<p class="scene-step-heading" data-i18n>SHOTS</p>
<div class="scene-controls">
<button id="scene-capture-btn" type="button" class="gev-action-button scene-btn" data-i18n>CAPTURE SHOT</button>
<button id="scene-update-shot-btn" type="button" class="gev-action-button scene-btn" data-i18n>UPDATE SHOT</button>
<button id="scene-capture-btn" type="button" class="gev-action-button scene-btn scene-btn-primary" title="Add the current view, style and layers as the next shot" data-i18n data-i18n-title>+ CAPTURE SHOT</button>
<button id="scene-update-shot-btn" type="button" class="gev-action-button scene-btn" title="Replace the selected shot with the current view" data-i18n data-i18n-title>UPDATE SHOT</button>
</div>
<div id="scene-shot-list" class="scene-shot-list"></div>
<p class="scene-shot-hint" data-i18n>Click a shot to select it, double-click to rename.</p>
<p class="scene-step-heading" data-i18n>PREVIEW</p>
<div class="scene-controls">
<button id="scene-start-btn" type="button" class="gev-action-button scene-btn" data-i18n>START</button>
<button id="scene-stop-btn" type="button" class="gev-action-button gev-action-button--danger scene-btn scene-btn-danger" data-i18n>STOP</button>
<button id="scene-next-btn" type="button" class="gev-action-button scene-btn" data-i18n>NEXT</button>
<button id="scene-start-btn" type="button" class="gev-action-button scene-btn scene-btn-primary" title="Play from this scene through the rest of the project" data-i18n-title data-i18n-runtime>PLAY</button>
<button id="scene-stop-btn" type="button" class="gev-action-button gev-action-button--danger scene-btn scene-btn-danger" title="Stop playback (Esc)" data-i18n data-i18n-title>STOP</button>
<button id="scene-next-btn" type="button" class="gev-action-button scene-btn" title="Play the shot after the selected one, moving on to the next scene when needed" data-i18n data-i18n-title>NEXT</button>
</div>
<div class="scene-controls">
<button id="scene-export-btn" type="button" class="gev-action-button scene-btn" data-i18n>EXPORT PRESETS</button>
<button id="scene-import-btn" type="button" class="gev-action-button scene-btn" data-i18n>IMPORT</button>
<button id="scene-download-btn" type="button" class="gev-action-button scene-btn" data-i18n>RUN LOG</button>
<p class="scene-step-heading" data-i18n>SAVE &amp; SHARE</p>
<div class="scene-controls scene-file-controls">
<button id="scene-export-btn" type="button" class="gev-action-button scene-btn" title="Download every scene as a JSON file you can import later" data-i18n data-i18n-title>EXPORT</button>
<button id="scene-import-btn" type="button" class="gev-action-button scene-btn" title="Load scenes from an exported JSON file" data-i18n data-i18n-title>IMPORT</button>
<button id="scene-download-btn" type="button" class="gev-action-button scene-btn" title="Download the timing log of the last play" data-i18n data-i18n-title>RUN LOG</button>
<input type="file" id="scene-import-file" accept="application/json,.json" hidden />
</div>
<div class="scene-progress">
<div id="scene-progress-fill">0%</div>
</div>
<div id="scene-status" data-i18n>Ready</div>
<div id="scene-status" role="status" aria-live="polite" data-i18n>Ready</div>
</div>
</div>
+1
View File
@@ -97,6 +97,7 @@ const PANEL_ALIASES = new Map([
['global context', 'global-context-panel'],
['right context', 'global-context-panel'],
['context right panel', 'global-context-panel'],
['authoring', 'scene-panel'],
['scenes', 'scene-panel'],
['scene', 'scene-panel'],
['post processing', 'pp-toggles'],
+23
View File
@@ -3931,3 +3931,26 @@ test('analyst-only lookup distinguishes duplicate names and uses canonical aircr
assert.equal(plane.ok, true);
assert.equal(plane.latitude, 36, 'canonical ID resolves the current position, not old coordinates');
});
test('set_panel_open accepts the Authoring name for the scene panel', async () => {
const calls = [];
const runner = createGevActionRunner({
viewer: {
clock: { onTick: { addEventListener: () => () => {} } },
scene: { canvas: { addEventListener() {}, removeEventListener() {} } },
camera: {
moveEnd: { addEventListener() {} },
positionWC: Cesium.Cartesian3.fromDegrees(-97.7, 30.2, 1000),
},
},
styleManager: {
setPanelCollapsed: (...args) => calls.push(args),
},
dataManager: { layers: new Map(), isEnabled: () => false, getAll: () => [] },
});
for (const panel of ['authoring', 'Authoring', 'scenes']) {
const result = await runner('set_panel_open', { panelId: panel, open: true });
assert.equal(result.panelId, 'scene-panel');
}
assert.deepEqual(calls.at(-1), ['scene-panel', false, { explicit: true }]);
});