Merge pull request #42 from SpicyMarinara/revert-40-feat/responsive-dashboard-layout
Revert "feat: responsive dashboard layout"
This commit is contained in:
@@ -27,7 +27,6 @@ import { registerSceneInfoWidget } from './widgets/sceneInfoWidget.js';
|
||||
import { registerPresentCharactersWidget } from './widgets/presentCharactersWidget.js';
|
||||
import { registerInventoryWidget } from './widgets/inventoryWidget.js';
|
||||
import { registerQuestsWidget } from './widgets/questsWidget.js';
|
||||
import { registerUserSkillsWidget } from './widgets/userSkillsWidget.js';
|
||||
|
||||
// Global dashboard manager instance
|
||||
let dashboardManager = null;
|
||||
@@ -255,9 +254,6 @@ function registerAllWidgets(registry, dependencies) {
|
||||
// Quest widget
|
||||
registerQuestsWidget(registry, dependencies);
|
||||
|
||||
// Skills widget
|
||||
registerUserSkillsWidget(registry, dependencies);
|
||||
|
||||
console.log(`[RPG Companion] Registered ${registry.getAll().length} widgets`);
|
||||
}
|
||||
|
||||
@@ -339,16 +335,24 @@ function setupDashboardEventListeners(dependencies) {
|
||||
if (dashboardManager && dashboardManager.editManager) {
|
||||
console.log('[RPG Companion] Lock button clicked');
|
||||
dashboardManager.editManager.toggleLock();
|
||||
// Refresh header overflow menu to reflect lock button state change
|
||||
if (headerOverflowManager) {
|
||||
setTimeout(() => headerOverflowManager.refresh(), 50);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Tracker Settings button now uses ID 'rpg-open-tracker-editor'
|
||||
// Event handler is in trackerEditor.js using jQuery delegation
|
||||
// Tracker Settings button (open tracker editor modal)
|
||||
const trackerSettingsBtn = document.querySelector('#rpg-dashboard-tracker-settings');
|
||||
if (trackerSettingsBtn) {
|
||||
trackerSettingsBtn.addEventListener('click', () => {
|
||||
console.log('[RPG Companion] Tracker Settings button clicked');
|
||||
// Trigger the tracker editor button from main UI
|
||||
const trackerEditorBtn = document.getElementById('rpg-open-tracker-editor');
|
||||
if (trackerEditorBtn) {
|
||||
trackerEditorBtn.click();
|
||||
} else {
|
||||
console.warn('[RPG Companion] Tracker editor button not found');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Done button (exit edit mode)
|
||||
const doneBtn = document.querySelector('#rpg-dashboard-done-edit');
|
||||
|
||||
@@ -185,8 +185,18 @@ export class DashboardManager {
|
||||
});
|
||||
|
||||
// Initialize Tab Manager with dashboard data structure
|
||||
// Note: Tabs will be populated by loadLayout() which runs after init()
|
||||
// Default layout is set via setDefaultLayout() before init() is called
|
||||
// Create default tab if no tabs exist
|
||||
if (this.dashboard.tabs.length === 0) {
|
||||
this.dashboard.tabs.push({
|
||||
id: 'main',
|
||||
name: 'Main',
|
||||
icon: 'fa-solid fa-house',
|
||||
order: 0,
|
||||
widgets: []
|
||||
});
|
||||
this.dashboard.defaultTab = 'main';
|
||||
}
|
||||
|
||||
this.tabManager = new TabManager(this.dashboard);
|
||||
|
||||
// Set current tab to active tab from TabManager
|
||||
@@ -949,8 +959,7 @@ export class DashboardManager {
|
||||
scene: [],
|
||||
social: [],
|
||||
inventory: [],
|
||||
quests: [],
|
||||
skills: []
|
||||
quests: []
|
||||
};
|
||||
|
||||
widgets.forEach(widget => {
|
||||
@@ -1032,19 +1041,6 @@ export class DashboardManager {
|
||||
this.gridEngine.autoLayout(groups.quests, { preserveOrder: true });
|
||||
}
|
||||
|
||||
// Create Skills tab if there are skills widgets
|
||||
if (groups.skills.length > 0) {
|
||||
this.dashboard.tabs.push({
|
||||
id: 'tab-skills',
|
||||
name: 'Skills',
|
||||
icon: 'fa-solid fa-book',
|
||||
order: 5,
|
||||
widgets: groups.skills
|
||||
});
|
||||
|
||||
this.gridEngine.autoLayout(groups.skills, { preserveOrder: true });
|
||||
}
|
||||
|
||||
console.log('[DashboardManager] Created', this.dashboard.tabs.length, 'tabs');
|
||||
|
||||
// Re-render tabs and switch to first tab
|
||||
@@ -1084,8 +1080,7 @@ export class DashboardManager {
|
||||
'social': 3,
|
||||
'inventory': 4,
|
||||
'quests': 5,
|
||||
'skills': 6,
|
||||
'other': 7
|
||||
'other': 6
|
||||
};
|
||||
|
||||
// Specific widget type ordering within user category
|
||||
@@ -1490,29 +1485,32 @@ export class DashboardManager {
|
||||
|
||||
/**
|
||||
* Load saved layout
|
||||
*
|
||||
* For first-run users (no saved layout), calls resetLayout() for comprehensive
|
||||
* initialization. This ensures consistent behavior between first-run and manual
|
||||
* reset, using a single code path for default layout setup.
|
||||
*/
|
||||
async loadLayout() {
|
||||
try {
|
||||
const saved = await this.persistence.loadLayout();
|
||||
if (saved) {
|
||||
console.log('[DashboardManager] Loading saved layout');
|
||||
this.applyDashboardConfig(saved);
|
||||
} else {
|
||||
// First run - use resetLayout() for comprehensive initialization
|
||||
// This provides: fresh layout generation, state reset, validation,
|
||||
// column-aware sizing, and proper UI rendering
|
||||
console.log('[DashboardManager] No saved layout found, calling resetLayout() for first-run initialization');
|
||||
await this.resetLayout();
|
||||
} else if (this.defaultLayout) {
|
||||
console.log('[DashboardManager] No saved layout, using default with auto-layout');
|
||||
this.applyDashboardConfig(this.defaultLayout);
|
||||
|
||||
// Auto-layout each tab to prevent overlap (default positions may not fit screen)
|
||||
this.dashboard.tabs.forEach(tab => {
|
||||
if (tab.widgets && tab.widgets.length > 0) {
|
||||
console.log(`[DashboardManager] Auto-laying out default tab "${tab.name}" (${tab.widgets.length} widgets)`);
|
||||
this.gridEngine.autoLayout(tab.widgets, { preserveOrder: true });
|
||||
}
|
||||
});
|
||||
|
||||
// Save the auto-laid-out default as the initial saved layout
|
||||
await this.saveLayout(true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[DashboardManager] Failed to load layout:', error);
|
||||
// Fallback: use resetLayout() for clean state recovery
|
||||
console.log('[DashboardManager] Recovering with resetLayout()');
|
||||
await this.resetLayout();
|
||||
if (this.defaultLayout) {
|
||||
this.applyDashboardConfig(this.defaultLayout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1564,8 +1562,7 @@ export class DashboardManager {
|
||||
// Skip initial switch in applyDashboardConfig since we'll switch after layout calculations
|
||||
this.applyDashboardConfig(this.defaultLayout, { skipInitialSwitch: true });
|
||||
|
||||
// Apply column-aware widget sizes from widget definitions
|
||||
// This makes widgets scale properly based on screen width (2-4 columns)
|
||||
// Reset all widgets to default sizes
|
||||
const allWidgets = [];
|
||||
this.dashboard.tabs.forEach(tab => {
|
||||
if (tab.widgets && tab.widgets.length > 0) {
|
||||
@@ -1574,10 +1571,13 @@ export class DashboardManager {
|
||||
});
|
||||
this.resetWidgetSizesToDefault(allWidgets);
|
||||
|
||||
// Don't call autoLayout - preserve positions from defaultLayout.js
|
||||
// Widget definitions now have column-aware sizes (defaultSize returns correct size for column count)
|
||||
// ResizeObserver will handle column changes and trigger autoLayout when screen resizes
|
||||
console.log('[DashboardManager] Using column-aware sizes from widget definitions, preserving positions from defaultLayout.js');
|
||||
// Auto-layout each tab to prevent overlap (default positions may have changed)
|
||||
this.dashboard.tabs.forEach(tab => {
|
||||
if (tab.widgets && tab.widgets.length > 0) {
|
||||
console.log(`[DashboardManager] Auto-laying out tab "${tab.name}" (${tab.widgets.length} widgets)`);
|
||||
this.gridEngine.autoLayout(tab.widgets, { preserveOrder: true });
|
||||
}
|
||||
});
|
||||
|
||||
// Force re-render tabs
|
||||
this.renderTabs();
|
||||
@@ -1631,146 +1631,6 @@ export class DashboardManager {
|
||||
console.log(`[DashboardManager] Reset ${resetCount} widgets to default sizes`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to apply default layout positions to current tab
|
||||
*
|
||||
* Checks if the current tab's widgets match the default layout and applies
|
||||
* the default positions if they do. This ensures "Sort Current Page" produces
|
||||
* the same layout as "Reset Layout" for default widgets.
|
||||
*
|
||||
* @param {Object} tab - Tab to apply default layout to
|
||||
* @param {Object} options - Layout options
|
||||
* @returns {boolean} True if default layout was applied, false otherwise
|
||||
*/
|
||||
tryApplyDefaultLayoutToTab(tab, options = {}) {
|
||||
if (!this.defaultLayout || !this.defaultLayout.tabs) {
|
||||
console.log('[DashboardManager] No default layout available');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find matching default tab by ID
|
||||
const defaultTab = this.defaultLayout.tabs.find(t => t.id === tab.id);
|
||||
if (!defaultTab) {
|
||||
console.log(`[DashboardManager] No default layout for tab "${tab.name}" (${tab.id})`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if widgets match (same types, possibly different IDs)
|
||||
const currentTypes = tab.widgets.map(w => w.type).sort();
|
||||
const defaultTypes = defaultTab.widgets.map(w => w.type).sort();
|
||||
|
||||
if (currentTypes.length !== defaultTypes.length ||
|
||||
!currentTypes.every((type, i) => type === defaultTypes[i])) {
|
||||
console.log('[DashboardManager] Tab widgets do not match default layout (custom widgets present)');
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log('[DashboardManager] Applying default layout positions to current tab');
|
||||
|
||||
// Reset widget sizes to defaults (unless explicitly disabled)
|
||||
if (options.resetSizes !== false) {
|
||||
this.resetWidgetSizesToDefault(tab.widgets);
|
||||
}
|
||||
|
||||
// Apply default positions to each widget
|
||||
tab.widgets.forEach(widget => {
|
||||
const defaultWidget = defaultTab.widgets.find(w => w.type === widget.type);
|
||||
if (defaultWidget) {
|
||||
widget.x = defaultWidget.x;
|
||||
widget.y = defaultWidget.y;
|
||||
// Size is already set by resetWidgetSizesToDefault
|
||||
console.log(`[DashboardManager] Set ${widget.type} to default position (${widget.x}, ${widget.y})`);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to apply default layout to all tabs
|
||||
*
|
||||
* Checks if the current dashboard widgets match the default layout and applies
|
||||
* the default positions if they do. This ensures "Auto Arrange" produces
|
||||
* the same layout as "Reset Layout" for default widgets.
|
||||
*
|
||||
* @param {Object} options - Layout options
|
||||
* @returns {boolean} True if default layout was applied, false otherwise
|
||||
*/
|
||||
tryApplyDefaultLayout(options = {}) {
|
||||
if (!this.defaultLayout || !this.defaultLayout.tabs) {
|
||||
console.log('[DashboardManager] No default layout available');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if tabs match default layout
|
||||
if (this.dashboard.tabs.length !== this.defaultLayout.tabs.length) {
|
||||
console.log('[DashboardManager] Tab count does not match default layout');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if all tabs and widgets match
|
||||
for (let i = 0; i < this.dashboard.tabs.length; i++) {
|
||||
const tab = this.dashboard.tabs[i];
|
||||
const defaultTab = this.defaultLayout.tabs.find(t => t.id === tab.id);
|
||||
|
||||
if (!defaultTab) {
|
||||
console.log(`[DashboardManager] No default tab found for "${tab.name}" (${tab.id})`);
|
||||
return false;
|
||||
}
|
||||
|
||||
const currentTypes = tab.widgets.map(w => w.type).sort();
|
||||
const defaultTypes = defaultTab.widgets.map(w => w.type).sort();
|
||||
|
||||
if (currentTypes.length !== defaultTypes.length ||
|
||||
!currentTypes.every((type, j) => type === defaultTypes[j])) {
|
||||
console.log(`[DashboardManager] Tab "${tab.name}" widgets do not match default layout`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('[DashboardManager] Applying default layout positions to all tabs');
|
||||
|
||||
// Gather all widgets from all tabs
|
||||
const allWidgets = [];
|
||||
this.dashboard.tabs.forEach(tab => {
|
||||
if (tab.widgets && tab.widgets.length > 0) {
|
||||
allWidgets.push(...tab.widgets);
|
||||
}
|
||||
});
|
||||
|
||||
// Reset widget sizes to defaults (unless explicitly disabled)
|
||||
if (options.resetSizes !== false) {
|
||||
this.resetWidgetSizesToDefault(allWidgets);
|
||||
}
|
||||
|
||||
// Apply default positions to each tab
|
||||
this.dashboard.tabs.forEach(tab => {
|
||||
const defaultTab = this.defaultLayout.tabs.find(t => t.id === tab.id);
|
||||
if (defaultTab) {
|
||||
tab.widgets.forEach(widget => {
|
||||
const defaultWidget = defaultTab.widgets.find(w => w.type === widget.type);
|
||||
if (defaultWidget) {
|
||||
widget.x = defaultWidget.x;
|
||||
widget.y = defaultWidget.y;
|
||||
console.log(`[DashboardManager] Set ${widget.type} to default position (${widget.x}, ${widget.y})`);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Re-render tabs and switch to first tab
|
||||
this.renderTabs();
|
||||
if (this.dashboard.tabs.length > 0) {
|
||||
this.switchTab(this.dashboard.tabs[0].id);
|
||||
}
|
||||
|
||||
// Save layout
|
||||
this.triggerAutoSave();
|
||||
|
||||
console.log('[DashboardManager] Default layout applied successfully');
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-layout widgets on current tab only
|
||||
* Sorts and arranges widgets on the current tab to maximize space usage
|
||||
@@ -1796,13 +1656,6 @@ export class DashboardManager {
|
||||
|
||||
console.log(`[DashboardManager] Laying out ${currentTab.widgets.length} widgets on tab "${currentTab.name}"`);
|
||||
|
||||
// Check if we can use default layout positions
|
||||
const useDefaultLayout = this.tryApplyDefaultLayoutToTab(currentTab, options);
|
||||
|
||||
if (!useDefaultLayout) {
|
||||
// Fallback to traditional auto-layout
|
||||
console.log('[DashboardManager] Using gridEngine.autoLayout (custom widgets or no default layout)');
|
||||
|
||||
// Reset widget sizes to defaults (unless explicitly disabled)
|
||||
if (options.resetSizes !== false) {
|
||||
this.resetWidgetSizesToDefault(currentTab.widgets);
|
||||
@@ -1837,7 +1690,6 @@ export class DashboardManager {
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Re-render all widgets with new positions
|
||||
this.clearGrid();
|
||||
@@ -1869,13 +1721,6 @@ export class DashboardManager {
|
||||
console.log('[DashboardManager] ===== AUTO-LAYOUT WIDGETS CALLED =====');
|
||||
console.log('[DashboardManager] Auto-layout widgets requested');
|
||||
|
||||
// Check if we can use default layout
|
||||
const useDefaultLayout = this.tryApplyDefaultLayout(options);
|
||||
|
||||
if (!useDefaultLayout) {
|
||||
// Fallback to traditional auto-layout
|
||||
console.log('[DashboardManager] Using traditional auto-layout (custom widgets or no default layout)');
|
||||
|
||||
// Gather ALL widgets from ALL tabs (don't lose inventory, social, etc.)
|
||||
const allWidgets = [];
|
||||
this.dashboard.tabs.forEach(tab => {
|
||||
@@ -1913,7 +1758,6 @@ export class DashboardManager {
|
||||
|
||||
// distributeWidgetsByCategory handles rendering and tab switching
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger auto-save
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<i class="fa-solid fa-pen-to-square"></i>
|
||||
</button>
|
||||
|
||||
<button id="rpg-open-tracker-editor" class="rpg-dashboard-btn rpg-tracker-settings-btn rpg-priority-btn" title="Tracker Settings" aria-label="Tracker settings">
|
||||
<button id="rpg-dashboard-tracker-settings" class="rpg-dashboard-btn rpg-tracker-settings-btn rpg-priority-btn" title="Tracker Settings - Customize fields, names, and AI instructions" aria-label="Tracker settings">
|
||||
<i class="fa-solid fa-sliders"></i>
|
||||
</button>
|
||||
|
||||
|
||||
@@ -39,46 +39,45 @@ export function generateDefaultDashboard() {
|
||||
icon: 'fa-solid fa-user',
|
||||
order: 0,
|
||||
widgets: [
|
||||
// Row 0-1: User Info (left column, vertical)
|
||||
// Row 0: User Info (left) + User Mood (top right in 3-col)
|
||||
{
|
||||
id: 'widget-userinfo',
|
||||
type: 'userInfo',
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 1,
|
||||
h: 2,
|
||||
w: 2,
|
||||
h: 1,
|
||||
config: {}
|
||||
},
|
||||
// Row 0-2: User Stats (right side, tall, 2 cols wide)
|
||||
{
|
||||
id: 'widget-userstats',
|
||||
type: 'userStats',
|
||||
x: 1,
|
||||
y: 0,
|
||||
w: 2,
|
||||
h: 3,
|
||||
config: {
|
||||
statBarGradient: true
|
||||
}
|
||||
},
|
||||
// Row 2: User Mood (below user info, left column)
|
||||
{
|
||||
id: 'widget-usermood',
|
||||
type: 'userMood',
|
||||
x: 0,
|
||||
y: 2,
|
||||
x: 2,
|
||||
y: 0,
|
||||
w: 1,
|
||||
h: 1,
|
||||
config: {}
|
||||
},
|
||||
// Row 3-6: User Attributes (full width below everything, 3 cols wide)
|
||||
// Row 1-2: User Stats (health/energy bars)
|
||||
{
|
||||
id: 'widget-userstats',
|
||||
type: 'userStats',
|
||||
x: 0,
|
||||
y: 1,
|
||||
w: 2,
|
||||
h: 2,
|
||||
config: {
|
||||
statBarGradient: true
|
||||
}
|
||||
},
|
||||
// Row 3-4: User Attributes
|
||||
{
|
||||
id: 'widget-userattributes',
|
||||
type: 'userAttributes',
|
||||
x: 0,
|
||||
y: 3,
|
||||
w: 3,
|
||||
h: 4,
|
||||
w: 2,
|
||||
h: 2,
|
||||
config: {}
|
||||
}
|
||||
]
|
||||
@@ -90,36 +89,36 @@ export function generateDefaultDashboard() {
|
||||
icon: 'fa-solid fa-map',
|
||||
order: 1,
|
||||
widgets: [
|
||||
// Row 0-2: Scene Info (combined: calendar, weather, temp, clock, location)
|
||||
// Row 0-1: Scene Info (combined: calendar, weather, temp, clock, location)
|
||||
{
|
||||
id: 'widget-sceneinfo',
|
||||
type: 'sceneInfo',
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 3,
|
||||
h: 3,
|
||||
w: 2,
|
||||
h: 2,
|
||||
config: {}
|
||||
},
|
||||
// Row 3-4: Recent Events (notebook style, full width)
|
||||
// Row 2-3: Recent Events (notebook style, full width)
|
||||
{
|
||||
id: 'widget-recentevents',
|
||||
type: 'recentEvents',
|
||||
x: 0,
|
||||
y: 3,
|
||||
w: 3,
|
||||
y: 2,
|
||||
w: 2,
|
||||
h: 2,
|
||||
config: {
|
||||
maxEvents: 3
|
||||
}
|
||||
},
|
||||
// Row 5-6: Present Characters (full width, fits 1080p screen)
|
||||
// Row 4-7: Present Characters (full width, will expand with auto-layout)
|
||||
{
|
||||
id: 'widget-presentchars',
|
||||
type: 'presentCharacters',
|
||||
x: 0,
|
||||
y: 5,
|
||||
w: 3,
|
||||
h: 2,
|
||||
y: 4,
|
||||
w: 2,
|
||||
h: 4,
|
||||
config: {
|
||||
cardLayout: 'grid',
|
||||
showThoughtBubbles: true
|
||||
@@ -167,29 +166,6 @@ export function generateDefaultDashboard() {
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
// Tab 5: Skills (Full tab for skills system)
|
||||
{
|
||||
id: 'tab-skills',
|
||||
name: 'Skills',
|
||||
icon: 'fa-solid fa-book',
|
||||
order: 4,
|
||||
widgets: [
|
||||
{
|
||||
id: 'widget-userskills',
|
||||
type: 'userSkills',
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 3,
|
||||
h: 7,
|
||||
config: {
|
||||
defaultSubTab: 'all',
|
||||
showXP: true,
|
||||
showCategories: true,
|
||||
maxLevel: 10
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
|
||||
@@ -336,6 +336,15 @@ export class EditModeManager {
|
||||
controls.style.opacity = '0';
|
||||
controls.style.transition = 'opacity 0.2s';
|
||||
|
||||
// Settings button
|
||||
const settingsBtn = this.createControlButton('⚙', 'Settings');
|
||||
settingsBtn.onclick = (e) => {
|
||||
e.stopPropagation();
|
||||
if (this.onWidgetSettings) {
|
||||
this.onWidgetSettings(widgetId);
|
||||
}
|
||||
};
|
||||
|
||||
// Delete button
|
||||
const deleteBtn = this.createControlButton('×', 'Delete');
|
||||
deleteBtn.onclick = (e) => {
|
||||
@@ -344,6 +353,7 @@ export class EditModeManager {
|
||||
};
|
||||
deleteBtn.style.background = '#e94560';
|
||||
|
||||
controls.appendChild(settingsBtn);
|
||||
controls.appendChild(deleteBtn);
|
||||
|
||||
// Store reference to widget element for positioning
|
||||
|
||||
@@ -529,19 +529,7 @@ export function registerRecentEventsWidget(registry, dependencies) {
|
||||
description: 'Recent events notebook',
|
||||
category: 'scene',
|
||||
minSize: { w: 2, h: 2 },
|
||||
// Column-aware sizing: full width at all sizes
|
||||
defaultSize: (columns) => {
|
||||
if (columns <= 2) {
|
||||
return { w: 2, h: 2 }; // Mobile: 2 cols wide (full), 2 rows
|
||||
}
|
||||
return { w: 3, h: 2 }; // Desktop: 3 cols wide (full), 2 rows
|
||||
},
|
||||
maxAutoSize: (columns) => {
|
||||
if (columns <= 2) {
|
||||
return { w: 2, h: 3 };
|
||||
}
|
||||
return { w: 3, h: 3 };
|
||||
},
|
||||
defaultSize: { w: 2, h: 2 },
|
||||
requiresSchema: false,
|
||||
|
||||
/**
|
||||
@@ -550,40 +538,7 @@ export function registerRecentEventsWidget(registry, dependencies) {
|
||||
* @param {Object} config - Widget configuration
|
||||
*/
|
||||
render(container, config = {}) {
|
||||
const { getInfoBoxData, getExtensionSettings } = dependencies;
|
||||
|
||||
// Check if Recent Events is enabled in tracker config
|
||||
const settings = getExtensionSettings();
|
||||
const trackerConfig = settings.trackerConfig;
|
||||
const isEnabled = trackerConfig?.infoBox?.widgets?.recentEvents?.enabled !== false;
|
||||
|
||||
// If disabled, show helpful message
|
||||
if (!isEnabled) {
|
||||
const html = `
|
||||
<div class="rpg-dashboard-widget">
|
||||
<div class="rpg-events-widget rpg-widget-disabled">
|
||||
<div class="rpg-notebook-header">
|
||||
<div class="rpg-notebook-ring"></div>
|
||||
<div class="rpg-notebook-ring"></div>
|
||||
<div class="rpg-notebook-ring"></div>
|
||||
</div>
|
||||
<div class="rpg-notebook-title">Recent Events</div>
|
||||
<div class="rpg-widget-disabled-message">
|
||||
<i class="fa-solid fa-circle-info"></i>
|
||||
<p>Recent Events tracking is currently disabled.</p>
|
||||
<button class="rpg-widget-enable-btn" data-widget-type="recentEvents">
|
||||
<i class="fa-solid fa-toggle-on"></i>
|
||||
Enable in Tracker Settings
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
container.innerHTML = html;
|
||||
attachDisabledStateHandlers(container);
|
||||
return;
|
||||
}
|
||||
|
||||
const { getInfoBoxData } = dependencies;
|
||||
const data = parseInfoBoxData(getInfoBoxData());
|
||||
|
||||
// Merge default config with user config
|
||||
@@ -800,31 +755,3 @@ function updateRecentEvent(eventIndex, value, dependencies) {
|
||||
|
||||
console.log(`[Recent Events Widget] Updated event ${eventIndex}: "${value}"`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach handlers for disabled widget state
|
||||
* Opens Tracker Settings when "Enable" button is clicked
|
||||
* @private
|
||||
*/
|
||||
function attachDisabledStateHandlers(container) {
|
||||
const enableBtn = container.querySelector('.rpg-widget-enable-btn');
|
||||
if (enableBtn) {
|
||||
enableBtn.addEventListener('click', () => {
|
||||
// Open Tracker Settings modal
|
||||
const trackerSettingsBtn = document.querySelector('#rpg-open-tracker-editor');
|
||||
if (trackerSettingsBtn) {
|
||||
trackerSettingsBtn.click();
|
||||
|
||||
// After modal opens, switch to Info Box tab
|
||||
setTimeout(() => {
|
||||
const infoBoxTab = document.querySelector('.rpg-editor-tab[data-tab="infoBox"]');
|
||||
if (infoBoxTab) {
|
||||
infoBoxTab.click();
|
||||
}
|
||||
}, 100);
|
||||
} else {
|
||||
console.warn('[Recent Events Widget] Tracker Settings button not found');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,18 +65,18 @@ export function registerInventoryWidget(registry, dependencies) {
|
||||
description: 'Full inventory system with On Person, Stored, and Assets',
|
||||
category: 'inventory',
|
||||
minSize: { w: 2, h: 4 },
|
||||
// Column-aware sizing: compact on mobile, full width on desktop
|
||||
// Column-aware sizing: compact on mobile, spacious on desktop
|
||||
defaultSize: (columns) => {
|
||||
if (columns <= 2) {
|
||||
return { w: 2, h: 5 }; // Mobile: 2×5 (full width, compact)
|
||||
}
|
||||
return { w: 3, h: 7 }; // Desktop: 3×7 (full width, spacious for 1080p)
|
||||
return { w: 2, h: 6 }; // Desktop: 2×6 (default)
|
||||
},
|
||||
maxAutoSize: (columns) => {
|
||||
if (columns <= 2) {
|
||||
return { w: 2, h: 8 }; // Mobile: 2×8 max
|
||||
return { w: 2, h: 8 }; // Mobile: 2×8 max (increased for expansion headroom)
|
||||
}
|
||||
return { w: 3, h: 10 }; // Desktop: 3×10 max (can expand)
|
||||
return { w: 3, h: 8 }; // Desktop: 3×8 max (can expand)
|
||||
},
|
||||
requiresSchema: false,
|
||||
|
||||
|
||||
@@ -276,20 +276,8 @@ export function registerPresentCharactersWidget(registry, dependencies) {
|
||||
description: 'Character cards with avatars, traits, and relationships',
|
||||
category: 'scene',
|
||||
minSize: { w: 2, h: 2 },
|
||||
// Column-aware sizing: narrow and tall on mobile, wide and short on desktop
|
||||
defaultSize: (columns) => {
|
||||
if (columns <= 2) {
|
||||
return { w: 2, h: 4 }; // Mobile: 2 cols wide (full), 4 rows tall
|
||||
}
|
||||
return { w: 3, h: 2 }; // Desktop: 3 cols wide (full), 2 rows tall (fits 1080p)
|
||||
},
|
||||
// Column-aware max size: same as default to prevent expansion
|
||||
maxAutoSize: (columns) => {
|
||||
if (columns <= 2) {
|
||||
return { w: 2, h: 4 }; // Mobile: stay at 4 rows
|
||||
}
|
||||
return { w: 3, h: 2 }; // Desktop: stay at 2 rows (fits 1080p without scrolling)
|
||||
},
|
||||
defaultSize: { w: 2, h: 2 }, // Compact size fits both mobile and desktop viewports
|
||||
maxAutoSize: { w: 4, h: 5 }, // Max size for auto-arrange expansion (supports up to 4-col on large displays)
|
||||
requiresSchema: false,
|
||||
|
||||
render(container, config = {}) {
|
||||
|
||||
@@ -395,18 +395,18 @@ export function registerQuestsWidget(registry, dependencies) {
|
||||
description: 'Quest tracking with main and optional quests',
|
||||
category: 'quests',
|
||||
minSize: { w: 2, h: 4 },
|
||||
// Column-aware sizing: compact on mobile, full width on desktop
|
||||
// Column-aware sizing: compact on mobile, spacious on desktop
|
||||
defaultSize: (columns) => {
|
||||
if (columns <= 2) {
|
||||
return { w: 2, h: 5 }; // Mobile: 2×5 (full width, compact)
|
||||
return { w: 2, h: 4 }; // Mobile: 2×4 (full width, compact)
|
||||
}
|
||||
return { w: 3, h: 7 }; // Desktop: 3×7 (full width, spacious for 1080p)
|
||||
return { w: 2, h: 5 }; // Desktop: 2×5 (default)
|
||||
},
|
||||
maxAutoSize: (columns) => {
|
||||
if (columns <= 2) {
|
||||
return { w: 2, h: 8 }; // Mobile: 2×8 max
|
||||
return { w: 2, h: 7 }; // Mobile: 2×7 max (increased for expansion headroom)
|
||||
}
|
||||
return { w: 3, h: 10 }; // Desktop: 3×10 max (can expand)
|
||||
return { w: 3, h: 7 }; // Desktop: 3×7 max (can expand)
|
||||
},
|
||||
requiresSchema: false,
|
||||
|
||||
|
||||
@@ -35,20 +35,8 @@ export function registerUserAttributesWidget(registry, dependencies) {
|
||||
description: 'Customizable RPG attributes with +/- buttons (STR, DEX, etc.)',
|
||||
category: 'user',
|
||||
minSize: { w: 2, h: 2 },
|
||||
// Column-aware sizing: full width at each column count
|
||||
defaultSize: (columns) => {
|
||||
if (columns <= 2) {
|
||||
return { w: 2, h: 4 }; // Mobile: 2 cols wide (full), 4 rows tall
|
||||
}
|
||||
return { w: 3, h: 4 }; // Desktop: 3 cols wide (full), 4 rows tall
|
||||
},
|
||||
// Column-aware max size: same as default
|
||||
maxAutoSize: (columns) => {
|
||||
if (columns <= 2) {
|
||||
return { w: 2, h: 4 };
|
||||
}
|
||||
return { w: 3, h: 4 };
|
||||
},
|
||||
defaultSize: { w: 2, h: 2 },
|
||||
maxAutoSize: { w: 3, h: 5 }, // Max size for auto-arrange expansion
|
||||
requiresSchema: false,
|
||||
|
||||
/**
|
||||
|
||||
@@ -38,22 +38,19 @@ export function registerUserInfoWidget(registry, dependencies) {
|
||||
description: 'User avatar, name, and level display',
|
||||
category: 'user',
|
||||
minSize: { w: 1, h: 1 },
|
||||
// Column-aware default size: vertical 1x2 with mood below
|
||||
// Column-aware default size: start at 2x1 in desktop so mood doesn't block expansion
|
||||
defaultSize: (columns) => {
|
||||
// Mobile detection: screen width ≤ 1000px uses compact 1x1
|
||||
const isMobile = window.innerWidth <= 1000;
|
||||
if (isMobile) {
|
||||
return { w: 1, h: 1 }; // Mobile: 1x1, compact (round avatar)
|
||||
if (columns <= 2) {
|
||||
return { w: 1, h: 1 }; // Mobile: 1x1, horizontal layout
|
||||
}
|
||||
return { w: 1, h: 2 }; // Desktop (all widths): 1x2 vertical, mood sits below
|
||||
return { w: 2, h: 1 }; // Desktop: 2x1 from the start
|
||||
},
|
||||
// Column-aware max size: same as defaultSize to prevent expansion
|
||||
// Column-aware max size: same as defaultSize to prevent further expansion
|
||||
maxAutoSize: (columns) => {
|
||||
const isMobile = window.innerWidth <= 1000;
|
||||
if (isMobile) {
|
||||
return { w: 1, h: 1 }; // Mobile: 1x1, compact
|
||||
if (columns <= 2) {
|
||||
return { w: 1, h: 1 }; // Mobile: 1x1, horizontal layout
|
||||
}
|
||||
return { w: 1, h: 2 }; // Desktop: 1x2 vertical, mood below at y:2
|
||||
return { w: 2, h: 1 }; // Desktop: 2x1, mood sits in top-right
|
||||
},
|
||||
requiresSchema: false,
|
||||
|
||||
@@ -92,23 +89,16 @@ export function registerUserInfoWidget(registry, dependencies) {
|
||||
|
||||
const html = `
|
||||
<div class="rpg-user-info-container" style="${backgroundStyle}">
|
||||
${finalConfig.showAvatar ? `<img class="rpg-user-avatar-img" src="${userPortrait}" alt="User Avatar">` : ''}
|
||||
|
||||
${finalConfig.showName ? `
|
||||
<div class="rpg-user-name-container">
|
||||
<div class="rpg-user-name">${userName}</div>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<div class="rpg-user-info-text">
|
||||
${finalConfig.showName ? `<div class="rpg-user-name">${userName}</div>` : ''}
|
||||
${finalConfig.showLevel ? `
|
||||
<div class="rpg-user-level-container">
|
||||
<div class="rpg-user-level">
|
||||
<span class="rpg-level-label">LVL</span>
|
||||
<span class="rpg-level-value rpg-editable" contenteditable="true" data-field="level" title="Click to edit level">${settings.level}</span>
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
container.innerHTML = html;
|
||||
@@ -165,15 +155,11 @@ export function registerUserInfoWidget(registry, dependencies) {
|
||||
const infoContainer = container.querySelector('.rpg-user-info-container');
|
||||
if (!infoContainer) return;
|
||||
|
||||
// Apply layout classes based on widget width
|
||||
if (newW >= 2) {
|
||||
// Wide layout (2x1+): Horizontal split with name left, level right
|
||||
infoContainer.classList.add('rpg-user-info-wide');
|
||||
infoContainer.classList.remove('rpg-user-info-compact');
|
||||
} else {
|
||||
// Compact layout (1x1): Round avatar with flush text overlays
|
||||
// Apply compact mode class at narrow widths for smaller text
|
||||
if (newW < 3) {
|
||||
infoContainer.classList.add('rpg-user-info-compact');
|
||||
infoContainer.classList.remove('rpg-user-info-wide');
|
||||
} else {
|
||||
infoContainer.classList.remove('rpg-user-info-compact');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -33,19 +33,13 @@ export function registerUserStatsWidget(registry, dependencies) {
|
||||
description: 'Health, energy, satiety bars',
|
||||
category: 'user',
|
||||
minSize: { w: 1, h: 2 },
|
||||
// Column-aware sizing: narrow and tall at 2 cols, wider at 3+ cols
|
||||
defaultSize: (columns) => {
|
||||
if (columns <= 2) {
|
||||
return { w: 1, h: 3 }; // Mobile: 1 col wide, 3 rows tall
|
||||
}
|
||||
return { w: 2, h: 3 }; // Desktop: 2 cols wide, 3 rows tall
|
||||
},
|
||||
// Column-aware max size: same as default to prevent expansion
|
||||
defaultSize: { w: 2, h: 2 },
|
||||
// Column-aware max size: full width in 3-4 col for horizontal spread
|
||||
maxAutoSize: (columns) => {
|
||||
if (columns <= 2) {
|
||||
return { w: 1, h: 3 }; // Mobile: 1x3
|
||||
return { w: 2, h: 2 }; // Mobile: use full 2-col width
|
||||
}
|
||||
return { w: 2, h: 3 }; // Desktop: 2x3
|
||||
return { w: 3, h: 3 }; // Desktop: span 3 columns horizontally
|
||||
},
|
||||
requiresSchema: false,
|
||||
|
||||
|
||||
@@ -56,10 +56,6 @@ function separateEmojiFromText(str) {
|
||||
function stripBrackets(text) {
|
||||
if (!text) return text;
|
||||
|
||||
const originalLength = text.length;
|
||||
debugLog('[RPG Parser] stripBrackets: Input length:', originalLength);
|
||||
debugLog('[RPG Parser] stripBrackets: Contains "Skills:":', text.includes('Skills:'));
|
||||
|
||||
// Remove leading and trailing whitespace first
|
||||
text = text.trim();
|
||||
|
||||
@@ -71,7 +67,6 @@ function stripBrackets(text) {
|
||||
(text.startsWith('(') && text.endsWith(')'))
|
||||
) {
|
||||
text = text.substring(1, text.length - 1).trim();
|
||||
debugLog('[RPG Parser] stripBrackets: Removed wrapping brackets, new length:', text.length);
|
||||
}
|
||||
|
||||
// Remove placeholder text patterns like [Location], [Mood Emoji], [Name], etc.
|
||||
@@ -107,103 +102,23 @@ function stripBrackets(text) {
|
||||
};
|
||||
|
||||
// Replace placeholders with empty string, keep real content
|
||||
let removedPlaceholders = [];
|
||||
text = text.replace(placeholderPattern, (match, content) => {
|
||||
if (isPlaceholder(match, content)) {
|
||||
removedPlaceholders.push(match);
|
||||
return ''; // Remove placeholder
|
||||
}
|
||||
return match; // Keep real bracketed content
|
||||
});
|
||||
if (removedPlaceholders.length > 0) {
|
||||
debugLog('[RPG Parser] stripBrackets: Removed placeholders:', removedPlaceholders.join(', '));
|
||||
}
|
||||
|
||||
// Clean up any resulting empty labels (e.g., "Status: " with nothing after)
|
||||
// BUT: Don't remove structural section headers that have content on following lines
|
||||
const beforeCleanup = text.length;
|
||||
|
||||
// Known section headers that should NEVER be removed (structural markers)
|
||||
const structuralHeaders = ['Skills', 'Status', 'Inventory', 'On Person', 'Stored', 'Assets', 'Main Quest', 'Main Quests', 'Optional Quest', 'Optional Quests'];
|
||||
|
||||
// Split into lines to intelligently remove only truly empty labels
|
||||
const lines = text.split('\n');
|
||||
const filteredLines = [];
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
const trimmedLine = line.trim();
|
||||
|
||||
// Check if this is a label line (ends with colon, no other content)
|
||||
const labelMatch = trimmedLine.match(/^([A-Za-z\s]+):\s*$/);
|
||||
|
||||
if (labelMatch) {
|
||||
const labelName = labelMatch[1];
|
||||
|
||||
// Never remove structural section headers
|
||||
if (structuralHeaders.includes(labelName)) {
|
||||
debugLog('[RPG Parser] stripBrackets: Keeping structural header:', trimmedLine);
|
||||
filteredLines.push(line);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if there's ANY content in the next few lines (look ahead up to 3 lines)
|
||||
let hasContentBelow = false;
|
||||
for (let j = i + 1; j < Math.min(i + 4, lines.length); j++) {
|
||||
const futureLine = lines[j].trim();
|
||||
if (futureLine === '') continue; // Skip empty lines
|
||||
|
||||
// If we find a line with content (not just another label), this label has content below
|
||||
if (futureLine && !/^([A-Za-z\s]+):\s*$/.test(futureLine)) {
|
||||
hasContentBelow = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasContentBelow) {
|
||||
// This label has content below (even if through other labels), keep it
|
||||
debugLog('[RPG Parser] stripBrackets: Keeping section header:', trimmedLine);
|
||||
filteredLines.push(line);
|
||||
} else {
|
||||
// This is a truly empty label with no content anywhere below, remove it
|
||||
debugLog('[RPG Parser] stripBrackets: Removing empty label:', trimmedLine);
|
||||
}
|
||||
} else {
|
||||
// Not a label line, keep it
|
||||
filteredLines.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
text = filteredLines.join('\n');
|
||||
|
||||
if (text.length !== beforeCleanup) {
|
||||
debugLog('[RPG Parser] stripBrackets: Removed empty labels, chars removed:', beforeCleanup - text.length);
|
||||
}
|
||||
|
||||
text = text.replace(/^([A-Za-z\s]+):\s*$/gm, ''); // Remove lines that are just "Label: " with nothing
|
||||
text = text.replace(/^([A-Za-z\s]+):\s*,/gm, '$1:'); // Fix "Label: ," patterns
|
||||
text = text.replace(/:\s*\|/g, ':'); // Fix ": |" patterns
|
||||
text = text.replace(/\|\s*\|/g, '|'); // Fix "| |" patterns (double pipes from removed content)
|
||||
text = text.replace(/\|\s*$/gm, ''); // Remove trailing pipes at end of lines
|
||||
|
||||
// Clean up multiple spaces and empty lines
|
||||
const beforeSpaceCleanup = text.length;
|
||||
text = text.replace(/\s{2,}/g, ' '); // Multiple spaces to single space
|
||||
text = text.replace(/^\s*\n/gm, ''); // Remove empty lines
|
||||
if (text.length !== beforeSpaceCleanup) {
|
||||
debugLog('[RPG Parser] stripBrackets: Cleaned up spaces/newlines, chars removed:', beforeSpaceCleanup - text.length);
|
||||
}
|
||||
|
||||
const finalLength = text.trim().length;
|
||||
debugLog('[RPG Parser] stripBrackets: Output length:', finalLength);
|
||||
debugLog('[RPG Parser] stripBrackets: Total chars removed:', originalLength - finalLength);
|
||||
debugLog('[RPG Parser] stripBrackets: Contains "Skills:" after processing:', text.includes('Skills:'));
|
||||
|
||||
if (text.includes('Skills:')) {
|
||||
const skillsIndex = text.indexOf('Skills:');
|
||||
debugLog('[RPG Parser] stripBrackets: Text around Skills (index ' + skillsIndex + '):', text.substring(skillsIndex, skillsIndex + 200));
|
||||
} else if (originalLength !== finalLength) {
|
||||
debugLog('[RPG Parser] stripBrackets: WARNING - Skills section was removed! Last 200 chars:', text.substring(text.length - 200));
|
||||
}
|
||||
|
||||
return text.trim();
|
||||
}
|
||||
@@ -218,164 +133,6 @@ function debugLog(message, data = null) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract structured skills data from stats text
|
||||
* Parses format:
|
||||
* Skills:
|
||||
* CategoryName:
|
||||
* - SkillName (Lv X)
|
||||
* - SkillName (Lv X)
|
||||
* Uncategorized:
|
||||
* - SkillName (Lv X)
|
||||
*
|
||||
* @param {string} statsText - Stats section text containing skills
|
||||
* @returns {Object|null} Structured skills data or null if not found
|
||||
*/
|
||||
function extractSkills(statsText) {
|
||||
if (!statsText) {
|
||||
debugLog('[RPG Parser] extractSkills: No stats text provided');
|
||||
return null;
|
||||
}
|
||||
|
||||
debugLog('[RPG Parser] extractSkills: Searching for Skills section in text length:', statsText.length);
|
||||
debugLog('[RPG Parser] extractSkills: Text contains "Skills:":', statsText.includes('Skills:'));
|
||||
|
||||
// Find the Skills section
|
||||
const skillsMatch = statsText.match(/Skills:([\s\S]*?)(?=\n\n|On Person:|Stored|Assets:|Main Quest|Optional Quest|$)/i);
|
||||
if (!skillsMatch) {
|
||||
debugLog('[RPG Parser] extractSkills: Main regex did not match');
|
||||
debugLog('[RPG Parser] extractSkills: Checking if "On Person:" exists:', statsText.includes('On Person:'));
|
||||
debugLog('[RPG Parser] extractSkills: Text around Skills:', statsText.substring(statsText.indexOf('Skills:'), statsText.indexOf('Skills:') + 200));
|
||||
|
||||
// Fallback: try simple format "Skills: skill1, skill2"
|
||||
const simpleMatch = statsText.match(/Skills:\s*(.+)/i);
|
||||
if (simpleMatch) {
|
||||
const skillsText = simpleMatch[1].trim();
|
||||
debugLog('[RPG Parser] extractSkills: Simple format matched:', skillsText);
|
||||
if (skillsText && skillsText !== 'None') {
|
||||
// Return as string for backward compatibility
|
||||
return skillsText;
|
||||
}
|
||||
}
|
||||
debugLog('[RPG Parser] extractSkills: No Skills section found');
|
||||
return null;
|
||||
}
|
||||
|
||||
debugLog('[RPG Parser] extractSkills: Main regex matched, captured length:', skillsMatch[1].length);
|
||||
|
||||
const skillsSection = skillsMatch[1];
|
||||
const skillsData = {
|
||||
version: 1,
|
||||
categories: {},
|
||||
uncategorized: []
|
||||
};
|
||||
|
||||
// Split into lines and process
|
||||
const lines = skillsSection.split('\n').map(line => line.trim()).filter(line => line);
|
||||
|
||||
debugLog('[RPG Parser] Skills section lines:', lines);
|
||||
|
||||
let currentCategory = null;
|
||||
|
||||
for (const line of lines) {
|
||||
// Check if this is a category header (ends with colon, no dash)
|
||||
if (line.endsWith(':') && !line.startsWith('-')) {
|
||||
currentCategory = line.slice(0, -1).trim();
|
||||
debugLog(`[RPG Parser] Found category header: "${currentCategory}"`);
|
||||
if (currentCategory !== 'Uncategorized' && !skillsData.categories[currentCategory]) {
|
||||
skillsData.categories[currentCategory] = [];
|
||||
debugLog(`[RPG Parser] Created category array for: "${currentCategory}"`);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if this is a skill line (starts with -, has level info)
|
||||
// Try numeric format first: "- Skill Name (Lv 5)"
|
||||
let skillMatch = line.match(/^-\s*(.+?)\s*\(Lv\s*(\d+)\)/i);
|
||||
if (skillMatch) {
|
||||
const skillName = skillMatch[1].trim();
|
||||
const level = parseInt(skillMatch[2], 10) || 1;
|
||||
|
||||
const skill = {
|
||||
name: skillName,
|
||||
level: level,
|
||||
xp: 0,
|
||||
maxXP: 100
|
||||
};
|
||||
|
||||
if (currentCategory === 'Uncategorized' || currentCategory === null) {
|
||||
debugLog(`[RPG Parser] Adding "${skillName}" to uncategorized (currentCategory="${currentCategory}")`);
|
||||
skillsData.uncategorized.push(skill);
|
||||
} else if (currentCategory && skillsData.categories[currentCategory]) {
|
||||
debugLog(`[RPG Parser] Adding "${skillName}" to category "${currentCategory}"`);
|
||||
skillsData.categories[currentCategory].push(skill);
|
||||
} else {
|
||||
debugLog(`[RPG Parser] ERROR: Could not add "${skillName}" - currentCategory="${currentCategory}", categoryExists=${!!skillsData.categories[currentCategory]}`);
|
||||
// Fallback to uncategorized if category doesn't exist
|
||||
skillsData.uncategorized.push(skill);
|
||||
}
|
||||
} else {
|
||||
// Fallback: Try text-based proficiency format: "- Skill Name (Proficient)"
|
||||
const textMatch = line.match(/^-\s*(.+?)\s*\((.+?)\)/i);
|
||||
if (textMatch) {
|
||||
const skillName = textMatch[1].trim();
|
||||
const proficiencyText = textMatch[2].trim().toLowerCase();
|
||||
|
||||
// Map text proficiency to numeric level
|
||||
const proficiencyMap = {
|
||||
'initiated': 1,
|
||||
'novice': 1,
|
||||
'basic': 2,
|
||||
'beginner': 2,
|
||||
'intermediate': 4,
|
||||
'proficient': 5,
|
||||
'competent': 6,
|
||||
'advanced': 7,
|
||||
'expert': 8,
|
||||
'mastered': 9,
|
||||
'master': 9,
|
||||
'grandmaster': 10,
|
||||
'legendary': 10
|
||||
};
|
||||
|
||||
const level = proficiencyMap[proficiencyText] || 5; // Default to 5 if unknown
|
||||
|
||||
const skill = {
|
||||
name: skillName,
|
||||
level: level,
|
||||
xp: 0,
|
||||
maxXP: 100
|
||||
};
|
||||
|
||||
if (currentCategory === 'Uncategorized' || currentCategory === null) {
|
||||
debugLog(`[RPG Parser] Adding "${skillName}" to uncategorized (currentCategory="${currentCategory}")`);
|
||||
skillsData.uncategorized.push(skill);
|
||||
} else if (currentCategory && skillsData.categories[currentCategory]) {
|
||||
debugLog(`[RPG Parser] Adding "${skillName}" to category "${currentCategory}"`);
|
||||
skillsData.categories[currentCategory].push(skill);
|
||||
} else {
|
||||
debugLog(`[RPG Parser] ERROR: Could not add "${skillName}" - currentCategory="${currentCategory}", categoryExists=${!!skillsData.categories[currentCategory]}`);
|
||||
// Fallback to uncategorized if category doesn't exist
|
||||
skillsData.uncategorized.push(skill);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return null if no skills were found
|
||||
if (Object.keys(skillsData.categories).length === 0 && skillsData.uncategorized.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
debugLog('[RPG Parser] Final skills data:', {
|
||||
categories: Object.keys(skillsData.categories),
|
||||
categoryCounts: Object.entries(skillsData.categories).map(([cat, skills]) => `${cat}: ${skills.length}`),
|
||||
uncategorizedCount: skillsData.uncategorized.length
|
||||
});
|
||||
|
||||
return skillsData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the model response to extract the different data sections.
|
||||
* Extracts tracker data from markdown code blocks in the AI response.
|
||||
@@ -413,13 +170,7 @@ export function parseResponse(responseText) {
|
||||
const content = match[1].trim();
|
||||
|
||||
debugLog(`[RPG Parser] --- Code Block ${i + 1} ---`);
|
||||
debugLog('[RPG Parser] Content length:', content.length);
|
||||
debugLog('[RPG Parser] First 300 chars:', content.substring(0, 300));
|
||||
debugLog('[RPG Parser] Contains "Skills:":', content.includes('Skills:'));
|
||||
if (content.includes('Skills:')) {
|
||||
const skillsIndex = content.indexOf('Skills:');
|
||||
debugLog('[RPG Parser] Text around Skills (index ' + skillsIndex + '):', content.substring(skillsIndex, skillsIndex + 200));
|
||||
}
|
||||
|
||||
// Check if this is a combined code block with multiple sections
|
||||
const hasMultipleSections = (
|
||||
@@ -600,12 +351,10 @@ export function parseUserStats(statsText) {
|
||||
// Parse skills section if enabled
|
||||
const skillsConfig = trackerConfig?.userStats?.skillsSection;
|
||||
if (skillsConfig?.enabled) {
|
||||
const skillsData = extractSkills(statsText);
|
||||
if (skillsData) {
|
||||
extensionSettings.userStats.skills = skillsData;
|
||||
debugLog('[RPG Parser] Skills extracted:', skillsData);
|
||||
} else {
|
||||
debugLog('[RPG Parser] Skills extraction failed or none found');
|
||||
const skillsMatch = statsText.match(/Skills:\s*(.+)/i);
|
||||
if (skillsMatch) {
|
||||
extensionSettings.userStats.skills = skillsMatch[1].trim();
|
||||
debugLog('[RPG Parser] Skills extracted:', skillsMatch[1].trim());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,53 +10,6 @@ import { extensionSettings, committedTrackerData, FEATURE_FLAGS } from '../../co
|
||||
// Type imports
|
||||
/** @typedef {import('../../types/inventory.js').InventoryV2} InventoryV2 */
|
||||
|
||||
/**
|
||||
* Builds a formatted skills summary for AI context injection.
|
||||
* Converts structured skills data to multi-line plaintext format organized by category.
|
||||
*
|
||||
* @param {Object|string} skills - Current skills (structured or legacy string)
|
||||
* @returns {string} Formatted skills summary for prompt injection
|
||||
* @example
|
||||
* // Structured input: { version: 1, categories: { Combat: [{name: 'Swordsmanship', level: 5}] }, uncategorized: [] }
|
||||
* // Returns: "Skills:\nCombat:\n- Swordsmanship (Lv 5)"
|
||||
*/
|
||||
export function buildSkillsSummary(skills) {
|
||||
// Handle legacy string format
|
||||
if (typeof skills === 'string') {
|
||||
return `Skills: ${skills}`;
|
||||
}
|
||||
|
||||
// Handle structured format
|
||||
if (skills && typeof skills === 'object' && skills.version) {
|
||||
let summary = 'Skills:';
|
||||
const categories = skills.categories || {};
|
||||
const uncategorized = skills.uncategorized || [];
|
||||
|
||||
// Add categorized skills
|
||||
for (const [categoryName, skillsList] of Object.entries(categories)) {
|
||||
if (skillsList && skillsList.length > 0) {
|
||||
summary += `\n${categoryName}:`;
|
||||
for (const skill of skillsList) {
|
||||
summary += `\n- ${skill.name} (Lv ${skill.level})`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add uncategorized skills
|
||||
if (uncategorized.length > 0) {
|
||||
summary += '\nUncategorized:';
|
||||
for (const skill of uncategorized) {
|
||||
summary += `\n- ${skill.name} (Lv ${skill.level})`;
|
||||
}
|
||||
}
|
||||
|
||||
return summary;
|
||||
}
|
||||
|
||||
// Empty or invalid
|
||||
return 'Skills: None';
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a formatted inventory summary for AI context injection.
|
||||
* Converts v2 inventory structure to multi-line plaintext format.
|
||||
@@ -213,13 +166,9 @@ export function generateTrackerInstructions(includeHtmlPrompt = true, includeCon
|
||||
|
||||
// Add skills section if enabled
|
||||
if (userStatsConfig?.skillsSection?.enabled) {
|
||||
instructions += `Skills:\n`;
|
||||
instructions += `[Category Name]:\n`;
|
||||
instructions += `- [Skill Name] (Lv [1-100])\n`;
|
||||
instructions += `- [Another Skill] (Lv [1-100])\n`;
|
||||
instructions += `Uncategorized:\n`;
|
||||
instructions += `- [Uncategorized Skill] (Lv [1-100])\n`;
|
||||
instructions += `(Organize skills by logical categories like Combat, Magic, Social, Crafting, etc. IMPORTANT: Use numeric levels only - write "Lv 5" not "Proficient", "Lv 7" not "Advanced". Use integers 1-100 where 1=novice, 5=intermediate, 10=expert. Skills without a clear category go in Uncategorized.)\n`;
|
||||
const skillFields = userStatsConfig.skillsSection.customFields || [];
|
||||
const skillFieldsText = skillFields.map(f => `[${f}]`).join(', ');
|
||||
instructions += `Skills: [${skillFieldsText || 'Skill1, Skill2, etc.'}]\n`;
|
||||
}
|
||||
|
||||
// Add inventory format based on feature flag
|
||||
|
||||
+8
-3
@@ -59,11 +59,16 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons Row -->
|
||||
<div class="rpg-settings-buttons-row">
|
||||
<button id="rpg-manual-update" class="rpg-btn-primary rpg-manual-update-btn rpg-btn-half">
|
||||
<!-- Manual Update Button -->
|
||||
<button id="rpg-manual-update" class="rpg-btn-primary rpg-manual-update-btn">
|
||||
<i class="fa-solid fa-sync"></i> Refresh RPG Info
|
||||
</button>
|
||||
|
||||
<!-- Settings and Edit Trackers Buttons Row -->
|
||||
<div class="rpg-settings-buttons-row">
|
||||
<button id="rpg-open-tracker-editor" class="rpg-btn-settings rpg-btn-half">
|
||||
<i class="fa-solid fa-sliders"></i> Edit Trackers
|
||||
</button>
|
||||
<button id="rpg-open-settings" class="rpg-btn-settings rpg-btn-half">
|
||||
<i class="fa-solid fa-gear"></i> Settings
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user