338 lines
9.5 KiB
JavaScript
338 lines
9.5 KiB
JavaScript
/**
|
|
* Tooltip Definitions
|
|
* Defines all tooltip content, positioning, and behavior for the onboarding system
|
|
*/
|
|
|
|
(function(window) {
|
|
'use strict';
|
|
|
|
/**
|
|
* Validate a tooltip definition
|
|
* @param {Object} tooltip - The tooltip definition to validate
|
|
* @returns {Object} { valid: boolean, errors: string[] }
|
|
*/
|
|
function validateTooltipDefinition(tooltip) {
|
|
const errors = [];
|
|
|
|
// Required fields
|
|
if (!tooltip.id || typeof tooltip.id !== 'string') {
|
|
errors.push('Tooltip must have a valid string id');
|
|
}
|
|
|
|
if (!tooltip.element) {
|
|
errors.push('Tooltip must have an element selector or HTMLElement');
|
|
}
|
|
|
|
if (!tooltip.popover || typeof tooltip.popover !== 'object') {
|
|
errors.push('Tooltip must have a popover object');
|
|
} else {
|
|
// Validate popover fields
|
|
if (!tooltip.popover.title || typeof tooltip.popover.title !== 'string') {
|
|
errors.push('Tooltip popover must have a valid string title');
|
|
}
|
|
|
|
if (!tooltip.popover.description || typeof tooltip.popover.description !== 'string') {
|
|
errors.push('Tooltip popover must have a valid string description');
|
|
}
|
|
|
|
// Validate position if provided
|
|
if (tooltip.popover.position) {
|
|
const validPositions = ['top', 'bottom', 'left', 'right', 'center'];
|
|
if (!validPositions.includes(tooltip.popover.position)) {
|
|
errors.push(`Invalid position: ${tooltip.popover.position}. Must be one of: ${validPositions.join(', ')}`);
|
|
}
|
|
}
|
|
|
|
// Validate align if provided
|
|
if (tooltip.popover.align) {
|
|
const validAligns = ['start', 'center', 'end'];
|
|
if (!validAligns.includes(tooltip.popover.align)) {
|
|
errors.push(`Invalid align: ${tooltip.popover.align}. Must be one of: ${validAligns.join(', ')}`);
|
|
}
|
|
}
|
|
|
|
// Validate showButtons if provided
|
|
if (tooltip.popover.showButtons && !Array.isArray(tooltip.popover.showButtons)) {
|
|
errors.push('showButtons must be an array');
|
|
}
|
|
|
|
// Validate callbacks if provided
|
|
const callbacks = ['onNext', 'onPrevious', 'onClose', 'onSetupNow', 'onLater'];
|
|
callbacks.forEach(callback => {
|
|
if (tooltip.popover[callback] && typeof tooltip.popover[callback] !== 'function') {
|
|
errors.push(`${callback} must be a function`);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Validate condition if provided
|
|
if (tooltip.condition && typeof tooltip.condition !== 'function') {
|
|
errors.push('condition must be a function');
|
|
}
|
|
|
|
// Validate priority if provided
|
|
if (tooltip.priority !== undefined && typeof tooltip.priority !== 'number') {
|
|
errors.push('priority must be a number');
|
|
}
|
|
|
|
return {
|
|
valid: errors.length === 0,
|
|
errors
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Validate an array of tooltip definitions
|
|
* @param {Array} tooltips - Array of tooltip definitions
|
|
* @returns {Object} { valid: boolean, errors: Object[] }
|
|
*/
|
|
function validateTooltipDefinitions(tooltips) {
|
|
if (!Array.isArray(tooltips)) {
|
|
return {
|
|
valid: false,
|
|
errors: [{ tooltip: null, errors: ['tooltips must be an array'] }]
|
|
};
|
|
}
|
|
|
|
const allErrors = [];
|
|
const ids = new Set();
|
|
|
|
tooltips.forEach((tooltip, index) => {
|
|
const validation = validateTooltipDefinition(tooltip);
|
|
|
|
if (!validation.valid) {
|
|
allErrors.push({
|
|
tooltip: tooltip.id || `index ${index}`,
|
|
errors: validation.errors
|
|
});
|
|
}
|
|
|
|
// Check for duplicate IDs
|
|
if (tooltip.id) {
|
|
if (ids.has(tooltip.id)) {
|
|
allErrors.push({
|
|
tooltip: tooltip.id,
|
|
errors: [`Duplicate tooltip ID: ${tooltip.id}`]
|
|
});
|
|
}
|
|
ids.add(tooltip.id);
|
|
}
|
|
});
|
|
|
|
return {
|
|
valid: allErrors.length === 0,
|
|
errors: allErrors
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Error handler for tooltip system
|
|
*/
|
|
class TooltipError extends Error {
|
|
constructor(message, tooltipId = null) {
|
|
super(message);
|
|
this.name = 'TooltipError';
|
|
this.tooltipId = tooltipId;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle tooltip definition errors
|
|
* @param {Object} validation - Validation result
|
|
* @throws {TooltipError} If validation fails
|
|
*/
|
|
function handleValidationErrors(validation) {
|
|
if (!validation.valid) {
|
|
const errorMessages = validation.errors.map(e =>
|
|
`${e.tooltip}: ${e.errors.join(', ')}`
|
|
).join('\n');
|
|
|
|
console.error('[TooltipDefinitions] Validation errors:', errorMessages);
|
|
throw new TooltipError(`Tooltip validation failed:\n${errorMessages}`);
|
|
}
|
|
}
|
|
|
|
// Export to global scope
|
|
window.TooltipValidation = {
|
|
validateTooltipDefinition,
|
|
validateTooltipDefinitions,
|
|
handleValidationErrors,
|
|
TooltipError
|
|
};
|
|
|
|
console.log('[TooltipDefinitions] Validation module loaded');
|
|
|
|
})(window);
|
|
|
|
|
|
/**
|
|
* Tooltip Definitions Array
|
|
* Defines all tooltips for the onboarding tour
|
|
*/
|
|
const TOOLTIP_DEFINITIONS = [
|
|
// 1. Welcome tooltip pointing to logo
|
|
{
|
|
id: 'welcome',
|
|
element: '#brand',
|
|
popover: {
|
|
title: 'Welcome to DashCaddy!',
|
|
description: `
|
|
<p>Your personal dashboard for managing services with Caddy reverse proxy.</p>
|
|
<p>Let's take a quick tour to help you get started.</p>
|
|
<p style="margin-top: 8px; font-size: 0.85rem; opacity: 0.8;">Tip: You can customize this logo in Settings.</p>
|
|
`,
|
|
position: 'bottom',
|
|
align: 'start',
|
|
showButtons: ['next'],
|
|
showProgress: true
|
|
},
|
|
priority: 1,
|
|
isNewFeature: false
|
|
},
|
|
|
|
// 2. Add Service button
|
|
{
|
|
id: 'add-service',
|
|
element: '#add-service-btn',
|
|
popover: {
|
|
title: 'Adding New Services',
|
|
description: `
|
|
<p>Click <strong>+ Add Service</strong> to deploy new apps or add existing services to your dashboard.</p>
|
|
<p>Choose from 50+ templates including:</p>
|
|
<ul>
|
|
<li>Media servers (Plex, Jellyfin, Emby)</li>
|
|
<li>Download managers (qBittorrent, Transmission)</li>
|
|
<li>DNS servers (Technitium, Pi-hole)</li>
|
|
</ul>
|
|
`,
|
|
position: 'bottom',
|
|
showButtons: ['previous', 'next'],
|
|
showProgress: true
|
|
},
|
|
priority: 2,
|
|
isNewFeature: false,
|
|
condition: () => {
|
|
return document.getElementById('add-service-btn') !== null;
|
|
}
|
|
},
|
|
|
|
// 3. App Grid explanation
|
|
{
|
|
id: 'app-grid',
|
|
element: '#cards',
|
|
popover: {
|
|
title: 'Your Services',
|
|
description: `
|
|
<p>This is your service grid where all your deployed applications appear.</p>
|
|
<p>Each card shows:</p>
|
|
<ul>
|
|
<li>Service status (online/offline)</li>
|
|
<li>Response time</li>
|
|
<li>Quick actions (restart, open, logs, settings)</li>
|
|
</ul>
|
|
`,
|
|
position: 'top',
|
|
showButtons: ['previous', 'next'],
|
|
showProgress: true
|
|
},
|
|
priority: 3,
|
|
isNewFeature: false
|
|
},
|
|
|
|
// 4. Theme selector
|
|
{
|
|
id: 'theme-selector',
|
|
element: '#theme',
|
|
popover: {
|
|
title: 'Customize Your Theme',
|
|
description: `
|
|
<p>DashCaddy comes with 7 themes. Click here to switch between them.</p>
|
|
<p>Your preference is saved automatically.</p>
|
|
`,
|
|
position: 'bottom',
|
|
showButtons: ['previous', 'close'],
|
|
showProgress: true
|
|
},
|
|
priority: 4,
|
|
isNewFeature: false,
|
|
condition: () => {
|
|
return document.getElementById('theme') !== null;
|
|
}
|
|
}
|
|
];
|
|
|
|
/**
|
|
* Get tooltip definitions
|
|
* @returns {Array} Array of tooltip definitions
|
|
*/
|
|
function getTooltipDefinitions() {
|
|
return TOOLTIP_DEFINITIONS;
|
|
}
|
|
|
|
/**
|
|
* Get a specific tooltip by ID
|
|
* @param {string} id - Tooltip ID
|
|
* @returns {Object|null} Tooltip definition or null if not found
|
|
*/
|
|
function getTooltipById(id) {
|
|
return TOOLTIP_DEFINITIONS.find(t => t.id === id) || null;
|
|
}
|
|
|
|
/**
|
|
* Get tooltips filtered by condition
|
|
* @returns {Array} Array of tooltips that pass their condition check
|
|
*/
|
|
function getActiveTooltips() {
|
|
return TOOLTIP_DEFINITIONS.filter(tooltip => {
|
|
if (tooltip.condition && typeof tooltip.condition === 'function') {
|
|
try {
|
|
return tooltip.condition();
|
|
} catch (error) {
|
|
console.error(`[TooltipDefinitions] Error evaluating condition for ${tooltip.id}:`, error);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get tooltips sorted by priority
|
|
* @returns {Array} Array of tooltips sorted by priority (ascending)
|
|
*/
|
|
function getSortedTooltips() {
|
|
const tooltips = getActiveTooltips();
|
|
return tooltips.sort((a, b) => {
|
|
const priorityA = a.priority || 999;
|
|
const priorityB = b.priority || 999;
|
|
return priorityA - priorityB;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get tooltips marked as new features
|
|
* @returns {Array} Array of tooltips marked with isNewFeature flag
|
|
*/
|
|
function getNewFeatureTooltips() {
|
|
const tooltips = getActiveTooltips();
|
|
return tooltips.filter(tooltip => tooltip.isNewFeature === true)
|
|
.sort((a, b) => {
|
|
const priorityA = a.priority || 999;
|
|
const priorityB = b.priority || 999;
|
|
return priorityA - priorityB;
|
|
});
|
|
}
|
|
|
|
// Export to global scope
|
|
window.TooltipDefinitions = {
|
|
TOOLTIP_DEFINITIONS,
|
|
getTooltipDefinitions,
|
|
getTooltipById,
|
|
getActiveTooltips,
|
|
getSortedTooltips,
|
|
getNewFeatureTooltips
|
|
};
|
|
|
|
console.log('[TooltipDefinitions] Definitions loaded:', TOOLTIP_DEFINITIONS.length, 'tooltips');
|
|
|