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:
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user