mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 02:44:02 +00:00
feat: add declarative Director feature actions
This commit is contained in:
1 parent
a30c42322c
commit
80027756ef
22 files changed
+1150
-17
No files matched your search
@@ -0,0 +1,96 @@
|
||||
import {
|
||||
array,
|
||||
fields,
|
||||
string,
|
||||
optional,
|
||||
uniqueId,
|
||||
fail,
|
||||
} from '../documentFields.js';
|
||||
|
||||
/** Validate inert, scene-local actions. Feature existence is checked after asset loading. */
|
||||
export function validateSceneInteractions(scene, path) {
|
||||
const packs = new Map((scene.dataPacks || []).map((p) => [p.id, p]));
|
||||
const anchors = new Set((scene.anchors || []).map((a) => a.id));
|
||||
const shots = new Map(scene.shots.map((s) => [s.id, s]));
|
||||
scene.shots.forEach((shot, index) => {
|
||||
optional(shot, 'interactions', `${path}.shots[${index}]`, (items, at) => {
|
||||
array(items, at, 64);
|
||||
const seen = new Set();
|
||||
items.forEach((item, i) => {
|
||||
const field = `${at}[${i}]`;
|
||||
fields(item, field, ['id', 'label', 'target', 'action']);
|
||||
string(item.id, field);
|
||||
uniqueId(item, field, seen);
|
||||
string(item.label, field, 256);
|
||||
fields(item.target, `${field}.target`, ['packId', 'featureId']);
|
||||
string(item.target.packId, field);
|
||||
string(item.target.featureId, field);
|
||||
if (
|
||||
packs.get(item.target.packId)?.format !== 'geojson' ||
|
||||
!shot.dataPackIds?.includes(item.target.packId)
|
||||
)
|
||||
fail(field, 'target must belong to a selected GeoJSON pack');
|
||||
const a = item.action;
|
||||
const specs = {
|
||||
card: ['text', 'url'],
|
||||
focus: ['anchorId'],
|
||||
shot: ['shotId'],
|
||||
layer: ['layerId', 'enabled'],
|
||||
};
|
||||
if (!a || !Object.hasOwn(specs, a.type))
|
||||
fail(field, 'unsupported action');
|
||||
string(a.type, `${field}.action.type`);
|
||||
fields(a, `${field}.action`, ['type', ...specs[a.type]]);
|
||||
const reference = {
|
||||
focus: 'anchorId',
|
||||
shot: 'shotId',
|
||||
layer: 'layerId',
|
||||
}[a.type];
|
||||
if (reference) string(a[reference], `${field}.action.${reference}`);
|
||||
if (a.type === 'card') {
|
||||
string(a.text, field, 4096);
|
||||
optional(a, 'url', field, (url, at) => {
|
||||
string(url, at, 2048);
|
||||
let parsed;
|
||||
try {
|
||||
parsed = new URL(url);
|
||||
} catch {
|
||||
fail(at, 'expected HTTPS source URL');
|
||||
}
|
||||
if (
|
||||
parsed.protocol !== 'https:' ||
|
||||
parsed.username ||
|
||||
parsed.password ||
|
||||
parsed.search ||
|
||||
parsed.hash
|
||||
)
|
||||
fail(
|
||||
at,
|
||||
'expected HTTPS source URL without credentials, query or fragment',
|
||||
);
|
||||
});
|
||||
} else if (a.type === 'focus' && !anchors.has(a.anchorId))
|
||||
fail(field, 'unknown anchor');
|
||||
else if (a.type === 'shot') {
|
||||
const target = shots.get(a.shotId);
|
||||
if (!target) fail(field, 'unknown shot');
|
||||
// A transition must restore every layer this shot can change.
|
||||
for (const entry of items)
|
||||
if (
|
||||
entry?.action?.type === 'layer' &&
|
||||
!Object.hasOwn(target.layers || {}, entry.action.layerId)
|
||||
)
|
||||
fail(
|
||||
field,
|
||||
'target shot must declare interactive layer baseline',
|
||||
);
|
||||
} else if (a.type === 'layer') {
|
||||
if (!Object.hasOwn(shot.layers || {}, a.layerId))
|
||||
fail(field, 'layer must have an explicit shot baseline');
|
||||
if (typeof a.enabled !== 'boolean')
|
||||
fail(field, 'expected enabled boolean');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user