feat(dashboard): implement dashboard data structure (Task 1.3)
Add dashboard configuration to extensionSettings and create default layout system: State Management (state.js): - Added extensionSettings.dashboard with version 2 - gridConfig: columns (12), rowHeight (80px), gap (12px), snapToGrid, showGrid - tabs: Array of tab objects with widgets - defaultTab: ID of tab to show on load - Comprehensive inline documentation of structure Default Layout Generator (defaultLayout.js): - generateDefaultDashboard() - Creates 2-tab default layout - "Status" tab: userStats, infoBox, presentCharacters (3 widgets) - "Inventory" tab: inventory widget (1 widget) - migrateV1ToV2Dashboard() - Migrates v1.x settings to v2.0 - Respects user's visibility preferences (showUserStats, etc.) - Removes hidden widgets from migrated layout - Preserves user data during migration - validateDashboardConfig() - Validates dashboard structure - Utility functions: getWidgetCount(), findWidget() Persistence Layer (persistence.js): - Auto-migration on loadSettings() for existing users - Validates dashboard config on load - Regenerates default if config invalid or missing - Seamless backward compatibility Test Suite (defaultLayout.test.html): - 4 test scenarios with visual verification - Tests generation, validation, migration, utilities - Live dashboard JSON preview - Statistics panel (version, tabs, widgets, grid config) Features: - Automatic migration from v1.x hardcoded panel - Preserves user preferences during migration - Validates all dashboard configs on load - Generates sensible defaults for new users Acceptance Criteria Met: ✓ Dashboard config persists in extensionSettings ✓ Default layout generates on first load ✓ Existing users see migrated layout preserving their preferences ✓ All data structures validated Epic 1, Task 1.3 Complete (1-2 day estimate, <10 min actual)
This commit is contained in:
@@ -18,6 +18,7 @@ import {
|
|||||||
} from './state.js';
|
} from './state.js';
|
||||||
import { migrateInventory } from '../utils/migration.js';
|
import { migrateInventory } from '../utils/migration.js';
|
||||||
import { validateStoredInventory, cleanItemString } from '../utils/security.js';
|
import { validateStoredInventory, cleanItemString } from '../utils/security.js';
|
||||||
|
import { generateDefaultDashboard, migrateV1ToV2Dashboard, validateDashboardConfig } from '../systems/dashboard/defaultLayout.js';
|
||||||
|
|
||||||
const extensionName = 'third-party/rpg-companion-sillytavern';
|
const extensionName = 'third-party/rpg-companion-sillytavern';
|
||||||
|
|
||||||
@@ -95,6 +96,20 @@ export function loadSettings() {
|
|||||||
saveSettings(); // Persist migrated inventory
|
saveSettings(); // Persist migrated inventory
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Migrate to v2.0 dashboard if not present
|
||||||
|
if (!extensionSettings.dashboard || !extensionSettings.dashboard.tabs || extensionSettings.dashboard.tabs.length === 0) {
|
||||||
|
console.log('[RPG Companion] Dashboard v2.0 not found, migrating from v1.x');
|
||||||
|
extensionSettings.dashboard = migrateV1ToV2Dashboard(extensionSettings);
|
||||||
|
saveSettings(); // Persist migrated dashboard
|
||||||
|
} else {
|
||||||
|
// Validate existing dashboard config
|
||||||
|
if (!validateDashboardConfig(extensionSettings.dashboard)) {
|
||||||
|
console.warn('[RPG Companion] Dashboard config invalid, regenerating default');
|
||||||
|
extensionSettings.dashboard = generateDefaultDashboard();
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[RPG Companion] Error loading settings:', error);
|
console.error('[RPG Companion] Error loading settings:', error);
|
||||||
console.error('[RPG Companion] Error details:', error.message, error.stack);
|
console.error('[RPG Companion] Error details:', error.message, error.stack);
|
||||||
|
|||||||
+35
-1
@@ -77,7 +77,41 @@ export let extensionSettings = {
|
|||||||
stored: 'list', // 'list' or 'grid' view mode for Stored section
|
stored: 'list', // 'list' or 'grid' view mode for Stored section
|
||||||
assets: 'list' // 'list' or 'grid' view mode for Assets section
|
assets: 'list' // 'list' or 'grid' view mode for Assets section
|
||||||
},
|
},
|
||||||
debugMode: false // Enable debug logging visible in UI (for mobile debugging)
|
debugMode: false, // Enable debug logging visible in UI (for mobile debugging)
|
||||||
|
|
||||||
|
// Dashboard v2.0 Configuration
|
||||||
|
dashboard: {
|
||||||
|
version: 2, // Dashboard config version
|
||||||
|
|
||||||
|
gridConfig: {
|
||||||
|
columns: 12, // Grid columns
|
||||||
|
rowHeight: 80, // Pixels per row
|
||||||
|
gap: 12, // Gap between widgets (px)
|
||||||
|
snapToGrid: true, // Auto-snap enabled
|
||||||
|
showGrid: true // Show grid lines in edit mode
|
||||||
|
},
|
||||||
|
|
||||||
|
tabs: [
|
||||||
|
// Default tabs will be generated by generateDefaultDashboard()
|
||||||
|
// Structure:
|
||||||
|
// {
|
||||||
|
// id: 'tab-status',
|
||||||
|
// name: 'Status',
|
||||||
|
// icon: '📊',
|
||||||
|
// order: 0,
|
||||||
|
// widgets: [
|
||||||
|
// {
|
||||||
|
// id: 'widget-1',
|
||||||
|
// type: 'userStats',
|
||||||
|
// x: 0, y: 0, w: 6, h: 3,
|
||||||
|
// config: {}
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
],
|
||||||
|
|
||||||
|
defaultTab: 'tab-status' // Which tab to show on load
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,261 @@
|
|||||||
|
/**
|
||||||
|
* Default Dashboard Layout Generator
|
||||||
|
*
|
||||||
|
* Generates the default dashboard configuration for new users or when resetting layout.
|
||||||
|
* Maps existing v1.x panel structure to v2.0 widget dashboard.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate default dashboard configuration
|
||||||
|
*
|
||||||
|
* Creates a two-tab layout:
|
||||||
|
* - "Status" tab: User stats, info box, present characters
|
||||||
|
* - "Inventory" tab: Full inventory widget
|
||||||
|
*
|
||||||
|
* @returns {Object} Default dashboard configuration
|
||||||
|
*/
|
||||||
|
export function generateDefaultDashboard() {
|
||||||
|
const dashboard = {
|
||||||
|
version: 2,
|
||||||
|
|
||||||
|
gridConfig: {
|
||||||
|
columns: 12,
|
||||||
|
rowHeight: 80,
|
||||||
|
gap: 12,
|
||||||
|
snapToGrid: true,
|
||||||
|
showGrid: true
|
||||||
|
},
|
||||||
|
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
id: 'tab-status',
|
||||||
|
name: 'Status',
|
||||||
|
icon: '📊',
|
||||||
|
order: 0,
|
||||||
|
widgets: [
|
||||||
|
{
|
||||||
|
id: 'widget-userstats',
|
||||||
|
type: 'userStats',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
w: 6,
|
||||||
|
h: 3,
|
||||||
|
config: {
|
||||||
|
showClassicStats: true,
|
||||||
|
statBarStyle: 'gradient'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'widget-infobox',
|
||||||
|
type: 'infoBox',
|
||||||
|
x: 6,
|
||||||
|
y: 0,
|
||||||
|
w: 6,
|
||||||
|
h: 2,
|
||||||
|
config: {
|
||||||
|
layout: 'horizontal'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'widget-presentchars',
|
||||||
|
type: 'presentCharacters',
|
||||||
|
x: 0,
|
||||||
|
y: 3,
|
||||||
|
w: 12,
|
||||||
|
h: 3,
|
||||||
|
config: {
|
||||||
|
cardLayout: 'grid',
|
||||||
|
showThoughtBubbles: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'tab-inventory',
|
||||||
|
name: 'Inventory',
|
||||||
|
icon: '🎒',
|
||||||
|
order: 1,
|
||||||
|
widgets: [
|
||||||
|
{
|
||||||
|
id: 'widget-inventory',
|
||||||
|
type: 'inventory',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
w: 12,
|
||||||
|
h: 6,
|
||||||
|
config: {
|
||||||
|
defaultSubTab: 'onPerson',
|
||||||
|
defaultViewMode: 'list'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
defaultTab: 'tab-status'
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('[DefaultLayout] Generated default dashboard configuration');
|
||||||
|
return dashboard;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrate v1.x settings to v2.0 dashboard
|
||||||
|
*
|
||||||
|
* Converts existing hardcoded panel structure to widget-based layout.
|
||||||
|
* Preserves user's visibility preferences and data.
|
||||||
|
*
|
||||||
|
* @param {Object} oldSettings - v1.x extension settings
|
||||||
|
* @returns {Object} Migrated dashboard configuration
|
||||||
|
*/
|
||||||
|
export function migrateV1ToV2Dashboard(oldSettings) {
|
||||||
|
console.log('[DefaultLayout] Migrating v1.x settings to v2.0 dashboard');
|
||||||
|
|
||||||
|
const dashboard = generateDefaultDashboard();
|
||||||
|
|
||||||
|
// Respect user's visibility preferences from v1.x
|
||||||
|
const statusTab = dashboard.tabs[0];
|
||||||
|
|
||||||
|
// Remove widgets that were hidden in v1.x
|
||||||
|
if (!oldSettings.showUserStats) {
|
||||||
|
statusTab.widgets = statusTab.widgets.filter(w => w.type !== 'userStats');
|
||||||
|
console.log('[DefaultLayout] Removed userStats widget (was hidden in v1.x)');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!oldSettings.showInfoBox) {
|
||||||
|
statusTab.widgets = statusTab.widgets.filter(w => w.type !== 'infoBox');
|
||||||
|
console.log('[DefaultLayout] Removed infoBox widget (was hidden in v1.x)');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!oldSettings.showCharacterThoughts) {
|
||||||
|
statusTab.widgets = statusTab.widgets.filter(w => w.type !== 'presentCharacters');
|
||||||
|
console.log('[DefaultLayout] Removed presentCharacters widget (was hidden in v1.x)');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove inventory tab if it was hidden in v1.x
|
||||||
|
if (!oldSettings.showInventory) {
|
||||||
|
dashboard.tabs = dashboard.tabs.filter(t => t.id !== 'tab-inventory');
|
||||||
|
console.log('[DefaultLayout] Removed inventory tab (was hidden in v1.x)');
|
||||||
|
}
|
||||||
|
|
||||||
|
// If all widgets were hidden on status tab, remove it too
|
||||||
|
if (statusTab.widgets.length === 0) {
|
||||||
|
dashboard.tabs = dashboard.tabs.filter(t => t.id !== 'tab-status');
|
||||||
|
console.log('[DefaultLayout] Removed status tab (all widgets were hidden)');
|
||||||
|
|
||||||
|
// If we still have inventory tab, make it default
|
||||||
|
if (dashboard.tabs.length > 0) {
|
||||||
|
dashboard.defaultTab = dashboard.tabs[0].id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[DefaultLayout] Migration complete - ${dashboard.tabs.length} tabs, ${dashboard.tabs.reduce((sum, t) => sum + t.widgets.length, 0)} widgets`);
|
||||||
|
|
||||||
|
return dashboard;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate dashboard configuration
|
||||||
|
*
|
||||||
|
* Ensures dashboard config has all required fields and valid structure.
|
||||||
|
*
|
||||||
|
* @param {Object} dashboard - Dashboard configuration to validate
|
||||||
|
* @returns {boolean} True if valid, false otherwise
|
||||||
|
*/
|
||||||
|
export function validateDashboardConfig(dashboard) {
|
||||||
|
if (!dashboard) {
|
||||||
|
console.error('[DefaultLayout] Dashboard config is null or undefined');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dashboard.version) {
|
||||||
|
console.error('[DefaultLayout] Dashboard config missing version');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dashboard.gridConfig) {
|
||||||
|
console.error('[DefaultLayout] Dashboard config missing gridConfig');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(dashboard.tabs)) {
|
||||||
|
console.error('[DefaultLayout] Dashboard tabs is not an array');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate each tab
|
||||||
|
for (const tab of dashboard.tabs) {
|
||||||
|
if (!tab.id || !tab.name) {
|
||||||
|
console.error('[DefaultLayout] Tab missing id or name:', tab);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(tab.widgets)) {
|
||||||
|
console.error('[DefaultLayout] Tab widgets is not an array:', tab);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate each widget
|
||||||
|
for (const widget of tab.widgets) {
|
||||||
|
if (!widget.id || !widget.type) {
|
||||||
|
console.error('[DefaultLayout] Widget missing id or type:', widget);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof widget.x !== 'number' || typeof widget.y !== 'number') {
|
||||||
|
console.error('[DefaultLayout] Widget position invalid:', widget);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof widget.w !== 'number' || typeof widget.h !== 'number') {
|
||||||
|
console.error('[DefaultLayout] Widget size invalid:', widget);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get widget count in dashboard
|
||||||
|
*
|
||||||
|
* @param {Object} dashboard - Dashboard configuration
|
||||||
|
* @returns {number} Total number of widgets across all tabs
|
||||||
|
*/
|
||||||
|
export function getWidgetCount(dashboard) {
|
||||||
|
if (!dashboard || !Array.isArray(dashboard.tabs)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dashboard.tabs.reduce((sum, tab) => {
|
||||||
|
return sum + (Array.isArray(tab.widgets) ? tab.widgets.length : 0);
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find widget by ID across all tabs
|
||||||
|
*
|
||||||
|
* @param {Object} dashboard - Dashboard configuration
|
||||||
|
* @param {string} widgetId - Widget ID to find
|
||||||
|
* @returns {{tabIndex: number, widgetIndex: number, widget: Object}|null}
|
||||||
|
*/
|
||||||
|
export function findWidget(dashboard, widgetId) {
|
||||||
|
if (!dashboard || !Array.isArray(dashboard.tabs)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let tabIndex = 0; tabIndex < dashboard.tabs.length; tabIndex++) {
|
||||||
|
const tab = dashboard.tabs[tabIndex];
|
||||||
|
if (!Array.isArray(tab.widgets)) continue;
|
||||||
|
|
||||||
|
for (let widgetIndex = 0; widgetIndex < tab.widgets.length; widgetIndex++) {
|
||||||
|
const widget = tab.widgets[widgetIndex];
|
||||||
|
if (widget.id === widgetId) {
|
||||||
|
return { tabIndex, widgetIndex, widget };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@@ -0,0 +1,368 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Default Layout Test</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background: #1a1a2e;
|
||||||
|
color: #eee;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
color: #e94560;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-section {
|
||||||
|
background: #16213e;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-section h2 {
|
||||||
|
color: #4ecca3;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
background: #0f3460;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
overflow-x: auto;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result {
|
||||||
|
margin: 10px 0;
|
||||||
|
padding: 10px;
|
||||||
|
border-left: 3px solid #4ecca3;
|
||||||
|
background: #0f3460;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result.pass {
|
||||||
|
border-color: #4ecca3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result.fail {
|
||||||
|
border-color: #e94560;
|
||||||
|
background: #2a0f1b;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background: #e94560;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 10px 20px;
|
||||||
|
margin: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background: #d63651;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-box {
|
||||||
|
background: #0f3460;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 11px;
|
||||||
|
opacity: 0.7;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #4ecca3;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>🏗️ Default Layout Test Suite</h1>
|
||||||
|
|
||||||
|
<div class="test-section">
|
||||||
|
<h2>Test 1: Generate Default Dashboard</h2>
|
||||||
|
<div id="test1-results"></div>
|
||||||
|
<button onclick="test1()">Run Test 1</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="test-section">
|
||||||
|
<h2>Test 2: Validate Dashboard Config</h2>
|
||||||
|
<div id="test2-results"></div>
|
||||||
|
<button onclick="test2()">Run Test 2</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="test-section">
|
||||||
|
<h2>Test 3: Migrate v1.x Settings</h2>
|
||||||
|
<div id="test3-results"></div>
|
||||||
|
<button onclick="test3()">Run Test 3</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="test-section">
|
||||||
|
<h2>Test 4: Find Widget Utility</h2>
|
||||||
|
<div id="test4-results"></div>
|
||||||
|
<button onclick="test4()">Run Test 4</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="test-section">
|
||||||
|
<h2>Generated Dashboard JSON</h2>
|
||||||
|
<pre id="dashboard-json"></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="test-section">
|
||||||
|
<h2>Dashboard Statistics</h2>
|
||||||
|
<div id="stats" class="stats"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-top: 20px;">
|
||||||
|
<button onclick="runAllTests()">🔄 Run All Tests</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
import {
|
||||||
|
generateDefaultDashboard,
|
||||||
|
migrateV1ToV2Dashboard,
|
||||||
|
validateDashboardConfig,
|
||||||
|
getWidgetCount,
|
||||||
|
findWidget
|
||||||
|
} from './defaultLayout.js';
|
||||||
|
|
||||||
|
let dashboard = null;
|
||||||
|
|
||||||
|
function pass(message) {
|
||||||
|
return `<div class="result pass">✓ ${message}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fail(message) {
|
||||||
|
return `<div class="result fail">✗ ${message}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 1: Generate default dashboard
|
||||||
|
window.test1 = function() {
|
||||||
|
const container = document.getElementById('test1-results');
|
||||||
|
container.innerHTML = '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
dashboard = generateDefaultDashboard();
|
||||||
|
container.innerHTML += pass('Generated default dashboard');
|
||||||
|
|
||||||
|
if (dashboard.version === 2) {
|
||||||
|
container.innerHTML += pass(`Dashboard version: ${dashboard.version}`);
|
||||||
|
} else {
|
||||||
|
container.innerHTML += fail(`Wrong version: ${dashboard.version}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dashboard.tabs && dashboard.tabs.length === 2) {
|
||||||
|
container.innerHTML += pass(`Generated ${dashboard.tabs.length} tabs`);
|
||||||
|
} else {
|
||||||
|
container.innerHTML += fail(`Wrong tab count: ${dashboard.tabs?.length || 0}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusTab = dashboard.tabs.find(t => t.id === 'tab-status');
|
||||||
|
if (statusTab && statusTab.widgets.length === 3) {
|
||||||
|
container.innerHTML += pass(`Status tab has ${statusTab.widgets.length} widgets`);
|
||||||
|
} else {
|
||||||
|
container.innerHTML += fail(`Wrong widget count in status tab`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const inventoryTab = dashboard.tabs.find(t => t.id === 'tab-inventory');
|
||||||
|
if (inventoryTab && inventoryTab.widgets.length === 1) {
|
||||||
|
container.innerHTML += pass(`Inventory tab has ${inventoryTab.widgets.length} widget`);
|
||||||
|
} else {
|
||||||
|
container.innerHTML += fail(`Wrong widget count in inventory tab`);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDashboardDisplay();
|
||||||
|
updateStats();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
container.innerHTML += fail(`Error: ${error.message}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test 2: Validate dashboard config
|
||||||
|
window.test2 = function() {
|
||||||
|
const container = document.getElementById('test2-results');
|
||||||
|
container.innerHTML = '';
|
||||||
|
|
||||||
|
if (!dashboard) {
|
||||||
|
container.innerHTML += fail('No dashboard generated yet (run Test 1 first)');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const valid = validateDashboardConfig(dashboard);
|
||||||
|
if (valid) {
|
||||||
|
container.innerHTML += pass('Dashboard config is valid');
|
||||||
|
} else {
|
||||||
|
container.innerHTML += fail('Dashboard config validation failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test invalid configs
|
||||||
|
const invalidConfigs = [
|
||||||
|
{ config: null, name: 'null config' },
|
||||||
|
{ config: {}, name: 'empty config' },
|
||||||
|
{ config: { version: 2 }, name: 'missing gridConfig' },
|
||||||
|
{ config: { version: 2, gridConfig: {}, tabs: 'not-array' }, name: 'tabs not array' }
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const test of invalidConfigs) {
|
||||||
|
const result = validateDashboardConfig(test.config);
|
||||||
|
if (!result) {
|
||||||
|
container.innerHTML += pass(`Correctly rejected ${test.name}`);
|
||||||
|
} else {
|
||||||
|
container.innerHTML += fail(`Failed to reject ${test.name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test 3: Migrate v1.x settings
|
||||||
|
window.test3 = function() {
|
||||||
|
const container = document.getElementById('test3-results');
|
||||||
|
container.innerHTML = '';
|
||||||
|
|
||||||
|
// Simulate v1.x settings with some sections hidden
|
||||||
|
const v1Settings = {
|
||||||
|
showUserStats: true,
|
||||||
|
showInfoBox: false, // Hidden
|
||||||
|
showCharacterThoughts: true,
|
||||||
|
showInventory: true
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const migrated = migrateV1ToV2Dashboard(v1Settings);
|
||||||
|
container.innerHTML += pass('Migrated v1.x settings');
|
||||||
|
|
||||||
|
const statusTab = migrated.tabs.find(t => t.id === 'tab-status');
|
||||||
|
const hasInfoBox = statusTab?.widgets.some(w => w.type === 'infoBox');
|
||||||
|
|
||||||
|
if (!hasInfoBox) {
|
||||||
|
container.innerHTML += pass('Correctly removed hidden infoBox widget');
|
||||||
|
} else {
|
||||||
|
container.innerHTML += fail('Failed to remove hidden infoBox widget');
|
||||||
|
}
|
||||||
|
|
||||||
|
container.innerHTML += `<div class="result">Migrated dashboard has ${migrated.tabs.length} tabs</div>`;
|
||||||
|
container.innerHTML += `<div class="result">Status tab has ${statusTab?.widgets.length || 0} widgets</div>`;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
container.innerHTML += fail(`Error: ${error.message}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test 4: Find widget utility
|
||||||
|
window.test4 = function() {
|
||||||
|
const container = document.getElementById('test4-results');
|
||||||
|
container.innerHTML = '';
|
||||||
|
|
||||||
|
if (!dashboard) {
|
||||||
|
container.innerHTML += fail('No dashboard generated yet (run Test 1 first)');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const found = findWidget(dashboard, 'widget-userstats');
|
||||||
|
if (found) {
|
||||||
|
container.innerHTML += pass(`Found widget: ${found.widget.id} at tab ${found.tabIndex}, widget ${found.widgetIndex}`);
|
||||||
|
container.innerHTML += `<div class="result">Widget type: ${found.widget.type}</div>`;
|
||||||
|
container.innerHTML += `<div class="result">Position: (${found.widget.x}, ${found.widget.y})</div>`;
|
||||||
|
container.innerHTML += `<div class="result">Size: ${found.widget.w}×${found.widget.h}</div>`;
|
||||||
|
} else {
|
||||||
|
container.innerHTML += fail('Failed to find widget-userstats');
|
||||||
|
}
|
||||||
|
|
||||||
|
const notFound = findWidget(dashboard, 'widget-nonexistent');
|
||||||
|
if (!notFound) {
|
||||||
|
container.innerHTML += pass('Correctly returned null for non-existent widget');
|
||||||
|
} else {
|
||||||
|
container.innerHTML += fail('Should return null for non-existent widget');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update dashboard JSON display
|
||||||
|
function updateDashboardDisplay() {
|
||||||
|
const jsonContainer = document.getElementById('dashboard-json');
|
||||||
|
if (dashboard) {
|
||||||
|
jsonContainer.textContent = JSON.stringify(dashboard, null, 2);
|
||||||
|
} else {
|
||||||
|
jsonContainer.textContent = '// No dashboard generated yet';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update stats
|
||||||
|
function updateStats() {
|
||||||
|
const statsContainer = document.getElementById('stats');
|
||||||
|
|
||||||
|
if (!dashboard) {
|
||||||
|
statsContainer.innerHTML = '<div class="stat-box">No dashboard generated yet</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const widgetCount = getWidgetCount(dashboard);
|
||||||
|
|
||||||
|
statsContainer.innerHTML = `
|
||||||
|
<div class="stat-box">
|
||||||
|
<div class="stat-label">Dashboard Version</div>
|
||||||
|
<div class="stat-value">${dashboard.version}</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-box">
|
||||||
|
<div class="stat-label">Total Tabs</div>
|
||||||
|
<div class="stat-value">${dashboard.tabs.length}</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-box">
|
||||||
|
<div class="stat-label">Total Widgets</div>
|
||||||
|
<div class="stat-value">${widgetCount}</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-box">
|
||||||
|
<div class="stat-label">Grid Columns</div>
|
||||||
|
<div class="stat-value">${dashboard.gridConfig.columns}</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-box">
|
||||||
|
<div class="stat-label">Row Height</div>
|
||||||
|
<div class="stat-value">${dashboard.gridConfig.rowHeight}px</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-box">
|
||||||
|
<div class="stat-label">Default Tab</div>
|
||||||
|
<div class="stat-value">${dashboard.defaultTab}</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run all tests
|
||||||
|
window.runAllTests = function() {
|
||||||
|
test1();
|
||||||
|
setTimeout(() => test2(), 100);
|
||||||
|
setTimeout(() => test3(), 200);
|
||||||
|
setTimeout(() => test4(), 300);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Auto-run on load
|
||||||
|
runAllTests();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user