Sync DNS2 production changes - removed obsolete test suite and refactored structure
This commit is contained in:
337
dashcaddy-api/assets/tooltip-definitions.js
Normal file
337
dashcaddy-api/assets/tooltip-definitions.js
Normal file
@@ -0,0 +1,337 @@
|
||||
/**
|
||||
* 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');
|
||||
|
||||
Reference in New Issue
Block a user