mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
feat(recording): render the safe frame from a recording store
RecordingControls publishes the safe frame a recording composes in, 16:9 or 9:16 (null while none records), to the new recording store, and the scene chrome renders the overlay's active and ratio classes from it instead of the controls writing them. Any frame but 9:16, even none named, records in 16:9 as before.
This commit is contained in:
1 parent
0949f6a776
commit
3a750c9021
11 files changed
+116
-38
No files matched your search
@@ -63,6 +63,7 @@ import { useLocaleStore } from './stores/locale.js';
|
||||
import { useLocationStore } from './stores/location.js';
|
||||
import { useCockpitStore } from './stores/cockpit.js';
|
||||
import { useGlobeActionsStore } from './stores/globeActions.js';
|
||||
import { useRecordingStore } from './stores/recording.js';
|
||||
|
||||
/**
|
||||
* Central UI orchestrator for the God's Eye View application.
|
||||
@@ -129,6 +130,8 @@ export class StyleManager extends ShellFacade {
|
||||
this._recording = new RecordingControls({
|
||||
syncShareState: () => this._syncShareState(),
|
||||
syncHudControls: () => this._visualSettings._syncHudControls(),
|
||||
publishSafeFrame: (frame) =>
|
||||
useRecordingStore(applicationPinia()).publishSafeFrame(frame),
|
||||
});
|
||||
Object.assign(this, readShellElements());
|
||||
this._panelChrome = new PanelChrome({
|
||||
|
||||
+13
-25
@@ -1,7 +1,16 @@
|
||||
/** Own recording chrome, safe frames and the pre-recording HUD snapshot. */
|
||||
/**
|
||||
* Own recording chrome, safe frames and the pre-recording HUD snapshot. The
|
||||
* safe frame a recording composes in goes to `publishSafeFrame` ('16:9',
|
||||
* '9:16', or null while none records); the scene chrome renders it.
|
||||
*/
|
||||
export class RecordingControls {
|
||||
constructor({ syncShareState, syncHudControls }) {
|
||||
constructor({
|
||||
syncShareState,
|
||||
syncHudControls,
|
||||
publishSafeFrame = () => {},
|
||||
}) {
|
||||
this._syncShareState = syncShareState;
|
||||
this._publishSafeFrame = publishSafeFrame;
|
||||
// Shows the HUD's visibility and layout on the Display controls.
|
||||
this._syncHudControls = syncHudControls;
|
||||
this.destroyed = false;
|
||||
@@ -12,18 +21,10 @@ export class RecordingControls {
|
||||
safeFrame: '16:9',
|
||||
};
|
||||
this._preRecordingHudState = null;
|
||||
this._safeFrameOverlay = document.getElementById('safe-frame-overlay');
|
||||
this._safeFrameBox = document.getElementById('safe-frame-box');
|
||||
this.hud = null;
|
||||
}
|
||||
_initRecordingOverlay() {
|
||||
if (this.destroyed) return;
|
||||
if (!this._safeFrameOverlay || !this._safeFrameBox) return;
|
||||
this._safeFrameOverlay.classList.remove(
|
||||
'active',
|
||||
'ratio-9-16',
|
||||
'ratio-16-9',
|
||||
);
|
||||
if (!this.destroyed) this._publishSafeFrame(null);
|
||||
}
|
||||
|
||||
setRecordingMode(enabled, options = {}) {
|
||||
@@ -41,13 +42,7 @@ export class RecordingControls {
|
||||
this._recordingMode && hidePanels,
|
||||
);
|
||||
|
||||
if (this._safeFrameOverlay) {
|
||||
this._safeFrameOverlay.classList.remove('ratio-9-16', 'ratio-16-9');
|
||||
this._safeFrameOverlay.classList.toggle('active', this._recordingMode);
|
||||
this._safeFrameOverlay.classList.add(
|
||||
safeFrame === '9:16' ? 'ratio-9-16' : 'ratio-16-9',
|
||||
);
|
||||
}
|
||||
this._publishSafeFrame(this._recordingMode ? (safeFrame ?? '16:9') : null);
|
||||
|
||||
if (this._recordingMode) {
|
||||
// Snapshot the user's HUD state once per recording session so exit can
|
||||
@@ -73,13 +68,6 @@ export class RecordingControls {
|
||||
this.hud.setVariant(saved.variant);
|
||||
}
|
||||
this.hud.setMode(saved ? saved.mode : 'auto');
|
||||
if (this._safeFrameOverlay) {
|
||||
this._safeFrameOverlay.classList.remove(
|
||||
'active',
|
||||
'ratio-9-16',
|
||||
'ratio-16-9',
|
||||
);
|
||||
}
|
||||
}
|
||||
this._syncHudControls();
|
||||
this._syncShareState();
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
// src/ui/safeFrameView.test.mjs — the recording safe frame renders what
|
||||
// RecordingControls publishes to the recording store.
|
||||
import '../testSupport/registerDom.mjs';
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { createApp, nextTick } from 'vue';
|
||||
import { createPinia } from 'pinia';
|
||||
import { useRecordingStore } from './stores/recording.js';
|
||||
import { RecordingControls } from './recordingControls.js';
|
||||
import SceneChromeView from './views/SceneChromeView.vue';
|
||||
|
||||
test('the safe frame shows while a recording composes in it', async (t) => {
|
||||
document.body.replaceChildren();
|
||||
const pinia = createPinia();
|
||||
const app = createApp(SceneChromeView).use(pinia);
|
||||
app.mount(document.body.appendChild(document.createElement('div')));
|
||||
t.after(() => app.unmount());
|
||||
const store = useRecordingStore(pinia);
|
||||
const recording = new RecordingControls({
|
||||
syncShareState() {},
|
||||
syncHudControls() {},
|
||||
publishSafeFrame: (frame) => store.publishSafeFrame(frame),
|
||||
});
|
||||
recording.hud = {
|
||||
getMode: () => 'auto',
|
||||
getVariant: () => 'tactical',
|
||||
setMode() {},
|
||||
setVariant() {},
|
||||
};
|
||||
t.after(() => recording.destroy());
|
||||
const overlay = document.getElementById('safe-frame-overlay');
|
||||
const classes = () =>
|
||||
['active', 'ratio-16-9', 'ratio-9-16'].filter((name) =>
|
||||
overlay.classList.contains(name),
|
||||
);
|
||||
recording._initRecordingOverlay();
|
||||
await nextTick();
|
||||
assert.deepEqual(classes(), []);
|
||||
recording.setRecordingMode(true, { safeFrame: '9:16' });
|
||||
await nextTick();
|
||||
assert.deepEqual(classes(), ['active', 'ratio-9-16']);
|
||||
recording.setRecordingMode(true);
|
||||
await nextTick();
|
||||
assert.deepEqual(classes(), ['active', 'ratio-16-9']);
|
||||
recording.setRecordingMode(false);
|
||||
await nextTick();
|
||||
assert.deepEqual(classes(), []);
|
||||
// Any frame but 9:16, even none named, records in 16:9.
|
||||
recording.setRecordingMode(true, { safeFrame: null });
|
||||
await nextTick();
|
||||
assert.deepEqual(classes(), ['active', 'ratio-16-9']);
|
||||
recording.setRecordingMode(true, { safeFrame: '4:3' });
|
||||
await nextTick();
|
||||
assert.deepEqual(classes(), ['active', 'ratio-16-9']);
|
||||
// A recording that ends with its controls takes its frame with it.
|
||||
recording.destroy();
|
||||
await nextTick();
|
||||
assert.deepEqual(classes(), []);
|
||||
});
|
||||
@@ -64,8 +64,6 @@ export function readShellElements(document = globalThis.document) {
|
||||
_cctvMeta: document.getElementById('cctv-meta'),
|
||||
_cctvSummary: document.getElementById('cctv-summary'),
|
||||
_locationSearch: document.getElementById('location-search'),
|
||||
_safeFrameOverlay: document.getElementById('safe-frame-overlay'),
|
||||
_safeFrameBox: document.getElementById('safe-frame-box'),
|
||||
_detectionBtn: document.getElementById('detection-toggle'),
|
||||
_models3dBtn: document.getElementById('models3d-toggle'),
|
||||
};
|
||||
|
||||
@@ -56,8 +56,6 @@ function fixture() {
|
||||
'left-panel-stack',
|
||||
'right-context-rail',
|
||||
'pp-toggles',
|
||||
'safe-frame-overlay',
|
||||
'safe-frame-box',
|
||||
'hud-toggle',
|
||||
'hud-layout-select',
|
||||
].map((id) => [id, element()]),
|
||||
@@ -222,9 +220,11 @@ test('recording restores the original HUD after repeated entry and revokes retai
|
||||
let mode = 'auto',
|
||||
variant = 'tactical';
|
||||
const hudSyncs = [];
|
||||
const frames = [];
|
||||
const owner = new RecordingControls({
|
||||
syncShareState() {},
|
||||
syncHudControls: () => hudSyncs.push(variant),
|
||||
publishSafeFrame: (frame) => frames.push(frame),
|
||||
});
|
||||
owner.hud = {
|
||||
getMode: () => mode,
|
||||
@@ -249,9 +249,10 @@ test('recording restores the original HUD after repeated entry and revokes retai
|
||||
'each change shows the HUD layout it leaves',
|
||||
);
|
||||
assert.equal(document.body.classList.contains('recording-mode'), false);
|
||||
assert.equal(
|
||||
f.nodes.get('safe-frame-overlay').classList.contains('active'),
|
||||
false,
|
||||
assert.deepEqual(
|
||||
frames,
|
||||
['9:16', '16:9', null],
|
||||
'the safe frame follows each change and leaves with the recording',
|
||||
);
|
||||
} finally {
|
||||
f.restore();
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
|
||||
/**
|
||||
* Recording composition as the scene chrome renders it. RecordingControls
|
||||
* publishes the safe frame a recording composes in.
|
||||
*/
|
||||
export const useRecordingStore = defineStore('gev-recording', () => {
|
||||
/** The recording's safe frame, '16:9' or '9:16'; null while none records. */
|
||||
const safeFrame = ref(null);
|
||||
|
||||
/** @param {'16:9' | '9:16' | null} next */
|
||||
function publishSafeFrame(next) {
|
||||
safeFrame.value = next == null ? null : next === '9:16' ? '9:16' : '16:9';
|
||||
}
|
||||
|
||||
return { safeFrame, publishSafeFrame };
|
||||
});
|
||||
@@ -3,6 +3,7 @@ import { storeToRefs } from 'pinia';
|
||||
import { t } from '../../i18n/index.js';
|
||||
import { useDisplayStore } from '../stores/display.js';
|
||||
import { useLocationStore } from '../stores/location.js';
|
||||
import { useRecordingStore } from '../stores/recording.js';
|
||||
import { useStatusStore } from '../stores/status.js';
|
||||
import { useWorldOverlayActionsStore } from '../stores/worldOverlayActions.js';
|
||||
import { vClassToggle } from '../rail/classToggle.js';
|
||||
@@ -23,6 +24,8 @@ const { style } = storeToRefs(useDisplayStore());
|
||||
// The status chips, toast and FPS readout show what ShellFeedback,
|
||||
// CctvControls, createToast() and createFrameRateMonitor() publish.
|
||||
const { loading, traffic, cctv, toast, fps } = storeToRefs(useStatusStore());
|
||||
// The safe frame a recording composes in, as RecordingControls publishes it.
|
||||
const { safeFrame } = storeToRefs(useRecordingStore());
|
||||
// The ORBIT indicator LocationControls offers with the location tray.
|
||||
const { view: location } = storeToRefs(useLocationStore());
|
||||
// The focusable mirror of the overlay cards the world overlay painted.
|
||||
@@ -98,7 +101,7 @@ const vToastText = {
|
||||
<div id="toast" v-toast-text="toast" role="status" aria-live="polite" aria-atomic="true" v-class-toggle="{ visible: !!toast?.visible }"></div>
|
||||
|
||||
<!-- Safe frame guides for recording composition -->
|
||||
<div id="safe-frame-overlay">
|
||||
<div id="safe-frame-overlay" v-class-toggle="{ active: !!safeFrame, 'ratio-16-9': safeFrame === '16:9', 'ratio-9-16': safeFrame === '9:16' }">
|
||||
<div id="safe-frame-box"></div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in new issue
Block a user