feat(dashboard): replace emojis with Font Awesome, add theme support, and styled dialogs
This commit implements three major improvements to the dashboard system: 1. **Font Awesome Icons for Tabs** - Replace emoji tab icons (📊, 🌍, 🎒) with Font Awesome classes - Update defaultLayout.js with fa-solid icon classes - Add automatic migration for existing saved dashboards with emoji icons - Implement migrateEmojiIcons() to convert old emoji icons on load - Update fallback icons throughout the system 2. **Custom Theme Support for Dashboard** - Replace all --SmartTheme* variables with --rpg-* variables - Ensure custom themes (sci-fi, fantasy, cyberpunk) apply to dashboard - Update CSS for tabs, buttons, dropdowns, modals, and widget cards - Dashboard now respects extension themes instead of main SillyTavern theme 3. **Styled Confirmation Dialogs** - Create confirmDialog.js with showConfirmDialog() and showAlertDialog() - Support three variants: danger (red), warning (yellow), info (blue) - Add keyboard navigation (Enter/Escape) and accessibility features - Replace all native confirm() and alert() calls with styled dialogs - Add confirmation dialog modal to dashboardTemplate.html Files Modified: - src/systems/dashboard/confirmDialog.js (NEW) - src/systems/dashboard/dashboardManager.js - src/systems/dashboard/defaultLayout.js - src/systems/dashboard/tabManager.js - src/systems/dashboard/dashboardIntegration.js - src/systems/dashboard/editModeManager.js - src/systems/dashboard/widgets/inventoryWidget.js - src/systems/dashboard/dashboardTemplate.html - style.css
This commit is contained in:
@@ -0,0 +1,217 @@
|
|||||||
|
/**
|
||||||
|
* Confirmation Dialog System
|
||||||
|
*
|
||||||
|
* Provides styled confirmation and alert dialogs to replace native browser popups.
|
||||||
|
* Supports three variants: danger (red), warning (yellow), and info (blue).
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a confirmation dialog
|
||||||
|
* @param {Object} options - Dialog options
|
||||||
|
* @param {string} options.title - Dialog title
|
||||||
|
* @param {string} options.message - Dialog message
|
||||||
|
* @param {string} [options.variant='danger'] - Dialog variant: 'danger', 'warning', or 'info'
|
||||||
|
* @param {string} [options.confirmText='Confirm'] - Confirm button text
|
||||||
|
* @param {string} [options.cancelText='Cancel'] - Cancel button text
|
||||||
|
* @param {Function} [options.onConfirm] - Callback when confirmed
|
||||||
|
* @param {Function} [options.onCancel] - Callback when cancelled
|
||||||
|
* @returns {Promise<boolean>} Resolves to true if confirmed, false if cancelled
|
||||||
|
*/
|
||||||
|
export function showConfirmDialog(options) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const {
|
||||||
|
title = 'Confirm Action',
|
||||||
|
message = 'Are you sure?',
|
||||||
|
variant = 'danger',
|
||||||
|
confirmText = 'Confirm',
|
||||||
|
cancelText = 'Cancel',
|
||||||
|
onConfirm = null,
|
||||||
|
onCancel = null
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
// Get modal elements
|
||||||
|
const modal = document.getElementById('rpg-confirm-dialog');
|
||||||
|
const modalContent = modal.querySelector('.rpg-confirm-content');
|
||||||
|
const icon = document.getElementById('rpg-confirm-icon');
|
||||||
|
const titleEl = document.getElementById('rpg-confirm-title');
|
||||||
|
const messageEl = document.getElementById('rpg-confirm-message');
|
||||||
|
const confirmBtn = document.getElementById('rpg-confirm-confirm');
|
||||||
|
const cancelBtn = document.getElementById('rpg-confirm-cancel');
|
||||||
|
const closeBtn = modal.querySelector('.rpg-confirm-close');
|
||||||
|
|
||||||
|
if (!modal) {
|
||||||
|
console.error('[ConfirmDialog] Modal not found');
|
||||||
|
return resolve(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set icon based on variant
|
||||||
|
const iconMap = {
|
||||||
|
danger: 'fa-solid fa-triangle-exclamation',
|
||||||
|
warning: 'fa-solid fa-circle-exclamation',
|
||||||
|
info: 'fa-solid fa-circle-info'
|
||||||
|
};
|
||||||
|
icon.className = `rpg-confirm-icon ${iconMap[variant] || iconMap.danger}`;
|
||||||
|
|
||||||
|
// Set variant class on modal content
|
||||||
|
modalContent.className = `rpg-modal-content rpg-confirm-content rpg-confirm-${variant}`;
|
||||||
|
|
||||||
|
// Set content
|
||||||
|
titleEl.textContent = title;
|
||||||
|
messageEl.textContent = message;
|
||||||
|
confirmBtn.textContent = confirmText;
|
||||||
|
cancelBtn.textContent = cancelText;
|
||||||
|
|
||||||
|
// Show modal
|
||||||
|
modal.style.display = 'flex';
|
||||||
|
|
||||||
|
// Handle confirm
|
||||||
|
const handleConfirm = () => {
|
||||||
|
modal.style.display = 'none';
|
||||||
|
cleanup();
|
||||||
|
if (onConfirm) onConfirm();
|
||||||
|
resolve(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle cancel
|
||||||
|
const handleCancel = () => {
|
||||||
|
modal.style.display = 'none';
|
||||||
|
cleanup();
|
||||||
|
if (onCancel) onCancel();
|
||||||
|
resolve(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle keyboard
|
||||||
|
const handleKeyDown = (e) => {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
handleCancel();
|
||||||
|
} else if (e.key === 'Enter') {
|
||||||
|
handleConfirm();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle backdrop click
|
||||||
|
const handleBackdropClick = (e) => {
|
||||||
|
if (e.target === modal) {
|
||||||
|
handleCancel();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Clean up event listeners
|
||||||
|
const cleanup = () => {
|
||||||
|
confirmBtn.removeEventListener('click', handleConfirm);
|
||||||
|
cancelBtn.removeEventListener('click', handleCancel);
|
||||||
|
closeBtn.removeEventListener('click', handleCancel);
|
||||||
|
document.removeEventListener('keydown', handleKeyDown);
|
||||||
|
modal.removeEventListener('click', handleBackdropClick);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Attach event listeners
|
||||||
|
confirmBtn.addEventListener('click', handleConfirm);
|
||||||
|
cancelBtn.addEventListener('click', handleCancel);
|
||||||
|
closeBtn.addEventListener('click', handleCancel);
|
||||||
|
document.addEventListener('keydown', handleKeyDown);
|
||||||
|
modal.addEventListener('click', handleBackdropClick);
|
||||||
|
|
||||||
|
// Focus confirm button
|
||||||
|
setTimeout(() => confirmBtn.focus(), 100);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show an alert dialog (info only, single OK button)
|
||||||
|
* @param {Object} options - Dialog options
|
||||||
|
* @param {string} options.title - Dialog title
|
||||||
|
* @param {string} options.message - Dialog message
|
||||||
|
* @param {string} [options.variant='info'] - Dialog variant: 'danger', 'warning', or 'info'
|
||||||
|
* @param {string} [options.okText='OK'] - OK button text
|
||||||
|
* @param {Function} [options.onOk] - Callback when OK clicked
|
||||||
|
* @returns {Promise<void>} Resolves when OK clicked
|
||||||
|
*/
|
||||||
|
export function showAlertDialog(options) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const {
|
||||||
|
title = 'Alert',
|
||||||
|
message = '',
|
||||||
|
variant = 'info',
|
||||||
|
okText = 'OK',
|
||||||
|
onOk = null
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
// Get modal elements
|
||||||
|
const modal = document.getElementById('rpg-confirm-dialog');
|
||||||
|
const modalContent = modal.querySelector('.rpg-confirm-content');
|
||||||
|
const icon = document.getElementById('rpg-confirm-icon');
|
||||||
|
const titleEl = document.getElementById('rpg-confirm-title');
|
||||||
|
const messageEl = document.getElementById('rpg-confirm-message');
|
||||||
|
const confirmBtn = document.getElementById('rpg-confirm-confirm');
|
||||||
|
const cancelBtn = document.getElementById('rpg-confirm-cancel');
|
||||||
|
const closeBtn = modal.querySelector('.rpg-confirm-close');
|
||||||
|
|
||||||
|
if (!modal) {
|
||||||
|
console.error('[ConfirmDialog] Modal not found');
|
||||||
|
return resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set icon based on variant
|
||||||
|
const iconMap = {
|
||||||
|
danger: 'fa-solid fa-triangle-exclamation',
|
||||||
|
warning: 'fa-solid fa-circle-exclamation',
|
||||||
|
info: 'fa-solid fa-circle-info'
|
||||||
|
};
|
||||||
|
icon.className = `rpg-confirm-icon ${iconMap[variant] || iconMap.info}`;
|
||||||
|
|
||||||
|
// Set variant class on modal content
|
||||||
|
modalContent.className = `rpg-modal-content rpg-confirm-content rpg-confirm-${variant}`;
|
||||||
|
|
||||||
|
// Set content
|
||||||
|
titleEl.textContent = title;
|
||||||
|
messageEl.textContent = message;
|
||||||
|
confirmBtn.textContent = okText;
|
||||||
|
|
||||||
|
// Hide cancel button for alerts
|
||||||
|
cancelBtn.style.display = 'none';
|
||||||
|
|
||||||
|
// Show modal
|
||||||
|
modal.style.display = 'flex';
|
||||||
|
|
||||||
|
// Handle OK
|
||||||
|
const handleOk = () => {
|
||||||
|
modal.style.display = 'none';
|
||||||
|
cancelBtn.style.display = ''; // Restore for future confirms
|
||||||
|
cleanup();
|
||||||
|
if (onOk) onOk();
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle keyboard
|
||||||
|
const handleKeyDown = (e) => {
|
||||||
|
if (e.key === 'Escape' || e.key === 'Enter') {
|
||||||
|
handleOk();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle backdrop click
|
||||||
|
const handleBackdropClick = (e) => {
|
||||||
|
if (e.target === modal) {
|
||||||
|
handleOk();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Clean up event listeners
|
||||||
|
const cleanup = () => {
|
||||||
|
confirmBtn.removeEventListener('click', handleOk);
|
||||||
|
closeBtn.removeEventListener('click', handleOk);
|
||||||
|
document.removeEventListener('keydown', handleKeyDown);
|
||||||
|
modal.removeEventListener('click', handleBackdropClick);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Attach event listeners
|
||||||
|
confirmBtn.addEventListener('click', handleOk);
|
||||||
|
closeBtn.addEventListener('click', handleOk);
|
||||||
|
document.addEventListener('keydown', handleKeyDown);
|
||||||
|
modal.addEventListener('click', handleBackdropClick);
|
||||||
|
|
||||||
|
// Focus OK button
|
||||||
|
setTimeout(() => confirmBtn.focus(), 100);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ import { WidgetRegistry } from './widgetRegistry.js';
|
|||||||
import { generateDefaultDashboard } from './defaultLayout.js';
|
import { generateDefaultDashboard } from './defaultLayout.js';
|
||||||
import { TabScrollManager } from './tabScrollManager.js';
|
import { TabScrollManager } from './tabScrollManager.js';
|
||||||
import { HeaderOverflowManager } from './headerOverflowManager.js';
|
import { HeaderOverflowManager } from './headerOverflowManager.js';
|
||||||
|
import { showConfirmDialog } from './confirmDialog.js';
|
||||||
|
|
||||||
// Widget imports
|
// Widget imports
|
||||||
import { registerUserInfoWidget } from './widgets/userInfoWidget.js';
|
import { registerUserInfoWidget } from './widgets/userInfoWidget.js';
|
||||||
@@ -224,9 +225,17 @@ function setupDashboardEventListeners(dependencies) {
|
|||||||
// Reset layout button
|
// Reset layout button
|
||||||
const resetLayoutBtn = document.querySelector('#rpg-dashboard-reset-layout');
|
const resetLayoutBtn = document.querySelector('#rpg-dashboard-reset-layout');
|
||||||
if (resetLayoutBtn) {
|
if (resetLayoutBtn) {
|
||||||
resetLayoutBtn.addEventListener('click', () => {
|
resetLayoutBtn.addEventListener('click', async () => {
|
||||||
if (dashboardManager) {
|
if (dashboardManager) {
|
||||||
if (confirm('Reset dashboard to default layout? This will remove all widgets and reload the defaults.')) {
|
const confirmed = await showConfirmDialog({
|
||||||
|
title: 'Reset Layout?',
|
||||||
|
message: 'This will remove all widgets and reload the default layout. This action cannot be undone.',
|
||||||
|
variant: 'danger',
|
||||||
|
confirmText: 'Reset',
|
||||||
|
cancelText: 'Cancel'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (confirmed) {
|
||||||
console.log('[RPG Companion] Reset layout button clicked');
|
console.log('[RPG Companion] Reset layout button clicked');
|
||||||
dashboardManager.resetLayout();
|
dashboardManager.resetLayout();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ export class DashboardManager {
|
|||||||
this.dashboard.tabs.push({
|
this.dashboard.tabs.push({
|
||||||
id: 'main',
|
id: 'main',
|
||||||
name: 'Main',
|
name: 'Main',
|
||||||
icon: '🏠',
|
icon: 'fa-solid fa-house',
|
||||||
order: 0,
|
order: 0,
|
||||||
widgets: []
|
widgets: []
|
||||||
});
|
});
|
||||||
@@ -361,7 +361,7 @@ export class DashboardManager {
|
|||||||
button.className = 'rpg-dashboard-tab';
|
button.className = 'rpg-dashboard-tab';
|
||||||
button.dataset.tabId = tab.id;
|
button.dataset.tabId = tab.id;
|
||||||
button.innerHTML = `
|
button.innerHTML = `
|
||||||
<span class="rpg-tab-icon">${tab.icon}</span>
|
<span class="rpg-tab-icon"><i class="${tab.icon}"></i></span>
|
||||||
<span class="rpg-tab-name">${tab.name}</span>
|
<span class="rpg-tab-name">${tab.name}</span>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -1189,6 +1189,47 @@ export class DashboardManager {
|
|||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrate emoji icons to Font Awesome
|
||||||
|
* @param {Object} config - Dashboard configuration
|
||||||
|
* @returns {Object} Migrated configuration
|
||||||
|
*/
|
||||||
|
migrateEmojiIcons(config) {
|
||||||
|
// Map of common emojis to Font Awesome classes
|
||||||
|
const emojiToFontAwesome = {
|
||||||
|
'📊': 'fa-solid fa-chart-line',
|
||||||
|
'🌍': 'fa-solid fa-map',
|
||||||
|
'🎒': 'fa-solid fa-bag-shopping',
|
||||||
|
'🏠': 'fa-solid fa-house',
|
||||||
|
'📄': 'fa-solid fa-file',
|
||||||
|
'⚙️': 'fa-solid fa-gear',
|
||||||
|
'👤': 'fa-solid fa-user',
|
||||||
|
'📝': 'fa-solid fa-note-sticky',
|
||||||
|
'🗂️': 'fa-solid fa-folder',
|
||||||
|
'📁': 'fa-solid fa-folder-open'
|
||||||
|
};
|
||||||
|
|
||||||
|
if (config && config.tabs) {
|
||||||
|
config.tabs.forEach(tab => {
|
||||||
|
// Check if icon is an emoji (contains emoji characters)
|
||||||
|
if (tab.icon && /[\u{1F300}-\u{1F9FF}]/u.test(tab.icon)) {
|
||||||
|
// Convert to Font Awesome if we have a mapping
|
||||||
|
const faIcon = emojiToFontAwesome[tab.icon];
|
||||||
|
if (faIcon) {
|
||||||
|
console.log(`[DashboardManager] Migrating emoji icon "${tab.icon}" → "${faIcon}" for tab "${tab.name}"`);
|
||||||
|
tab.icon = faIcon;
|
||||||
|
} else {
|
||||||
|
// Fallback to generic file icon
|
||||||
|
console.warn(`[DashboardManager] Unknown emoji icon "${tab.icon}", using fa-solid fa-file for tab "${tab.name}"`);
|
||||||
|
tab.icon = 'fa-solid fa-file';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply dashboard configuration
|
* Apply dashboard configuration
|
||||||
* @param {Object} config - Dashboard configuration
|
* @param {Object} config - Dashboard configuration
|
||||||
@@ -1196,6 +1237,9 @@ export class DashboardManager {
|
|||||||
applyDashboardConfig(config) {
|
applyDashboardConfig(config) {
|
||||||
console.log('[DashboardManager] Applying dashboard config');
|
console.log('[DashboardManager] Applying dashboard config');
|
||||||
|
|
||||||
|
// Migrate emoji icons to Font Awesome
|
||||||
|
config = this.migrateEmojiIcons(config);
|
||||||
|
|
||||||
// Update grid config from dashboard config
|
// Update grid config from dashboard config
|
||||||
if (config.gridConfig) {
|
if (config.gridConfig) {
|
||||||
this.config.rowHeight = config.gridConfig.rowHeight || this.config.rowHeight;
|
this.config.rowHeight = config.gridConfig.rowHeight || this.config.rowHeight;
|
||||||
|
|||||||
@@ -132,4 +132,28 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Confirmation Dialog Modal -->
|
||||||
|
<div id="rpg-confirm-dialog" class="rpg-modal rpg-confirm-modal" style="display: none;">
|
||||||
|
<div class="rpg-modal-content rpg-confirm-content">
|
||||||
|
<div class="rpg-modal-header rpg-confirm-header">
|
||||||
|
<div class="rpg-confirm-header-content">
|
||||||
|
<i id="rpg-confirm-icon" class="rpg-confirm-icon"></i>
|
||||||
|
<h3 id="rpg-confirm-title"></h3>
|
||||||
|
</div>
|
||||||
|
<button class="rpg-modal-close rpg-confirm-close">
|
||||||
|
<i class="fa-solid fa-times"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rpg-modal-body rpg-confirm-body">
|
||||||
|
<p id="rpg-confirm-message"></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rpg-modal-footer rpg-confirm-footer">
|
||||||
|
<button class="rpg-btn-secondary" id="rpg-confirm-cancel"></button>
|
||||||
|
<button class="rpg-btn-primary" id="rpg-confirm-confirm"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export function generateDefaultDashboard() {
|
|||||||
{
|
{
|
||||||
id: 'tab-status',
|
id: 'tab-status',
|
||||||
name: 'Status',
|
name: 'Status',
|
||||||
icon: '📊',
|
icon: 'fa-solid fa-user',
|
||||||
order: 0,
|
order: 0,
|
||||||
widgets: [
|
widgets: [
|
||||||
// Row 0: User Info (left) + User Mood (top right in 3-col)
|
// Row 0: User Info (left) + User Mood (top right in 3-col)
|
||||||
@@ -86,7 +86,7 @@ export function generateDefaultDashboard() {
|
|||||||
{
|
{
|
||||||
id: 'tab-scene',
|
id: 'tab-scene',
|
||||||
name: 'Scene',
|
name: 'Scene',
|
||||||
icon: '🌍',
|
icon: 'fa-solid fa-map',
|
||||||
order: 1,
|
order: 1,
|
||||||
widgets: [
|
widgets: [
|
||||||
// Row 0: Calendar (left) + Weather (right)
|
// Row 0: Calendar (left) + Weather (right)
|
||||||
@@ -162,7 +162,7 @@ export function generateDefaultDashboard() {
|
|||||||
{
|
{
|
||||||
id: 'tab-inventory',
|
id: 'tab-inventory',
|
||||||
name: 'Inventory',
|
name: 'Inventory',
|
||||||
icon: '🎒',
|
icon: 'fa-solid fa-bag-shopping',
|
||||||
order: 2,
|
order: 2,
|
||||||
widgets: [
|
widgets: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
* Handles edit controls, widget library, and layout modifications.
|
* Handles edit controls, widget library, and layout modifications.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { showConfirmDialog } from './confirmDialog.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} EditModeConfig
|
* @typedef {Object} EditModeConfig
|
||||||
* @property {HTMLElement} container - Dashboard container element
|
* @property {HTMLElement} container - Dashboard container element
|
||||||
@@ -453,9 +455,16 @@ export class EditModeManager {
|
|||||||
* Show confirmation dialog before canceling
|
* Show confirmation dialog before canceling
|
||||||
* @param {Function} onConfirm - Callback if confirmed
|
* @param {Function} onConfirm - Callback if confirmed
|
||||||
*/
|
*/
|
||||||
confirmCancel(onConfirm) {
|
async confirmCancel(onConfirm) {
|
||||||
const message = 'You have unsaved changes. Are you sure you want to cancel?';
|
const confirmed = await showConfirmDialog({
|
||||||
if (confirm(message)) {
|
title: 'Discard Changes?',
|
||||||
|
message: 'You have unsaved changes. Are you sure you want to discard them?',
|
||||||
|
variant: 'warning',
|
||||||
|
confirmText: 'Discard',
|
||||||
|
cancelText: 'Keep Editing'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (confirmed) {
|
||||||
onConfirm();
|
onConfirm();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -464,9 +473,16 @@ export class EditModeManager {
|
|||||||
* Show confirmation dialog before deleting widget
|
* Show confirmation dialog before deleting widget
|
||||||
* @param {string} widgetId - Widget ID to delete
|
* @param {string} widgetId - Widget ID to delete
|
||||||
*/
|
*/
|
||||||
confirmDeleteWidget(widgetId) {
|
async confirmDeleteWidget(widgetId) {
|
||||||
const message = 'Are you sure you want to delete this widget?';
|
const confirmed = await showConfirmDialog({
|
||||||
if (confirm(message)) {
|
title: 'Delete Widget?',
|
||||||
|
message: 'Are you sure you want to delete this widget? This action cannot be undone.',
|
||||||
|
variant: 'danger',
|
||||||
|
confirmText: 'Delete',
|
||||||
|
cancelText: 'Cancel'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (confirmed) {
|
||||||
if (this.onWidgetDelete) {
|
if (this.onWidgetDelete) {
|
||||||
this.onWidgetDelete(widgetId);
|
this.onWidgetDelete(widgetId);
|
||||||
}
|
}
|
||||||
@@ -477,9 +493,16 @@ export class EditModeManager {
|
|||||||
* Show confirmation dialog before resetting layout
|
* Show confirmation dialog before resetting layout
|
||||||
* @param {Function} onConfirm - Callback if confirmed
|
* @param {Function} onConfirm - Callback if confirmed
|
||||||
*/
|
*/
|
||||||
confirmReset(onConfirm) {
|
async confirmReset(onConfirm) {
|
||||||
const message = 'This will reset the layout to default. Are you sure?';
|
const confirmed = await showConfirmDialog({
|
||||||
if (confirm(message)) {
|
title: 'Reset Layout?',
|
||||||
|
message: 'This will reset the layout to default. All widgets will be removed and the default layout will be restored.',
|
||||||
|
variant: 'danger',
|
||||||
|
confirmText: 'Reset',
|
||||||
|
cancelText: 'Cancel'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (confirmed) {
|
||||||
onConfirm();
|
onConfirm();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ export class TabManager {
|
|||||||
const tab = {
|
const tab = {
|
||||||
id,
|
id,
|
||||||
name: config.name,
|
name: config.name,
|
||||||
icon: config.icon || '📄',
|
icon: config.icon || 'fa-solid fa-file',
|
||||||
order,
|
order,
|
||||||
widgets: []
|
widgets: []
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
import { parseItems, serializeItems } from '../../../utils/itemParser.js';
|
import { parseItems, serializeItems } from '../../../utils/itemParser.js';
|
||||||
import { sanitizeItemName, sanitizeLocationName } from '../../../utils/security.js';
|
import { sanitizeItemName, sanitizeLocationName } from '../../../utils/security.js';
|
||||||
|
import { showAlertDialog } from '../confirmDialog.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert location name to safe HTML ID
|
* Convert location name to safe HTML ID
|
||||||
@@ -663,7 +664,11 @@ export function registerInventoryWidget(registry, dependencies) {
|
|||||||
|
|
||||||
const itemName = sanitizeItemName(rawItemName);
|
const itemName = sanitizeItemName(rawItemName);
|
||||||
if (!itemName) {
|
if (!itemName) {
|
||||||
alert('Invalid item name.');
|
showAlertDialog({
|
||||||
|
title: 'Invalid Item',
|
||||||
|
message: 'Please enter a valid item name.',
|
||||||
|
variant: 'warning'
|
||||||
|
});
|
||||||
hideAddItemForm(widget, field, location);
|
hideAddItemForm(widget, field, location);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -819,7 +824,11 @@ export function registerInventoryWidget(registry, dependencies) {
|
|||||||
|
|
||||||
const locationName = sanitizeLocationName(rawLocationName);
|
const locationName = sanitizeLocationName(rawLocationName);
|
||||||
if (!locationName) {
|
if (!locationName) {
|
||||||
alert('Invalid location name.');
|
showAlertDialog({
|
||||||
|
title: 'Invalid Location',
|
||||||
|
message: 'Please enter a valid location name.',
|
||||||
|
variant: 'warning'
|
||||||
|
});
|
||||||
hideAddLocationForm(widget);
|
hideAddLocationForm(widget);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -829,7 +838,11 @@ export function registerInventoryWidget(registry, dependencies) {
|
|||||||
|
|
||||||
// Check if location already exists
|
// Check if location already exists
|
||||||
if (inventory.stored[locationName]) {
|
if (inventory.stored[locationName]) {
|
||||||
alert('A location with this name already exists.');
|
showAlertDialog({
|
||||||
|
title: 'Duplicate Location',
|
||||||
|
message: 'A location with this name already exists.',
|
||||||
|
variant: 'warning'
|
||||||
|
});
|
||||||
hideAddLocationForm(widget);
|
hideAddLocationForm(widget);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1133,7 +1133,7 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--SmartThemeBodyColor);
|
color: var(--rpg-text);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
@@ -1143,13 +1143,13 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.rpg-dashboard-tab:hover {
|
.rpg-dashboard-tab:hover {
|
||||||
background: var(--SmartThemeBlurTintColor);
|
background: var(--rpg-accent);
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rpg-dashboard-tab.active {
|
.rpg-dashboard-tab.active {
|
||||||
background: var(--SmartThemeBlurTintColor);
|
background: var(--rpg-accent);
|
||||||
border-color: var(--SmartThemeBorderColor);
|
border-color: var(--rpg-border);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
@@ -1182,9 +1182,9 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
width: 2rem;
|
width: 2rem;
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: 1px solid var(--SmartThemeBorderColor);
|
border: 1px solid var(--rpg-border);
|
||||||
background: var(--SmartThemeBlurTintColor);
|
background: var(--rpg-bg);
|
||||||
color: var(--SmartThemeBodyColor);
|
color: var(--rpg-text);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
@@ -1200,7 +1200,7 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
|
|
||||||
.rpg-tab-nav-arrow:hover {
|
.rpg-tab-nav-arrow:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
background: var(--SmartThemeQuoteColor);
|
background: var(--rpg-highlight);
|
||||||
transform: translateY(-50%) scale(1.05);
|
transform: translateY(-50%) scale(1.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1232,7 +1232,7 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
left: 0;
|
left: 0;
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
to right,
|
to right,
|
||||||
var(--SmartThemeBlurTintColor) 0%,
|
var(--rpg-bg) 0%,
|
||||||
transparent 100%
|
transparent 100%
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1241,7 +1241,7 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
right: 0;
|
right: 0;
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
to left,
|
to left,
|
||||||
var(--SmartThemeBlurTintColor) 0%,
|
var(--rpg-bg) 0%,
|
||||||
transparent 100%
|
transparent 100%
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1281,16 +1281,16 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
width: 2rem;
|
width: 2rem;
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
border: 1px solid var(--SmartThemeBorderColor);
|
border: 1px solid var(--rpg-border);
|
||||||
background: var(--SmartThemeBlurTintColor);
|
background: var(--rpg-accent);
|
||||||
color: var(--SmartThemeBodyColor);
|
color: var(--rpg-text);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rpg-dashboard-btn:hover {
|
.rpg-dashboard-btn:hover {
|
||||||
background: var(--SmartThemeQuoteColor);
|
background: var(--rpg-highlight);
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1309,10 +1309,10 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
right: 0;
|
right: 0;
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
background: var(--SmartThemeBlurTintColor);
|
background: var(--rpg-bg);
|
||||||
border: 1px solid var(--SmartThemeBorderColor);
|
border: 1px solid var(--rpg-border);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
box-shadow: 0 4px 12px var(--rpg-shadow);
|
||||||
z-index: 10003; /* Above modals (10000) and mobile toggle (10002) */
|
z-index: 10003; /* Above modals (10000) and mobile toggle (10002) */
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
animation: slideDown 0.2s ease-out;
|
animation: slideDown 0.2s ease-out;
|
||||||
@@ -1338,7 +1338,7 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
padding: 0.75rem 1rem;
|
padding: 0.75rem 1rem;
|
||||||
border: none;
|
border: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--SmartThemeBodyColor);
|
color: var(--rpg-text);
|
||||||
text-align: left;
|
text-align: left;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background 0.15s;
|
transition: background 0.15s;
|
||||||
@@ -1347,7 +1347,7 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
|
|
||||||
.rpg-dropdown-item:hover,
|
.rpg-dropdown-item:hover,
|
||||||
.rpg-dropdown-item:focus {
|
.rpg-dropdown-item:focus {
|
||||||
background: var(--SmartThemeQuoteColor);
|
background: var(--rpg-highlight);
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1444,13 +1444,13 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
|
|
||||||
/* Modal Content Container */
|
/* Modal Content Container */
|
||||||
.rpg-modal-content {
|
.rpg-modal-content {
|
||||||
background: var(--SmartThemeBlurTintColor);
|
background: var(--rpg-bg);
|
||||||
border: 2px solid var(--SmartThemeBorderColor);
|
border: 2px solid var(--rpg-border);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
max-height: 80vh;
|
max-height: 80vh;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
|
box-shadow: 0 4px 20px var(--rpg-shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Modal Header */
|
/* Modal Header */
|
||||||
@@ -1459,12 +1459,12 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
border-bottom: 1px solid var(--SmartThemeBorderColor);
|
border-bottom: 1px solid var(--rpg-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.rpg-modal-header h3 {
|
.rpg-modal-header h3 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: var(--SmartThemeBodyColor);
|
color: var(--rpg-text);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
@@ -1473,7 +1473,7 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
.rpg-modal-close {
|
.rpg-modal-close {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
color: var(--SmartThemeBodyColor);
|
color: var(--rpg-text);
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 0.25rem 0.5rem;
|
padding: 0.25rem 0.5rem;
|
||||||
@@ -1496,7 +1496,7 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
border-top: 1px solid var(--SmartThemeBorderColor);
|
border-top: 1px solid var(--rpg-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Widget Grid in Modal */
|
/* Widget Grid in Modal */
|
||||||
@@ -1509,9 +1509,9 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
/* Widget Card */
|
/* Widget Card */
|
||||||
.rpg-widget-card {
|
.rpg-widget-card {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
border: 2px solid var(--SmartThemeBorderColor);
|
border: 2px solid var(--rpg-border);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: var(--SmartThemeQuoteColor);
|
background: var(--rpg-accent);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -1522,7 +1522,7 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
.rpg-widget-card:hover {
|
.rpg-widget-card:hover {
|
||||||
border-color: var(--rpg-highlight);
|
border-color: var(--rpg-highlight);
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
box-shadow: 0 4px 12px var(--rpg-shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
.rpg-widget-card-icon {
|
.rpg-widget-card-icon {
|
||||||
@@ -1533,12 +1533,13 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
.rpg-widget-card-name {
|
.rpg-widget-card-name {
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--SmartThemeBodyColor);
|
color: var(--rpg-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.rpg-widget-card-description {
|
.rpg-widget-card-description {
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
color: var(--SmartThemeFastUISliderColColor);
|
color: var(--rpg-text);
|
||||||
|
opacity: 0.7;
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1587,12 +1588,12 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
|
|
||||||
.rpg-btn-secondary {
|
.rpg-btn-secondary {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: 1px solid var(--SmartThemeBorderColor);
|
border: 1px solid var(--rpg-border);
|
||||||
color: var(--SmartThemeBodyColor);
|
color: var(--rpg-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.rpg-btn-secondary:hover {
|
.rpg-btn-secondary:hover {
|
||||||
background: var(--SmartThemeQuoteColor);
|
background: var(--rpg-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tab Drop Zone Highlight */
|
/* Tab Drop Zone Highlight */
|
||||||
@@ -1603,6 +1604,87 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
box-shadow: 0 0 12px var(--rpg-highlight);
|
box-shadow: 0 0 12px var(--rpg-highlight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ========================================
|
||||||
|
Confirmation Dialog Styles
|
||||||
|
======================================== */
|
||||||
|
|
||||||
|
/* Confirmation Dialog Content */
|
||||||
|
.rpg-confirm-content {
|
||||||
|
max-width: 450px;
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Confirmation Dialog Header */
|
||||||
|
.rpg-confirm-header-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rpg-confirm-icon {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Confirmation Dialog Body */
|
||||||
|
.rpg-confirm-body {
|
||||||
|
padding: 1.5rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rpg-confirm-body p {
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: var(--rpg-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Confirmation Dialog Footer */
|
||||||
|
.rpg-confirm-footer {
|
||||||
|
padding: 1rem;
|
||||||
|
display: flex;
|
||||||
|
gap: 0.75rem;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Variant Styles - Danger (Red) */
|
||||||
|
.rpg-confirm-danger .rpg-confirm-icon {
|
||||||
|
color: #ff4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rpg-confirm-danger .rpg-btn-primary {
|
||||||
|
background: #ff4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rpg-confirm-danger .rpg-btn-primary:hover {
|
||||||
|
background: #cc0000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Variant Styles - Warning (Yellow/Orange) */
|
||||||
|
.rpg-confirm-warning .rpg-confirm-icon {
|
||||||
|
color: #ffaa00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rpg-confirm-warning .rpg-btn-primary {
|
||||||
|
background: #ffaa00;
|
||||||
|
color: #1a1a1a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rpg-confirm-warning .rpg-btn-primary:hover {
|
||||||
|
background: #cc8800;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Variant Styles - Info (Blue) */
|
||||||
|
.rpg-confirm-info .rpg-confirm-icon {
|
||||||
|
color: #00aaff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rpg-confirm-info .rpg-btn-primary {
|
||||||
|
background: #00aaff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rpg-confirm-info .rpg-btn-primary:hover {
|
||||||
|
background: #0088cc;
|
||||||
|
}
|
||||||
|
|
||||||
.rpg-dashboard-grid {
|
.rpg-dashboard-grid {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
Reference in New Issue
Block a user