feat(scenes): render the data-pack attribution cards from a store

The data-pack presentations keep the Cesium resources, object URLs and
pick targets and present each pack's attribution card, with its media
player, to the gev-data-packs store; the DataPacks view teleports one
panel per set of presentations into the viewer's container with the same
markup and inline styles, and stops a removed card's player as it goes.
This commit is contained in:
Sameh Khamis committed 2026-10-31 20:22:22 -07:00
1 parent 59e3aaa271
commit e0abf74308
8 files changed
+290 -69

No files matched your search

+34
View File
@@ -0,0 +1,34 @@
import { defineStore } from 'pinia';
import { markRaw, shallowRef } from 'vue';
/**
* The loaded data packs' attribution cards as their view renders them. Each
* set of data-pack presentations (src/scenes/dataPacks/presentation.js)
* presents its own panel, with a card for each pack and its media player
* when the pack is media.
*/
export const useDataPacksStore = defineStore('gev-data-packs', () => {
/**
* `[{owner, key, container, cards: [{key, id, title, url, media}]}]`, one
* per owner showing cards. `container` is the element the panel sits in;
* `media` is `{kind: 'video'|'audio', src}` or null.
*/
const panels = shallowRef([]);
/**
* @param {object} owner The presentations publishing.
* @param {?object} panel Their fresh panel, or null to remove it.
*/
function publish(owner, panel) {
const index = panels.value.findIndex((item) => item.owner === owner);
const next = panels.value.slice();
if (!panel) {
if (index < 0) return;
next.splice(index, 1);
} else if (index < 0) next.push(markRaw({ ...panel, owner }));
else next[index] = markRaw({ ...panel, owner });
panels.value = next;
}
return { panels, publish };
});
+2
View File
@@ -6,6 +6,7 @@ import { useLocationStore } from '../stores/location.js';
import { useStatusStore } from '../stores/status.js';
import { vClassToggle } from '../rail/classToggle.js';
import ImagerySplitView from './panels/ImagerySplitView.vue';
import DataPacks from './panels/DataPacks.vue';
import SceneInteractions from './panels/SceneInteractions.vue';
import SplitFlapText from './panels/SplitFlapText.vue';
@@ -96,5 +97,6 @@ const vToastText = {
</Teleport>
<ImagerySplitView />
<DataPacks />
<SceneInteractions />
</template>
+45
View File
@@ -0,0 +1,45 @@
<script setup>
import { storeToRefs } from 'pinia';
import { t } from '../../../i18n/index.js';
import { useDataPacksStore } from '../../stores/dataPacks.js';
// The loaded data packs' attribution cards, one panel per set of data-pack
// presentations (src/scenes/dataPacks/presentation.js), inside the viewer's
// container.
const { panels } = storeToRefs(useDataPacksStore());
const panelStyle = {
position: 'absolute',
right: '10px',
bottom: '70px',
maxHeight: '40vh',
overflowY: 'auto',
zIndex: '30',
};
const cardStyle = {
color: '#e3faff',
background: '#08141eed',
border: '1px solid #2491a8',
padding: '8px',
margin: '4px',
maxWidth: '260px',
font: '12px sans-serif',
};
// A removed player stops and lets go of its media, which a detached
// element would otherwise keep playing. This runs as Vue renders the
// removal, in the same task as the dispose that removed it.
const vPackMedia = {
beforeUnmount(el) {
el.pause();
el.removeAttribute('src');
el.load();
},
};
</script>
<template>
<Teleport v-for="panel in panels" :key="panel.key" :to="panel.container">
<div data-director-packs="" :style="panelStyle"><section v-for="card in panel.cards" :key="card.key" :data-director-pack="card.id" :style="cardStyle"><div>{{ card.title }}</div><a v-if="card.url" :href="card.url" target="_blank" rel="noopener noreferrer" style="color: #6eeaff">{{ t('Source') }}</a><video v-if="card.media?.kind === 'video'" v-pack-media controls preload="none" style="width: 240px" :src="card.media.src"></video><audio v-else-if="card.media?.kind === 'audio'" v-pack-media controls preload="none" style="width: 240px" :src="card.media.src"></audio></section></div>
</Teleport>
</template>