feat(scenes): render the Scene actions panel from a store

The scene interactions owner keeps the pick handler and the interaction
session and presents the panel to the gev-scene-interactions store; the
SceneInteractions view teleports it into the viewer's container with the
same markup and inline styles. Each activation keys its own panel, each
button carries the owner it was rendered for, and only the owner the
store shows may change or remove it.
This commit is contained in:
Sameh Khamis committed 2026-10-31 20:14:35 -07:00
1 parent 6398103b60
commit 59e3aaa271
8 files changed
+376 -104

No files matched your search

+35
View File
@@ -0,0 +1,35 @@
import { defineStore } from 'pinia';
import { markRaw, shallowRef } from 'vue';
/**
* The active shot's Scene actions panel as its view renders it. The scene
* interactions owner (src/scenes/interactions.js) presents the actions, the
* status line and the source card here; the view sends action clicks back to
* the connected controls.
*/
export const useSceneInteractionsStore = defineStore(
'gev-scene-interactions',
() => {
/**
* `{id, owner, container, items, status, busy, selected, outlined,
* card}`, or null while the shot has no actions. `container` is the
* element it sits in; `owner` is passed back with each dispatch.
*/
const panel = shallowRef(null);
/** @param {?object} next A fresh presentation; every publish re-renders. */
function publish(next) {
panel.value = next && markRaw(next);
}
/** The controls whose `dispatch(id, owner)` an action button calls. */
const controls = shallowRef(null);
/** @param {?object} next The controls, or null to disconnect. */
function connect(next) {
controls.value = next && markRaw(next);
}
return { panel, publish, controls, connect };
},
);
+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 SceneInteractions from './panels/SceneInteractions.vue';
import SplitFlapText from './panels/SplitFlapText.vue';
// The ACTIVE STYLE corner shows the style VisualSettings publishes.
@@ -95,4 +96,5 @@ const vToastText = {
</Teleport>
<ImagerySplitView />
<SceneInteractions />
</template>
+50
View File
@@ -0,0 +1,50 @@
<script setup>
import { storeToRefs } from 'pinia';
import { t } from '../../../i18n/index.js';
import { useSceneInteractionsStore } from '../../stores/sceneInteractions.js';
// The active shot's Scene actions panel, inside the viewer's container, as
// the scene interactions owner (src/scenes/interactions.js) presents it.
const { panel, controls } = storeToRefs(useSceneInteractionsStore());
const panelStyle = {
position: 'absolute',
left: '12px',
bottom: '80px',
zIndex: '31',
maxWidth: '300px',
maxHeight: '40vh',
overflowY: 'auto',
background: '#08141eed',
color: '#e3faff',
border: '1px solid #2491a8',
padding: '10px',
font: '13px sans-serif',
};
const buttonStyle = {
display: 'block',
background: '#10242e',
color: '#e3faff',
border: '1px solid #397080',
borderRadius: '3px',
padding: '6px 10px',
cursor: 'pointer',
font: 'inherit',
margin: '6px 0',
maxWidth: '100%',
whiteSpace: 'normal',
};
// Each button keeps the panel it was rendered for, so one left over from an
// earlier panel cannot run the current actions.
const onClick = (owner, id) => () => controls.value?.dispatch(id, owner);
const outline = (id) => ({
...buttonStyle,
outline: panel.value.outlined.includes(id) ? '2px solid #6eeaff' : '',
});
</script>
<template>
<Teleport v-if="panel" :to="panel.container">
<section :key="panel.id" data-director-interactions="" :aria-label="t('Scene actions')" :style="panelStyle"><strong>{{ t('Scene actions') }}</strong><p role="status">{{ panel.status }}</p><button v-for="item in panel.items" :key="item.id" type="button" :data-director-action="item.id" :disabled="panel.busy" :aria-pressed="String(panel.selected === item.id)" :style="outline(item.id)" :onClick="onClick(panel.owner, item.id)">{{ item.label }}</button><div data-director-action-card=""><template v-if="panel.card"><p>{{ panel.card.text }}</p><a v-if="panel.card.url" :href="panel.card.url" target="_blank" rel="noopener noreferrer" style="color: #6eeaff">{{ t('Source') }}</a></template></div></section>
</Teleport>
</template>