mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
Release God's Eye View as open source
This commit is contained in:
1 parent
a5aaea7e16
commit
880a672b5e
433 files changed
+219893
-23
No files matched your search
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* Camera-ownership policy for explicit and deferred navigation.
|
||||
*
|
||||
* Immediate destinations refuse Cockpit before mutation, then stamp, release,
|
||||
* and fly. Deferred destinations stamp without releasing; after resolution
|
||||
* they must recheck the stamp and Cockpit immediately before releasing and
|
||||
* flying.
|
||||
*/
|
||||
|
||||
export const NAVIGATION_AUTHORITY_EVENT = 'gev:navigation-authority-taken';
|
||||
|
||||
/**
|
||||
* Announce that a layer-owned camera flight is taking navigation authority.
|
||||
*
|
||||
* Aircraft focus reaches the stamp for free: it assigns `viewer.trackedEntity`,
|
||||
* and the UI stamps on `trackedEntityChanged`. Vessel and installation focus
|
||||
* fly the camera WITHOUT ever setting a tracked entity, so they have no such
|
||||
* seam — an earlier deferred geocode would still match the generation it
|
||||
* captured and could resolve on top of the new Context focus. This is that
|
||||
* missing seam, kept explicit so the flight and the stamp cannot drift apart.
|
||||
* @param {string} reason Diagnostic label for the taking path.
|
||||
* @param {object} [options] Authority options.
|
||||
* @param {EventTarget} [options.eventTarget=globalThis.window] Dispatch target.
|
||||
* @param {boolean} [options.cancelPendingSelection=true] Whether this is newer
|
||||
* direct intent that may supersede a passive selected-entity restore.
|
||||
* @returns {boolean} Whether the announcement was dispatched.
|
||||
*/
|
||||
export function announceNavigationAuthority(
|
||||
reason,
|
||||
{ eventTarget = globalThis.window, cancelPendingSelection = true } = {},
|
||||
) {
|
||||
if (typeof eventTarget?.dispatchEvent !== 'function') return false;
|
||||
eventTarget.dispatchEvent(new CustomEvent(NAVIGATION_AUTHORITY_EVENT, {
|
||||
detail: {
|
||||
reason: String(reason || 'layer-focus'),
|
||||
cancelPendingSelection: Boolean(cancelPendingSelection),
|
||||
},
|
||||
}));
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register one authority listener and return an idempotent disposer.
|
||||
* @param {EventTarget} eventTarget Listener host.
|
||||
* @param {Function} listener Authority handler.
|
||||
* @returns {() => void} Idempotent disposer.
|
||||
*/
|
||||
export function registerNavigationAuthorityListener(eventTarget, listener) {
|
||||
if (!eventTarget?.addEventListener || !eventTarget?.removeEventListener
|
||||
|| typeof listener !== 'function') return () => {};
|
||||
eventTarget.addEventListener(NAVIGATION_AUTHORITY_EVENT, listener);
|
||||
let disposed = false;
|
||||
return () => {
|
||||
if (disposed) return;
|
||||
disposed = true;
|
||||
eventTarget.removeEventListener(NAVIGATION_AUTHORITY_EVENT, listener);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Let a physical globe gesture supersede the delayed shared camera and Follow.
|
||||
* Ordinary layer visibility/options remain authoritative, but camera intent
|
||||
* from the recipient always wins over a passive selected-subject restore.
|
||||
* @param {Function} stamp Navigation stamp callback.
|
||||
* @returns {*} The callback result, when present.
|
||||
*/
|
||||
export function stampInitialShareGesture(stamp) {
|
||||
return stamp?.({ cancelPendingSelection: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Run an immediate explicit camera navigation.
|
||||
* @param {Object} options
|
||||
* @returns {*} Navigation result, or false when disposed or Cockpit refuses.
|
||||
*/
|
||||
export function runExplicitNavigation({
|
||||
disposed = false,
|
||||
cockpitActive = false,
|
||||
noun = 'target',
|
||||
showToast,
|
||||
stamp,
|
||||
release,
|
||||
navigate,
|
||||
} = {}) {
|
||||
if (disposed) return false;
|
||||
if (cockpitActive) {
|
||||
showToast?.(`Exit cockpit to fly to a ${noun}`);
|
||||
return false;
|
||||
}
|
||||
const generation = stamp?.();
|
||||
release?.();
|
||||
return navigate?.(generation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept a deferred navigation intent without releasing the current owner.
|
||||
* @param {Object} options
|
||||
* @returns {number|false} Generation stamp, or false when disposed or Cockpit refuses.
|
||||
*/
|
||||
export function beginDeferredNavigation({
|
||||
disposed = false,
|
||||
cockpitActive = false,
|
||||
noun = 'location',
|
||||
showToast,
|
||||
stamp,
|
||||
} = {}) {
|
||||
if (disposed) return false;
|
||||
if (cockpitActive) {
|
||||
showToast?.(`Exit cockpit to fly to a ${noun}`);
|
||||
return false;
|
||||
}
|
||||
return stamp?.();
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-assert authority immediately before a deferred flight.
|
||||
* @param {Object} options
|
||||
* @returns {boolean} Whether the deferred flight still owns the camera.
|
||||
*/
|
||||
export function reassertNavigationHandoff({
|
||||
generation,
|
||||
currentGeneration,
|
||||
cockpitActive = false,
|
||||
disposed = false,
|
||||
showToast,
|
||||
release,
|
||||
} = {}) {
|
||||
if (disposed || generation !== currentGeneration) return false;
|
||||
if (cockpitActive) {
|
||||
showToast?.('Exit cockpit to fly to a location');
|
||||
return false;
|
||||
}
|
||||
release?.();
|
||||
return true;
|
||||
}
|
||||
Reference in new issue
Block a user