feat(dashboard): integrate User Attributes widget with trackerConfig.rpgAttributes

Integrates the User Attributes Widget with upstream's new RPG attributes
customization system, enabling full attribute customization (add/remove/rename)
with bi-directional sync between widget and Tracker Editor.

Changes to userAttributesWidget.js:

1. render() method (lines 44-106):
   - Read from trackerConfig.userStats.rpgAttributes (not hardcoded)
   - Filter to enabled attributes only
   - Use custom attr.name for labels (e.g., "STRENGTH" vs "STR")
   - Support widget-level visibleAttrs filtering
   - Support legacy visibleStats config for backward compat
   - Fallback to default 6 attributes if no config

2. getConfig() method (lines 112-143):
   - Dynamically generate options from enabled attributes
   - Changed visibleStats → visibleAttrs (with legacy support)
   - Set default to null (show all enabled attributes)
   - Add hint: "To add/remove/rename attributes globally, use Tracker Settings"

3. getOptimalSize() method (lines 179-199):
   - Calculate height based on enabled attribute count (not hardcoded 6)
   - Respect widget-level visibleAttrs override if specified
   - Support legacy visibleStats parameter

4. Widget description updated:
   - Header docs: Added customization features, bi-directional sync
   - Registry description: "Customizable RPG attributes" instead of "Classic RPG stats"

Changes to dashboardManager.js:

1. shouldWidgetBeRemoved() (lines 1976-1984):
   - Add 'userAttributes' removal rule
   - Remove if showRPGAttributes === false
   - Remove if all attributes disabled

2. detectConfigChanges() (lines 1743-1752):
   - Detect when RPG Attributes section re-enabled
   - Detect when attributes re-enabled
   - Auto-add widget when conditions met

Integration Benefits:
 Custom attribute names (e.g., "STRENGTH", "AGILITY", "LUCK")
 Add custom attributes (e.g., "LCK", "PER", "APP")
 Remove unwanted default attributes
 Widget auto-updates when tracker config changes
 Widget auto-removed when section/attrs disabled
 Widget auto-added when section/attrs re-enabled
 Widget-level filtering (show subset of enabled attrs)
 Backward compatible with existing dashboards

Testing Required:
- Widget renders with default attributes
- Widget respects custom attribute names
- Widget supports custom attributes (e.g., adding "LCK")
- Widget removed when section disabled
- Widget re-added when section re-enabled
- +/- buttons work with custom attributes
- AI prompts use custom attribute names

Follows pattern from: userStatsWidget.js (lines 51-78)
Related: commit a02be34 (upstream merge)
This commit is contained in:
Lucas 'Paperboy' Rose-Winters
2025-11-04 10:00:07 +11:00
parent a02be34827
commit 724281b6bb
2 changed files with 96 additions and 28 deletions
+20
View File
@@ -1740,6 +1740,17 @@ export class DashboardManager {
console.log('[DashboardManager] Detected re-enabled userStats widget');
}
// Check userAttributes widget (enabled when RPG Attributes section is enabled AND at least one attribute is enabled)
const oldAttrsDisabled = oldConfig.userStats?.showRPGAttributes === false ||
(oldConfig.userStats?.rpgAttributes?.filter(a => a.enabled).length || 0) === 0;
const newAttrsEnabled = newConfig.userStats?.showRPGAttributes !== false &&
(newConfig.userStats?.rpgAttributes?.filter(a => a.enabled).length || 0) > 0;
if (oldAttrsDisabled && newAttrsEnabled) {
widgetsToAdd.push('userAttributes');
console.log('[DashboardManager] Detected re-enabled userAttributes widget');
}
// Check presentCharacters widget
const wasThoughtsDisabled = oldConfig.presentCharacters?.thoughts?.enabled === false;
const isThoughtsEnabled = newConfig.presentCharacters?.thoughts?.enabled !== false;
@@ -1973,6 +1984,15 @@ export class DashboardManager {
const customStats = config.userStats?.customStats || [];
return customStats.filter(s => s.enabled).length === 0;
},
'userAttributes': () => {
// Remove if RPG Attributes section is disabled
if (config.userStats?.showRPGAttributes === false) {
return true;
}
// Remove if all attributes are disabled
const rpgAttrs = config.userStats?.rpgAttributes || [];
return rpgAttrs.filter(attr => attr.enabled).length === 0;
},
'presentCharacters': () => config.presentCharacters?.thoughts?.enabled === false
};
@@ -1,14 +1,17 @@
/**
* User Attributes Widget
*
* Displays classic D&D-style attribute scores with +/- adjustment buttons.
* Shows STR, DEX, CON, INT, WIS, CHA stats.
* Displays customizable RPG attribute scores with +/- adjustment buttons.
* Integrates with Tracker Settings for full attribute customization.
*
* Features:
* - 6 classic RPG attributes
* - Fully customizable attributes (add/remove/rename via Tracker Settings)
* - Custom attribute names (e.g., "STRENGTH" instead of "STR", or add "LCK")
* - Widget-level filtering (show subset of globally enabled attributes)
* - +/- buttons for quick adjustments (1-20 range)
* - Responsive grid layout
* - Smart sizing: compact for narrow, grid for wide
* - Responsive 2-column grid layout
* - Smart sizing: auto-adjusts height based on attribute count
* - Bi-directional sync with Tracker Editor
*/
import { parseNumber } from '../widgetBase.js';
@@ -29,7 +32,7 @@ export function registerUserAttributesWidget(registry, dependencies) {
registry.register('userAttributes', {
name: 'User Attributes',
icon: '⚔️',
description: 'Classic RPG stats (STR, DEX, CON, INT, WIS, CHA)',
description: 'Customizable RPG attributes with +/- buttons (STR, DEX, etc.)',
category: 'user',
minSize: { w: 2, h: 2 },
defaultSize: { w: 2, h: 2 },
@@ -44,22 +47,48 @@ export function registerUserAttributesWidget(registry, dependencies) {
render(container, config = {}) {
const settings = getExtensionSettings();
const classicStats = settings.classicStats;
const trackerConfig = settings.trackerConfig?.userStats;
// Get globally enabled attributes from trackerConfig
const globallyEnabledAttrs = trackerConfig?.rpgAttributes
?.filter(attr => attr.enabled)
.map(attr => ({ id: attr.id, name: attr.name })) || [];
// If no globally enabled attrs, fall back to defaults
const availableAttrs = globallyEnabledAttrs.length > 0
? globallyEnabledAttrs
: [
{ id: 'str', name: 'STR' },
{ id: 'dex', name: 'DEX' },
{ id: 'con', name: 'CON' },
{ id: 'int', name: 'INT' },
{ id: 'wis', name: 'WIS' },
{ id: 'cha', name: 'CHA' }
];
// Apply widget-level filter if specified (support both visibleAttrs and legacy visibleStats)
let visibleAttrs = availableAttrs;
const filterList = config.visibleAttrs || config.visibleStats;
if (filterList && filterList.length > 0) {
visibleAttrs = availableAttrs.filter(attr =>
filterList.includes(attr.id)
);
}
// Merge default config
const finalConfig = {
visibleStats: ['str', 'dex', 'con', 'int', 'wis', 'cha'],
showLabels: true,
...config
};
// Build stats HTML
const statsHtml = finalConfig.visibleStats.map(stat => `
<div class="rpg-classic-stat" data-stat="${stat}">
${finalConfig.showLabels ? `<span class="rpg-classic-stat-label">${stat.toUpperCase()}</span>` : ''}
// Build stats HTML using custom names from trackerConfig
const statsHtml = visibleAttrs.map(attr => `
<div class="rpg-classic-stat" data-stat="${attr.id}">
${finalConfig.showLabels ? `<span class="rpg-classic-stat-label">${attr.name}</span>` : ''}
<div class="rpg-classic-stat-buttons">
<button class="rpg-classic-stat-btn rpg-stat-decrease" data-stat="${stat}"></button>
<span class="rpg-classic-stat-value">${classicStats[stat]}</span>
<button class="rpg-classic-stat-btn rpg-stat-increase" data-stat="${stat}">+</button>
<button class="rpg-classic-stat-btn rpg-stat-decrease" data-stat="${attr.id}"></button>
<span class="rpg-classic-stat-value">${classicStats[attr.id] || 10}</span>
<button class="rpg-classic-stat-btn rpg-stat-increase" data-stat="${attr.id}">+</button>
</div>
</div>
`).join('');
@@ -84,19 +113,29 @@ export function registerUserAttributesWidget(registry, dependencies) {
* @returns {Object} Configuration schema
*/
getConfig() {
const settings = getExtensionSettings();
const trackerConfig = settings.trackerConfig?.userStats;
// Get enabled attributes from trackerConfig for options
const enabledAttrs = trackerConfig?.rpgAttributes
?.filter(attr => attr.enabled)
.map(attr => ({ value: attr.id, label: attr.name })) || [
{ value: 'str', label: 'STR' },
{ value: 'dex', label: 'DEX' },
{ value: 'con', label: 'CON' },
{ value: 'int', label: 'INT' },
{ value: 'wis', label: 'WIS' },
{ value: 'cha', label: 'CHA' }
];
return {
visibleStats: {
visibleAttrs: {
type: 'multiselect',
label: 'Visible Attributes',
default: ['str', 'dex', 'con', 'int', 'wis', 'cha'],
options: [
{ value: 'str', label: 'Strength (STR)' },
{ value: 'dex', label: 'Dexterity (DEX)' },
{ value: 'con', label: 'Constitution (CON)' },
{ value: 'int', label: 'Intelligence (INT)' },
{ value: 'wis', label: 'Wisdom (WIS)' },
{ value: 'cha', label: 'Charisma (CHA)' }
]
default: null, // null means "show all enabled attributes"
options: enabledAttrs,
description: 'Select which attributes to show in this widget (leave empty to show all enabled attributes)',
hint: 'To add/remove/rename attributes globally, use Tracker Settings'
},
showLabels: {
type: 'boolean',
@@ -141,11 +180,20 @@ export function registerUserAttributesWidget(registry, dependencies) {
* @returns {Object} Optimal size { w, h }
*/
getOptimalSize(config = {}) {
const visibleStatCount = config.visibleStats?.length || 6;
const settings = getExtensionSettings();
const trackerConfig = settings.trackerConfig?.userStats;
// Each stat needs ~0.35 rows in 2-column grid
// For 6 stats: 3 rows (0.5 row padding = 3.5 total)
const optimalHeight = Math.ceil((visibleStatCount / 2) * 0.7 + 0.5);
// Count globally enabled attributes
const globallyEnabledCount = trackerConfig?.rpgAttributes
?.filter(attr => attr.enabled).length || 6;
// If widget has visibleAttrs override, use that count (support legacy visibleStats too)
const filterList = config.visibleAttrs || config.visibleStats;
const visibleAttrCount = filterList?.length || globallyEnabledCount;
// Each attribute needs ~0.35 rows in 2-column grid
// For 6 attrs: 3 rows (0.5 row padding = 3.5 total)
const optimalHeight = Math.ceil((visibleAttrCount / 2) * 0.7 + 0.5);
return {
w: 2, // Prefer 2-column grid layout