From 9d9eb267c53e7d2df3186a6c32953d329647828f Mon Sep 17 00:00:00 2001 From: Sameh Khamis Date: Sat, 31 Oct 2026 18:37:14 -0700 Subject: [PATCH] feat(ui): render the scene sharing dialogs and bar from a store createSceneDialog keeps its builder but publishes the dialog's lines, fields, buttons and status to a scene sharing store; SceneDialog.vue, teleported to the body from the scene panels view, draws the same modal with the same styles and opens it with Cancel focused. The EDIT DETAILS / SHARE SCENE bar is drawn by that view while sharing offers it. The status directive is shared with Provider Settings, and the two QA scripts that drove the old DOM read the new dialog. --- CHANGELOG.md | 4 + docs/APPLICATION.md | 5 +- scripts/package-boundaries.json | 9 +- scripts/qa-director-sharing.mjs | 3 + scripts/qa-scene-dialog-file-drop.mjs | 54 +++-- src/scenes/sharing.js | 124 +++++----- src/scenes/sharingDialogs.test.mjs | 197 +++++++++++++++ src/ui/rail/statusText.js | 14 ++ src/ui/sceneControls.test.mjs | 72 ------ src/ui/sceneSharing.js | 333 ++++++++++++-------------- src/ui/sceneSharingView.test.mjs | 254 ++++++++++++++++++++ src/ui/stores/sceneSharing.js | 36 +++ src/ui/views/LayerPanelsView.vue | 15 ++ src/ui/views/ProviderSettingsView.vue | 13 +- src/ui/views/panels/SceneDialog.vue | 64 +++++ 15 files changed, 842 insertions(+), 355 deletions(-) create mode 100644 src/scenes/sharingDialogs.test.mjs create mode 100644 src/ui/rail/statusText.js create mode 100644 src/ui/sceneSharingView.test.mjs create mode 100644 src/ui/stores/sceneSharing.js create mode 100644 src/ui/views/panels/SceneDialog.vue diff --git a/CHANGELOG.md b/CHANGELOG.md index 91de5887f..a2d4f90ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,10 @@ takes `omitViews` (for example `['provider-settings']`) for views an application leaves out (samehkhamis). +- `./scenes` presents the scene sharing dialogs and the EDIT DETAILS / SHARE + SCENE bar through a store that the scene panels view renders, so a page + that omits `layer-panels` shows neither (samehkhamis). + - `./ui/display`'s `bindDisplayControls` takes the keyhole fade and outside opacity sliders as `fadeSlider` and `outsideOpacitySlider`, and their actions receive whole percentages; `fadeSliders` still works. diff --git a/docs/APPLICATION.md b/docs/APPLICATION.md index 24a7111a9..ceb517133 100644 --- a/docs/APPLICATION.md +++ b/docs/APPLICATION.md @@ -205,8 +205,9 @@ Provider Settings. A page places each Vue view in `src/ui/views` with a host, screen (`#loading-screen` with a `.loader-status` line) as static markup so it paints before any script runs. `createApplication()` renders the views before anything reads the page; `omitViews` names views the application leaves out, -such as `provider-settings`, and their hosts are removed. An unknown view name -is an error. A host that does not initialize Core's locale passes +such as `provider-settings`, and their hosts are removed. Leaving out +`layer-panels` also leaves out the scene sharing dialogs it draws. An unknown +view name is an error. A host that does not initialize Core's locale passes `localePicker: false` to `createApplicationControls` instead of editing the markup. Node tests that import application modules register the component loader with `node --import=/build/register-vue`. diff --git a/scripts/package-boundaries.json b/scripts/package-boundaries.json index ec0d5131b..25e564755 100644 --- a/scripts/package-boundaries.json +++ b/scripts/package-boundaries.json @@ -28,6 +28,7 @@ "src/ui/views/panels/SdrCard.vue", "src/ui/views/panels/RadioTuner.vue", "src/ui/views/panels/LocalePicker.vue", + "src/ui/views/panels/SceneDialog.vue", "src/ui/localePicker.js", "src/ui/views/panels/PhoneTabBar.vue", "src/ui/views/panels/CockpitBriefing.vue", @@ -76,6 +77,7 @@ "src/ui/stores/cockpit.js", "src/ui/stores/locale.js", "src/ui/stores/providerSettings.js", + "src/ui/stores/sceneSharing.js", "src/ui/stores/location.js", "src/ui/stores/streetLevel.js", "src/ui/rail/RailChips.vue", @@ -84,7 +86,8 @@ "src/ui/rail/rangeValue.js", "src/ui/rail/classToggle.js", "src/ui/rail/focusSafeChildren.js", - "src/ui/rail/fieldValue.js" + "src/ui/rail/fieldValue.js", + "src/ui/rail/statusText.js" ], "external": ["vue", "pinia"] }, @@ -1004,6 +1007,7 @@ "src/ui/stores/cockpit.js", "src/ui/stores/locale.js", "src/ui/stores/providerSettings.js", + "src/ui/stores/sceneSharing.js", "src/ui/stores/location.js", "src/ui/stores/display.js" ], @@ -1118,6 +1122,7 @@ "src/director/authoring.js", "src/scenes/sharing.js", "src/ui/sceneSharing.js", + "src/ui/stores/sceneSharing.js", "src/data/cameraArrival.js", "src/i18n/index.js", "src/i18n/locale.js", @@ -2816,6 +2821,7 @@ "src/director/authoring.js", "src/scenes/sharing.js", "src/ui/sceneSharing.js", + "src/ui/stores/sceneSharing.js", "src/layers/weather/imageryOrder.js", "src/layers/weather/imageryHost.js", "src/layers/cyclones/index.js", @@ -2983,6 +2989,7 @@ "src/ui/stores/cockpit.js", "src/ui/stores/locale.js", "src/ui/stores/providerSettings.js", + "src/ui/stores/sceneSharing.js", "src/ui/stores/location.js", "src/ui/stores/display.js" ], diff --git a/scripts/qa-director-sharing.mjs b/scripts/qa-director-sharing.mjs index bfc76ed93..4a4a93d48 100644 --- a/scripts/qa-director-sharing.mjs +++ b/scripts/qa-director-sharing.mjs @@ -427,6 +427,9 @@ try { shot.interactions = []; fields[0].value = JSON.stringify(scene); fields[1].value = JSON.stringify(shot); + // Typing fires input; the dialog reads its fields from those events. + for (const field of fields) + field.dispatchEvent(new Event('input', { bubbles: true })); }); await page.click('[data-director-apply-details]'); await page.waitForFunction( diff --git a/scripts/qa-scene-dialog-file-drop.mjs b/scripts/qa-scene-dialog-file-drop.mjs index 9b48555ad..d6806ff72 100644 --- a/scripts/qa-scene-dialog-file-drop.mjs +++ b/scripts/qa-scene-dialog-file-drop.mjs @@ -4,7 +4,7 @@ * file dropped from the desktop, proved in a real browser. * * The dialog draws a translated row over the native file input - * (src/ui/sceneSharing.js, fileChooser). The input must still cover the row, + * (src/ui/views/panels/SceneDialog.vue). The input must still cover the row, * or drops miss it. Drops a file on the row's centre via CDP and expects * `input.files` to hold it. * @@ -15,31 +15,34 @@ * drop selects the file, 1 otherwise. */ import http from 'node:http'; -import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; +import { mkdtemp, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; +import vue from '@vitejs/plugin-vue'; import puppeteer from 'puppeteer'; +import { createServer } from 'vite'; +import { VUE_PLUGIN_OPTIONS } from '../build/vueOptions.js'; const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); -const TYPES = { - '.js': 'text/javascript', - '.mjs': 'text/javascript', - '.json': 'application/json', - '.html': 'text/html', -}; +// The scene panels view draws the dialog; Vite compiles it for the page. const PAGE = ` +
diff --git a/src/ui/views/ProviderSettingsView.vue b/src/ui/views/ProviderSettingsView.vue index dc575e877..baa13d021 100644 --- a/src/ui/views/ProviderSettingsView.vue +++ b/src/ui/views/ProviderSettingsView.vue @@ -4,6 +4,7 @@ import { computed, onBeforeUnmount, onMounted, shallowRef } from 'vue'; import { t } from '../../i18n/index.js'; import { vClassToggle } from '../rail/classToggle.js'; import { vFieldValue } from '../rail/fieldValue.js'; +import { vStatusText } from '../rail/statusText.js'; import { useLocaleStore } from '../stores/locale.js'; import { useProviderSettingsStore } from '../stores/providerSettings.js'; import LocalePicker from './panels/LocalePicker.vue'; @@ -34,17 +35,7 @@ onMounted(() => { }); onBeforeUnmount(() => themeObserver?.disconnect()); -// The status line is a live region: write each message, even a repeated one, -// so a screen reader announces it again. -const vStatusText = { - mounted(el, { value }) { - el.textContent = value.text; - }, - updated(el, { value, oldValue }) { - if (value.seq !== oldValue?.seq || value.text !== oldValue?.text) - el.textContent = value.text; - }, -}; +// The status line is a live region; vStatusText re-announces repeats. const statusText = computed(() => ({ text: view.value?.status ?? diff --git a/src/ui/views/panels/SceneDialog.vue b/src/ui/views/panels/SceneDialog.vue new file mode 100644 index 000000000..02e1f6918 --- /dev/null +++ b/src/ui/views/panels/SceneDialog.vue @@ -0,0 +1,64 @@ + + +