Video over token, free-form video windows
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* GMActorMappingPanel — GM settings submenu for assigning actors to users
|
||||
* for the Token Video Overlay feature.
|
||||
*
|
||||
* The GM maps an actor to a user's webcam feed. When "Show Webcam Video on
|
||||
* Tokens" is enabled, any token of the mapped actor will display that user's
|
||||
* webcam instead of the token icon.
|
||||
*
|
||||
* Extends ApplicationV2 via HandlebarsApplicationMixin.
|
||||
* Uses module-level _adapter for DI (same pattern as PlayerPrivacyPanelMenu).
|
||||
*
|
||||
* @module ui/gm/GMActorMappingPanel
|
||||
*/
|
||||
|
||||
/** @type {import('../../foundry/FoundryAdapter.js').FoundryAdapter|null} */
|
||||
let _adapter = null;
|
||||
|
||||
/**
|
||||
* Initialize static adapter reference. Called once from module.js ready hook.
|
||||
* @param {import('../../foundry/FoundryAdapter.js').FoundryAdapter} adapter
|
||||
*/
|
||||
export function initGMActorMappingPanel(adapter) {
|
||||
_adapter = adapter;
|
||||
}
|
||||
|
||||
const _AppBase =
|
||||
typeof foundry !== 'undefined' &&
|
||||
foundry.applications?.api?.HandlebarsApplicationMixin &&
|
||||
foundry.applications?.api?.ApplicationV2
|
||||
? foundry.applications.api.HandlebarsApplicationMixin(
|
||||
foundry.applications.api.ApplicationV2
|
||||
)
|
||||
: class _FallbackApp {
|
||||
static DEFAULT_OPTIONS = {};
|
||||
static PARTS = {};
|
||||
get rendered() { return this._rendered ?? false; }
|
||||
set rendered(v) { this._rendered = v; }
|
||||
get element() { return this._element ?? null; }
|
||||
set element(v) { this._element = v; }
|
||||
async render() { this._rendered = true; }
|
||||
async close() { this._rendered = false; }
|
||||
async _prepareContext() { return {}; }
|
||||
_onRender() {}
|
||||
_onClose() {}
|
||||
_onPosition() {}
|
||||
};
|
||||
|
||||
/**
|
||||
* GM Actor Mapping Panel — assign actors to user webcams for token overlay.
|
||||
*/
|
||||
export class GMActorMappingPanel extends _AppBase {
|
||||
static DEFAULT_OPTIONS = {
|
||||
id: 'scrying-pool-actor-mapping',
|
||||
classes: ['scrying-pool', 'actor-mapping'],
|
||||
window: {
|
||||
title: 'SCRYING_POOL.ActorMapping.title',
|
||||
resizable: false,
|
||||
width: 450,
|
||||
height: 'auto',
|
||||
},
|
||||
position: {},
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
form: {
|
||||
template: 'modules/scrying-pool/templates/actor-mapping.hbs',
|
||||
},
|
||||
};
|
||||
|
||||
async _prepareContext() {
|
||||
const mapping = _adapter?.settings?.get('userActorMapping') ?? {};
|
||||
const selectedId = (uid) => mapping[uid] ?? '';
|
||||
|
||||
const sortedActors = _adapter.actors.all()
|
||||
.map(a => ({ id: a.id, name: a.name }))
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
const users = _adapter.users.all()
|
||||
.map(u => ({
|
||||
id: u.id,
|
||||
name: u.name,
|
||||
isGM: u.isGM,
|
||||
avatar: u.avatar ?? 'icons/svg/mystery-man.svg',
|
||||
actors: sortedActors.map(a => ({
|
||||
id: a.id,
|
||||
name: a.name,
|
||||
selected: a.id === selectedId(u.id),
|
||||
})),
|
||||
}))
|
||||
.sort((a, b) => {
|
||||
if (a.isGM && !b.isGM) return -1;
|
||||
if (!a.isGM && b.isGM) return 1;
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
|
||||
return {
|
||||
hasNoUsers: users.length === 0,
|
||||
users,
|
||||
};
|
||||
}
|
||||
|
||||
_onRender(context, options) {
|
||||
if (this._formHandlerAttached) return;
|
||||
this._formHandlerAttached = true;
|
||||
|
||||
const form = this.element?.querySelector('form');
|
||||
if (!form) return;
|
||||
|
||||
form.addEventListener('change', (event) => {
|
||||
const select = event.target;
|
||||
if (select.tagName !== 'SELECT') return;
|
||||
|
||||
const mapping = { ...(_adapter?.settings?.get('userActorMapping') ?? {}) };
|
||||
if (select.value) {
|
||||
mapping[select.name] = select.value;
|
||||
} else {
|
||||
delete mapping[select.name];
|
||||
}
|
||||
_adapter?.settings?.set('userActorMapping', mapping).catch(err => {
|
||||
console.error('[ScryingPool] Failed to save actor mapping:', err);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user