Story 5.2: Video Widget Width Customization

Implements configurable video widget widths for small and large dock layouts:
- Add widgetWidthSm and widgetWidthMd world settings (defaults: 83px, 150px)
- Add width dropdown selectors in Director's Board UI
- Apply width settings to participant avatars via CSS custom properties
- Update strip width/height calculations to use custom widths
- Add localization strings for widget width settings
- Add CSS styling for widget width dropdown controls
- Fix Handlebars template syntax for select element selected values

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
2026-05-25 12:43:06 +02:00
parent 748c7d7f85
commit 7a0d935239
15 changed files with 1967 additions and 19 deletions
+57 -1
View File
@@ -377,7 +377,19 @@ export class DirectorsBoard extends _AppBase {
isActive: l.key === currentDockLayout,
label: (typeof game !== 'undefined' ? game.i18n?.localize?.(`scrying-pool.directorsBoard.dockLayout.${l.key}`) : null) ?? l.key,
}));
// Story 5.2: Video widget width customization
const widgetWidthSm = this._adapter.settings?.get?.('widgetWidthSm') ?? '80';
const widgetWidthMd = this._adapter.settings?.get?.('widgetWidthMd') ?? '120';
const WIDTH_OPTIONS = [
{ value: '60', label: '60px' },
{ value: '80', label: '80px' },
{ value: '100', label: '100px' },
{ value: '120', label: '120px' },
{ value: '150', label: '150px' },
{ value: '200', label: '200px' },
];
return {
...base,
hasUndo: this._undoSnapshot !== null,
@@ -394,6 +406,10 @@ export class DirectorsBoard extends _AppBase {
avModeEnabled: (game.webrtc?.settings?.world?.mode ?? 0) !== 0,
// Dock layout selector
dockLayouts,
// Story 5.2: Video widget width customization
widthOptions: WIDTH_OPTIONS,
widgetWidthSm,
widgetWidthMd,
};
}
@@ -423,6 +439,19 @@ export class DirectorsBoard extends _AppBase {
const btn = e.target.closest('[data-action]');
if (!btn) return;
e.stopPropagation();
// Handle select element changes (Story 5.2)
if (e.target.tagName === 'SELECT') {
const action = e.target.dataset.action;
if (action === 'set-widget-width-sm') {
this._onSetWidgetWidth(e.target.value, 'sm');
return;
} else if (action === 'set-widget-width-md') {
this._onSetWidgetWidth(e.target.value, 'md');
return;
}
}
switch (btn.dataset.action) {
case 'toggle-participant': this._dispatchToggle(btn.dataset.userId); break;
case 'show-all': this.showAll(); break;
@@ -453,6 +482,16 @@ export class DirectorsBoard extends _AppBase {
root.addEventListener('focusin', this._focusinHandler);
root.addEventListener('keydown', this._keydownHandler);
// Story 5.2: Set selected values on widget width dropdowns
const smSelect = root.querySelector('select[data-action="set-widget-width-sm"]');
if (smSelect && context?.widgetWidthSm) {
smSelect.value = context.widgetWidthSm;
}
const mdSelect = root.querySelector('select[data-action="set-widget-width-md"]');
if (mdSelect && context?.widgetWidthMd) {
mdSelect.value = context.widgetWidthMd;
}
// Drag grip — custom drag (ApplicationV2 header is hidden)
const grip = root.querySelector('[data-action="drag-grip"]');
if (grip) {
@@ -695,6 +734,23 @@ export class DirectorsBoard extends _AppBase {
if (this.rendered) this.render({ force: true });
}
/**
* Sets the widget width for small or large tiles.
* Story 5.2: Video widget width customization
* @param {string} value - The width value (e.g., '80', '120')
* @param {'sm'|'md'} size - The size variant
*/
async _onSetWidgetWidth(value, size) {
if (!value) return;
const settingKey = size === 'sm' ? 'widgetWidthSm' : 'widgetWidthMd';
try {
await this._adapter.settings?.set?.(settingKey, value);
} catch (err) {
console.error('[ScryingPool] Failed to set widget width:', err);
}
if (this.rendered) this.render({ force: true });
}
/**
* Opens the PresetSaveDialog for saving the current visibility matrix as a preset.
*/
+39 -11
View File
@@ -215,6 +215,12 @@ export class ScryingPoolStrip extends _AppBase {
const isExpanded = dockLayout === 'vertical-md';
const showName = dockLayout.endsWith('-md');
// Story 5.2: Video widget width customization
const widgetWidthSm = this._adapter.settings?.get?.('widgetWidthSm') ?? '80';
const widgetWidthMd = this._adapter.settings?.get?.('widgetWidthMd') ?? '120';
const isLarge = effectiveSize === 'md';
const effectiveWidth = isLarge ? widgetWidthMd : widgetWidthSm;
const isGM = this._adapter.users.isGM?.() ?? false;
return {
@@ -226,6 +232,8 @@ export class ScryingPoolStrip extends _AppBase {
showFirstOpenTip: showFirstOpenTip && isGM,
hasStreamAccess,
isGM,
// Story 5.2: Video widget width customization
widgetWidth: effectiveWidth,
};
}
@@ -317,6 +325,18 @@ export class ScryingPoolStrip extends _AppBase {
}
}
/**
* Gets the widget width for a given layout size.
* Story 5.2: Video widget width customization
* @param {'sm'|'md'} size - The size variant
* @returns {number} Width in pixels.
*/
_getWidgetWidth(size) {
const value = this._adapter.settings?.get?.(`widgetWidth${size === 'md' ? 'Md' : 'Sm'}`);
// Defaults match settings defaults: 83px for sm, 150px for md
return typeof value === 'string' ? parseInt(value, 10) : (size === 'md' ? 150 : 83);
}
/**
* Computes the strip window width for a given dock layout and participant count.
* @param {string} layout - The dock layout key.
@@ -329,14 +349,18 @@ export class ScryingPoolStrip extends _AppBase {
const maxCols = 4;
const rowWidth = (tileSize, cols) =>
cols * tileSize + Math.max(0, cols - 1) * GAP + PAD + BORDER_W;
const smWidth = this._getWidgetWidth('sm');
const mdWidth = this._getWidgetWidth('md');
switch (layout) {
case 'vertical-sm': return 85; // 83px tile + 2px border
case 'vertical-md': return 242; // 240px strip + 2px border
case 'horizontal-sm': return rowWidth(83, Math.min(maxCols, n));
case 'horizontal-md': return rowWidth(150, Math.min(maxCols, n));
case 'mosaic-sm': return rowWidth(83, Math.min(maxCols, Math.ceil(Math.sqrt(n))));
case 'mosaic-md': return rowWidth(150, Math.min(maxCols, Math.ceil(Math.sqrt(n))));
default: return 85;
case 'vertical-sm': return smWidth + 2; // widget + 2px border
case 'vertical-md': return 242; // 240px strip + 2px border (expanded view has fixed width)
case 'horizontal-sm': return rowWidth(smWidth, Math.min(maxCols, n));
case 'horizontal-md': return rowWidth(mdWidth, Math.min(maxCols, n));
case 'mosaic-sm': return rowWidth(smWidth, Math.min(maxCols, Math.ceil(Math.sqrt(n))));
case 'mosaic-md': return rowWidth(mdWidth, Math.min(maxCols, Math.ceil(Math.sqrt(n))));
default: return smWidth + 2;
}
}
@@ -361,15 +385,19 @@ export class ScryingPoolStrip extends _AppBase {
const BORDER_H = 2; // 1px border top + 1px border bottom on app element
const GAP = 4, TILE_PAD = 8; // 4px padding each side in .sp-strip__participants
const maxCols = 4;
const smWidth = this._getWidgetWidth('sm');
const mdWidth = this._getWidgetWidth('md');
const gridHeight = (tileSize, cols) => {
const rows = Math.ceil(n / cols);
return rows * tileSize + Math.max(0, rows - 1) * GAP + TILE_PAD;
};
switch (layout) {
case 'horizontal-sm': return CHROME + gridHeight(83, Math.min(maxCols, n)) + BORDER_H;
case 'horizontal-md': return CHROME + gridHeight(150, Math.min(maxCols, n)) + BORDER_H;
case 'mosaic-sm': return CHROME + gridHeight(83, Math.min(maxCols, Math.ceil(Math.sqrt(n)))) + BORDER_H;
case 'mosaic-md': return CHROME + gridHeight(150, Math.min(maxCols, Math.ceil(Math.sqrt(n)))) + BORDER_H;
case 'horizontal-sm': return CHROME + gridHeight(smWidth, Math.min(maxCols, n)) + BORDER_H;
case 'horizontal-md': return CHROME + gridHeight(mdWidth, Math.min(maxCols, n)) + BORDER_H;
case 'mosaic-sm': return CHROME + gridHeight(smWidth, Math.min(maxCols, Math.ceil(Math.sqrt(n)))) + BORDER_H;
case 'mosaic-md': return CHROME + gridHeight(mdWidth, Math.min(maxCols, Math.ceil(Math.sqrt(n)))) + BORDER_H;
default: return 'auto';
}
}