mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
Show a basic timeline while the world time is away from live
A bar above the command dock, like a video player's: play and pause on the left, the world time's date and time in UTC beside it, and Live to return. It stays hidden while live, so Core looks as before until weather history is scrubbed or replayed, a host moves the clock, or a link made away from live is opened. Adds the pause and play_arrow glyphs and the strings in every catalog.
This commit is contained in:
1 parent
32ec6ed436
commit
434fc9602d
21 files changed
+225
-13
No files matched your search
+1
-1
@@ -26,7 +26,7 @@
|
||||
with the sources: src/materialSymbolsSubset.test.mjs fails when a source
|
||||
names a glyph this list is missing, because a missing glyph does not draw
|
||||
a box — the ligature never forms and the element renders the literal word. -->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20,400,0,0&icon_names=adjust,arrow_drop_down,arrow_forward,arrow_left,arrow_right,bolt,chevron_left,chevron_right,close,close_fullscreen,dark_mode,draw,east,flare,flight,layers,layers_clear,light_mode,local_fire_department,more_horiz,movie,my_location,navigation,normal,on,open_in_full,partly_cloudy_day,public,radar,radio,right_panel_close,right_panel_open,rocket_launch,satellite_alt,search,settings,skip_next,skip_previous,streetview,tune,upload_file,videocam,view_in_ar,visibility,visibility_off" rel="stylesheet" />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20,400,0,0&icon_names=adjust,arrow_drop_down,arrow_forward,arrow_left,arrow_right,bolt,chevron_left,chevron_right,close,close_fullscreen,dark_mode,draw,east,flare,flight,layers,layers_clear,light_mode,local_fire_department,more_horiz,movie,my_location,navigation,normal,on,open_in_full,partly_cloudy_day,pause,play_arrow,public,radar,radio,right_panel_close,right_panel_open,rocket_launch,satellite_alt,search,settings,skip_next,skip_previous,streetview,tune,upload_file,videocam,view_in_ar,visibility,visibility_off" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="gev-view" data-gev-view="scene-chrome"></div>
|
||||
|
||||
@@ -146,7 +146,10 @@
|
||||
"src/ui/stores/recording.js",
|
||||
"src/ui/styleParameters.js",
|
||||
"src/ui/i18n.js",
|
||||
"src/time/worldClock.js"
|
||||
"src/time/worldClock.js",
|
||||
"src/ui/kit/UiIcon.vue",
|
||||
"src/ui/stores/worldTime.js",
|
||||
"src/ui/views/panels/WorldTimeline.vue"
|
||||
],
|
||||
"external": ["vue", "pinia", "vue-i18n"]
|
||||
},
|
||||
|
||||
@@ -3218,5 +3218,8 @@
|
||||
"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 أو حزمة موارد"
|
||||
"Download this scene as JSON or an asset bundle": "تنزيل هذا المشهد بصيغة JSON أو حزمة موارد",
|
||||
"World time": "وقت العالم",
|
||||
"Back to live": "العودة إلى البث المباشر",
|
||||
"Live": "مباشر"
|
||||
}
|
||||
@@ -2938,5 +2938,8 @@
|
||||
"Left panels": "Linke Panels",
|
||||
"Right panels": "Rechte Panels",
|
||||
"CONFIRMED": "BESTÄTIGT",
|
||||
"PAD / RTLS": "STARTPLATZ / RTLS"
|
||||
"PAD / RTLS": "STARTPLATZ / RTLS",
|
||||
"World time": "Weltzeit",
|
||||
"Back to live": "Zurück zu live",
|
||||
"Live": "Live"
|
||||
}
|
||||
@@ -3008,5 +3008,8 @@
|
||||
"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",
|
||||
"Left panels": "Paneles izquierdos",
|
||||
"Right panels": "Paneles derechos"
|
||||
"Right panels": "Paneles derechos",
|
||||
"World time": "Hora mundial",
|
||||
"Back to live": "Volver al directo",
|
||||
"Live": "En directo"
|
||||
}
|
||||
@@ -3008,5 +3008,8 @@
|
||||
"Left panels": "Panneaux de gauche",
|
||||
"Right panels": "Panneaux de droite",
|
||||
"CONFIRMED": "CONFIRMÉE",
|
||||
"PAD / RTLS": "PAS DE TIR / RTLS"
|
||||
"PAD / RTLS": "PAS DE TIR / RTLS",
|
||||
"World time": "Heure du monde",
|
||||
"Back to live": "Revenir au direct",
|
||||
"Live": "Direct"
|
||||
}
|
||||
@@ -3008,5 +3008,8 @@
|
||||
"Left panels": "Pannelli a sinistra",
|
||||
"Right panels": "Pannelli a destra",
|
||||
"CONFIRMED": "CONFERMATA",
|
||||
"PAD / RTLS": "SITO DI LANCIO / RTLS"
|
||||
"PAD / RTLS": "SITO DI LANCIO / RTLS",
|
||||
"World time": "Ora del mondo",
|
||||
"Back to live": "Torna alla diretta",
|
||||
"Live": "Diretta"
|
||||
}
|
||||
@@ -2868,5 +2868,8 @@
|
||||
"Left panels": "左パネル",
|
||||
"Right panels": "右パネル",
|
||||
"CONFIRMED": "確認済み",
|
||||
"PAD / RTLS": "射場 / RTLS"
|
||||
"PAD / RTLS": "射場 / RTLS",
|
||||
"World time": "世界時刻",
|
||||
"Back to live": "ライブに戻る",
|
||||
"Live": "ライブ"
|
||||
}
|
||||
@@ -3008,5 +3008,8 @@
|
||||
"Left panels": "Painéis à esquerda",
|
||||
"Right panels": "Painéis à direita",
|
||||
"CONFIRMED": "CONFIRMADA",
|
||||
"PAD / RTLS": "PLATAFORMA / RTLS"
|
||||
"PAD / RTLS": "PLATAFORMA / RTLS",
|
||||
"World time": "Hora do mundo",
|
||||
"Back to live": "Voltar ao vivo",
|
||||
"Live": "Ao vivo"
|
||||
}
|
||||
@@ -3078,5 +3078,8 @@
|
||||
"Edit anchors, data packs and shot details": "Изменить якоря, пакеты данных и параметры кадров",
|
||||
"Download this scene as JSON or an asset bundle": "Скачать эту сцену в формате JSON или архивом ресурсов",
|
||||
"Left panels": "Левые панели",
|
||||
"Right panels": "Правые панели"
|
||||
"Right panels": "Правые панели",
|
||||
"World time": "Мировое время",
|
||||
"Back to live": "Вернуться к прямому эфиру",
|
||||
"Live": "Сейчас"
|
||||
}
|
||||
@@ -3078,5 +3078,8 @@
|
||||
"Left panels": "Ліві панелі",
|
||||
"Right panels": "Праві панелі",
|
||||
"CONFIRMED": "ПІДТВЕРДЖЕНО",
|
||||
"PAD / RTLS": "МІСЦЕ ЗАПУСКУ / RTLS"
|
||||
"PAD / RTLS": "МІСЦЕ ЗАПУСКУ / RTLS",
|
||||
"World time": "Світовий час",
|
||||
"Back to live": "Повернутися до прямого ефіру",
|
||||
"Live": "Зараз"
|
||||
}
|
||||
@@ -2868,5 +2868,8 @@
|
||||
"Edit anchors, data packs and shot details": "编辑锚点、数据包和镜头详情",
|
||||
"Download this scene as JSON or an asset bundle": "将此场景下载为 JSON 或资源包",
|
||||
"Left panels": "左侧面板",
|
||||
"Right panels": "右侧面板"
|
||||
"Right panels": "右侧面板",
|
||||
"World time": "世界时间",
|
||||
"Back to live": "返回实时",
|
||||
"Live": "实时"
|
||||
}
|
||||
@@ -2868,5 +2868,8 @@
|
||||
"Edit anchors, data packs and shot details": "編輯錨點、數據包和鏡頭詳情",
|
||||
"Download this scene as JSON or an asset bundle": "將此場景下載為 JSON 或資源包",
|
||||
"Left panels": "左側面板",
|
||||
"Right panels": "右側面板"
|
||||
"Right panels": "右側面板",
|
||||
"World time": "世界時間",
|
||||
"Back to live": "返回即時",
|
||||
"Live": "即時"
|
||||
}
|
||||
@@ -88,6 +88,7 @@
|
||||
"Landsat 8/9",
|
||||
"Latin",
|
||||
"Layout",
|
||||
"Live",
|
||||
"London",
|
||||
"Los Angeles",
|
||||
"MIL",
|
||||
|
||||
@@ -32,6 +32,7 @@ const LEFT_STACK_OBSTACLE_SELECTOR = [
|
||||
'#gev-voice-control',
|
||||
'#pp-toggles',
|
||||
'#param-slider-panel',
|
||||
'#world-timeline',
|
||||
].join(', ');
|
||||
/**
|
||||
* Fixed UI regions that can occupy the right control lane. Runtime rectangle
|
||||
@@ -59,6 +60,7 @@ const RIGHT_STACK_OBSTACLE_SELECTOR = [
|
||||
'#cesium-credits .cesium-credit-textContainer',
|
||||
'#command-dock',
|
||||
'#gev-voice-control',
|
||||
'#world-timeline',
|
||||
].join(', ');
|
||||
export class PanelLayoutController {
|
||||
constructor({
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { onScopeDispose, shallowRef } from 'vue';
|
||||
import { worldClock } from '../../time/worldClock.js';
|
||||
|
||||
/**
|
||||
* The world clock as the timeline shows it: `{live, playing, timeMs}`. It
|
||||
* follows the page's world clock, which tells it of every move, and of the
|
||||
* time a few times a second while it plays on its own.
|
||||
*/
|
||||
export const useWorldTimeStore = defineStore('gev-world-time', () => {
|
||||
const read = (state = worldClock.getState()) =>
|
||||
Object.freeze({
|
||||
live: state.live,
|
||||
playing: state.playing,
|
||||
timeMs: state.timeMs,
|
||||
});
|
||||
const time = shallowRef(read());
|
||||
onScopeDispose(
|
||||
worldClock.subscribe((state) => {
|
||||
time.value = read(state);
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
time,
|
||||
togglePlay: () => worldClock.togglePlay(),
|
||||
goLive: () => worldClock.goLive(),
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
/* ── World timeline ─────────────────────────── */
|
||||
/* Shown while the world time is away from live, centred above the dock. */
|
||||
#world-timeline {
|
||||
position: fixed;
|
||||
bottom: calc(2vh + 7rem);
|
||||
left: 50%;
|
||||
z-index: 146;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
max-width: calc(100vw - 32px);
|
||||
box-sizing: border-box;
|
||||
padding: 4px 6px;
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 10px;
|
||||
background: var(--glass-bg);
|
||||
backdrop-filter: blur(24px) saturate(1.4);
|
||||
-webkit-backdrop-filter: blur(24px) saturate(1.4);
|
||||
box-shadow: 0 8px 28px rgba(0, 0, 0, 0.5);
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
body.ui-clean-view #world-timeline,
|
||||
body.recording-mode #world-timeline {
|
||||
bottom: 2vh;
|
||||
}
|
||||
|
||||
#world-timeline .world-timeline-play,
|
||||
#world-timeline .world-timeline-live {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 32px;
|
||||
height: 32px;
|
||||
padding: 0 8px;
|
||||
border: 1px solid rgba(123, 189, 211, 0.28);
|
||||
border-radius: 8px;
|
||||
background: rgba(0, 212, 255, 0.08);
|
||||
color: var(--text-primary);
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#world-timeline .world-timeline-play:hover,
|
||||
#world-timeline .world-timeline-live:hover {
|
||||
background: rgba(0, 212, 255, 0.18);
|
||||
}
|
||||
|
||||
#world-timeline .world-timeline-live {
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
#world-timeline .world-timeline-time {
|
||||
overflow: hidden;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 13px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import ScreenAnnotations from './panels/ScreenAnnotations.vue';
|
||||
import AwarenessDirection from './panels/AwarenessDirection.vue';
|
||||
import UiButton from '../kit/UiButton.vue';
|
||||
import GlobeActions from './panels/GlobeActions.vue';
|
||||
import WorldTimeline from './panels/WorldTimeline.vue';
|
||||
|
||||
// The ACTIVE STYLE corner shows the style VisualSettings publishes.
|
||||
const { style } = storeToRefs(useDisplayStore());
|
||||
@@ -117,4 +118,5 @@ const vToastText = {
|
||||
<MissionReplayOverlay />
|
||||
<ScreenAnnotations />
|
||||
<AwarenessDirection />
|
||||
<WorldTimeline />
|
||||
</template>
|
||||
@@ -0,0 +1,45 @@
|
||||
// The world timeline shows the world time while it is away from live, with
|
||||
// play and pause on the left and the way back to live on the right.
|
||||
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 { worldClock } from '../../../time/worldClock.js';
|
||||
import WorldTimeline from './WorldTimeline.vue';
|
||||
|
||||
test('the timeline shows away from live and plays, pauses and goes back', async (t) => {
|
||||
document.body.replaceChildren();
|
||||
const app = createApp(WorldTimeline).use(createPinia());
|
||||
app.mount(document.body.appendChild(document.createElement('div')));
|
||||
t.after(() => {
|
||||
app.unmount();
|
||||
worldClock.goLive();
|
||||
});
|
||||
const bar = () => document.getElementById('world-timeline');
|
||||
await nextTick();
|
||||
assert.equal(bar(), null, 'hidden while live');
|
||||
|
||||
const at = Date.UTC(2026, 9, 30, 6, 15, 42);
|
||||
worldClock.seek(at);
|
||||
await nextTick();
|
||||
assert.ok(bar());
|
||||
const time = bar().querySelector('time');
|
||||
assert.equal(time.getAttribute('datetime'), new Date(at).toISOString());
|
||||
assert.match(time.textContent, /UTC$/);
|
||||
const play = bar().querySelector('.world-timeline-play');
|
||||
assert.equal(play.getAttribute('aria-label'), 'Play');
|
||||
|
||||
play.click();
|
||||
await nextTick();
|
||||
assert.equal(worldClock.isPlaying(), true);
|
||||
assert.equal(play.getAttribute('aria-label'), 'Pause');
|
||||
play.click();
|
||||
await nextTick();
|
||||
assert.equal(worldClock.isPlaying(), false);
|
||||
|
||||
bar().querySelector('.world-timeline-live').click();
|
||||
await nextTick();
|
||||
assert.equal(worldClock.isLive(), true);
|
||||
assert.equal(bar(), null);
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { formatDate, t } from '../../../i18n/index.js';
|
||||
import { useWorldTimeStore } from '../../stores/worldTime.js';
|
||||
import UiButton from '../../kit/UiButton.vue';
|
||||
import UiIcon from '../../kit/UiIcon.vue';
|
||||
|
||||
// The world time while it is away from live: what is playing, or where it
|
||||
// was paused, with play and pause on the left and the way back to live on
|
||||
// the right. Live, the bar stays hidden.
|
||||
const store = useWorldTimeStore();
|
||||
const { time } = storeToRefs(store);
|
||||
|
||||
const DATE_TIME = Object.freeze({
|
||||
dateStyle: 'medium',
|
||||
timeStyle: 'medium',
|
||||
timeZone: 'UTC',
|
||||
});
|
||||
const readout = computed(() =>
|
||||
Number.isFinite(time.value.timeMs)
|
||||
? `${formatDate(time.value.timeMs, DATE_TIME)} UTC`
|
||||
: '',
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="!time.live" id="world-timeline" role="group" :aria-label="t('World time')"
|
||||
><UiButton class="world-timeline-play" :label="time.playing ? t('Pause') : t('Play')" @click="store.togglePlay()"
|
||||
><UiIcon :name="time.playing ? 'pause' : 'play_arrow'" :size="20" /></UiButton
|
||||
><time class="world-timeline-time" :datetime="new Date(time.timeMs).toISOString()" aria-live="off">{{ readout }}</time
|
||||
><UiButton class="world-timeline-live" :label="t('Back to live')" @click="store.goLive()">{{ t('Live') }}</UiButton
|
||||
></div>
|
||||
</template>
|
||||
@@ -29,6 +29,7 @@
|
||||
@import './src/ui/styles/provider-settings.css';
|
||||
@import './src/ui/styles/locale-picker.css';
|
||||
@import './src/ui/styles/weather.css';
|
||||
@import './src/ui/styles/world-timeline.css';
|
||||
@import './src/ui/styles/panel-surfaces.css';
|
||||
@import './src/ui/styles/segmented-control.css';
|
||||
@import './src/ui/styles/kit.css';
|
||||
|
||||
Reference in new issue
Block a user