mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
69 lines
1.8 KiB
JavaScript
69 lines
1.8 KiB
JavaScript
/** Own one user-triggered action at a time; cancellation settles even an uncooperative adapter. */
|
|
export function createInteractionSession({ execute, changed = () => {} }) {
|
|
let controller,
|
|
active = false,
|
|
busy = false,
|
|
selected = null;
|
|
let actions = new Map();
|
|
const state = () => ({ active, busy, selected, count: actions.size });
|
|
function notify() {
|
|
try {
|
|
changed(state());
|
|
} catch (error) {
|
|
console.warn('[Scenes] Interaction session observer failed:', error);
|
|
}
|
|
}
|
|
function clear() {
|
|
active = false;
|
|
busy = false;
|
|
selected = null;
|
|
actions.clear();
|
|
controller?.abort();
|
|
controller = null;
|
|
notify();
|
|
}
|
|
return {
|
|
clear,
|
|
getState: state,
|
|
activate(items) {
|
|
clear();
|
|
actions = new Map(items.map((item) => [item.id, item]));
|
|
active = !!actions.size;
|
|
notify();
|
|
},
|
|
async dispatch(id) {
|
|
const item = actions.get(id);
|
|
if (!active || busy || !item) return false;
|
|
const current = new AbortController();
|
|
controller = current;
|
|
busy = true;
|
|
selected = id;
|
|
notify();
|
|
let abort;
|
|
try {
|
|
const cancelled = new Promise((resolve) => {
|
|
abort = () => resolve(false);
|
|
current.signal.addEventListener('abort', abort, { once: true });
|
|
});
|
|
const work = Promise.resolve().then(() => {
|
|
if (current.signal.aborted) return false;
|
|
return execute(item, current.signal);
|
|
});
|
|
return (
|
|
(await Promise.race([work, cancelled])) !== false &&
|
|
!current.signal.aborted
|
|
);
|
|
} catch {
|
|
return false;
|
|
} finally {
|
|
current.signal.removeEventListener('abort', abort);
|
|
if (controller === current) {
|
|
busy = false;
|
|
controller = null;
|
|
notify();
|
|
}
|
|
}
|
|
},
|
|
};
|
|
}
|