diff --git a/static/css/components/header.css b/static/css/components/header.css index 0cac5bfb..f6a5ee68 100644 --- a/static/css/components/header.css +++ b/static/css/components/header.css @@ -136,6 +136,30 @@ opacity: 0; } +/* Badge styling */ +.update-badge { + position: absolute; + top: -3px; + right: -3px; + width: 8px; + height: 8px; + background-color: var(--lora-error); + border-radius: 50%; + border: 2px solid var(--card-bg); + transition: all 0.2s ease; + pointer-events: none; + opacity: 0; +} + +.update-badge.visible { + opacity: 1; +} + +.update-badge.hidden, +.update-badge:not(.visible) { + opacity: 0; +} + /* Mobile adjustments */ @media (max-width: 768px) { .app-title { diff --git a/static/css/components/modal.css b/static/css/components/modal.css index cfedbb43..78e42b89 100644 --- a/static/css/components/modal.css +++ b/static/css/components/modal.css @@ -544,7 +544,7 @@ input:checked + .toggle-slider:before { gap: 8px; padding: 8px 16px; background-color: var(--card-bg); - color: var(--text-color); + color: var (--text-color); border: 1px solid var(--border-color); border-radius: var(--border-radius-sm); cursor: pointer; @@ -718,4 +718,204 @@ input:checked + .toggle-slider:before { .density-description li { margin-bottom: 4px; +} + +/* Help Modal styles */ +.help-modal { + max-width: 850px; +} + +.help-header { + display: flex; + align-items: center; + margin-bottom: var(--space-2); +} + +.modal-help-icon { + font-size: 24px; + color: var(--lora-accent); + margin-right: var(--space-2); + vertical-align: text-bottom; +} + +/* Tab navigation styles */ +.help-tabs { + display: flex; + border-bottom: 1px solid var(--lora-border); + margin-bottom: var(--space-2); + gap: 8px; +} + +.tab-btn { + padding: 8px 16px; + background: transparent; + border: none; + border-bottom: 2px solid transparent; + color: var(--text-color); + cursor: pointer; + font-weight: 500; + transition: all 0.2s; + opacity: 0.7; +} + +.tab-btn:hover { + background-color: rgba(0, 0, 0, 0.05); + opacity: 0.9; +} + +.tab-btn.active { + color: var(--lora-accent); + border-bottom: 2px solid var(--lora-accent); + opacity: 1; +} + +/* Tab content styles */ +.help-content { + padding: var(--space-1) 0; + overflow-y: auto; +} + +.tab-pane { + display: none; +} + +.tab-pane.active { + display: block; +} + +/* Video embed styles */ +.video-embed { + position: relative; + padding-bottom: 56.25%; /* 16:9 aspect ratio */ + height: 0; + overflow: hidden; + max-width: 100%; + margin-bottom: var(--space-2); + border-radius: var(--border-radius-sm); +} + +.video-embed iframe { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +.video-embed.small { + max-width: 100%; + margin-bottom: var(--space-1); +} + +.help-text { + margin: var(--space-2) 0; +} + +.help-text ul { + padding-left: 20px; + margin-top: 8px; +} + +.help-text li { + margin-bottom: 8px; +} + +/* Documentation link styles */ +.docs-section { + margin-bottom: var(--space-3); +} + +.docs-section h4 { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: var(--space-1); +} + +.docs-links { + list-style-type: none; + padding-left: var(--space-3); +} + +.docs-links li { + margin-bottom: var(--space-1); + position: relative; +} + +.docs-links li:before { + content: "•"; + position: absolute; + left: -15px; + color: var(--lora-accent); +} + +.docs-links a { + color: var(--lora-accent); + text-decoration: none; + transition: color 0.2s; +} + +.docs-links a:hover { + text-decoration: underline; +} + +/* Update video list styles */ +.video-list { + display: flex; + flex-direction: column; + gap: var(--space-3); +} + +.video-item { + display: flex; + flex-direction: column; +} + +.video-info { + padding: var(--space-1); +} + +.video-info h4 { + margin-bottom: var(--space-1); +} + +.video-info p { + font-size: 0.9em; + opacity: 0.8; +} + +/* Dark theme adjustments */ +[data-theme="dark"] .tab-btn:hover { + background-color: rgba(255, 255, 255, 0.05); +} + +/* Update date badge styles */ +.update-date-badge { + display: inline-flex; + align-items: center; + font-size: 0.75em; + font-weight: 500; + background-color: var(--lora-accent); + color: var(--lora-text); + padding: 4px 8px; + border-radius: 12px; + margin-left: 10px; + vertical-align: middle; + animation: fadeIn 0.5s ease-in-out; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.update-date-badge i { + margin-right: 5px; + font-size: 0.9em; +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(-5px); } + to { opacity: 1; transform: translateY(0); } +} + +/* Dark theme adjustments */ +[data-theme="dark"] .update-date-badge { + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); } \ No newline at end of file diff --git a/static/js/components/Header.js b/static/js/components/Header.js index 4de0e83d..17f4129f 100644 --- a/static/js/components/Header.js +++ b/static/js/components/Header.js @@ -75,7 +75,9 @@ export class HeaderManager { const supportToggle = document.getElementById('supportToggleBtn'); if (supportToggle) { supportToggle.addEventListener('click', () => { - // Handle support panel logic + if (window.modalManager) { + window.modalManager.toggleModal('supportModal'); + } }); } @@ -106,5 +108,15 @@ export class HeaderManager { } }); } + + // Handle help toggle + // const helpToggle = document.querySelector('.help-toggle'); + // if (helpToggle) { + // helpToggle.addEventListener('click', () => { + // if (window.modalManager) { + // window.modalManager.toggleModal('helpModal'); + // } + // }); + // } } } diff --git a/static/js/core.js b/static/js/core.js index b0b858ea..f71eab59 100644 --- a/static/js/core.js +++ b/static/js/core.js @@ -6,6 +6,7 @@ import { updateService } from './managers/UpdateService.js'; import { HeaderManager } from './components/Header.js'; import { settingsManager } from './managers/SettingsManager.js'; import { exampleImagesManager } from './managers/ExampleImagesManager.js'; +import { helpManager } from './managers/HelpManager.js'; import { showToast, initTheme, initBackToTop } from './utils/uiHelpers.js'; import { initializeInfiniteScroll } from './utils/infiniteScroll.js'; import { migrateStorageItems } from './utils/storageHelpers.js'; @@ -30,6 +31,7 @@ export class AppCore { window.modalManager = modalManager; window.settingsManager = settingsManager; window.exampleImagesManager = exampleImagesManager; + window.helpManager = helpManager; // Initialize UI components window.headerManager = new HeaderManager(); @@ -38,6 +40,8 @@ export class AppCore { // Initialize the example images manager exampleImagesManager.initialize(); + // Initialize the help manager + helpManager.initialize(); // Mark as initialized this.initialized = true; diff --git a/static/js/managers/HelpManager.js b/static/js/managers/HelpManager.js new file mode 100644 index 00000000..59ab819e --- /dev/null +++ b/static/js/managers/HelpManager.js @@ -0,0 +1,155 @@ +import { getStorageItem, setStorageItem } from '../utils/storageHelpers.js'; + +/** + * Manages help modal functionality and tutorial update notifications + */ +export class HelpManager { + constructor() { + this.lastViewedTimestamp = getStorageItem('help_last_viewed', 0); + this.latestContentTimestamp = 0; // Will be updated from server or config + this.isInitialized = false; + + // Default latest content data - could be fetched from server + this.latestVideoData = { + timestamp: new Date('2024-06-09').getTime(), // Default timestamp + walkthrough: { + id: 'hvKw31YpE-U', + title: 'Getting Started with LoRA Manager' + }, + playlistUpdated: true + }; + } + + /** + * Initialize the help manager + */ + initialize() { + if (this.isInitialized) return; + + console.log('HelpManager: Initializing...'); + + // Set up event handlers + this.setupEventListeners(); + + // Check if we need to show the badge + this.updateHelpBadge(); + + // Fetch latest video data (could be implemented to fetch from remote source) + this.fetchLatestVideoData(); + + this.isInitialized = true; + return this; + } + + /** + * Set up event listeners for help modal + */ + setupEventListeners() { + // Help toggle button + const helpToggleBtn = document.getElementById('helpToggleBtn'); + if (helpToggleBtn) { + helpToggleBtn.addEventListener('click', () => this.openHelpModal()); + } + + // Help modal tab functionality + const tabButtons = document.querySelectorAll('.help-tabs .tab-btn'); + tabButtons.forEach(button => { + button.addEventListener('click', (event) => { + // Remove active class from all buttons and panes + document.querySelectorAll('.help-tabs .tab-btn').forEach(btn => { + btn.classList.remove('active'); + }); + document.querySelectorAll('.help-content .tab-pane').forEach(pane => { + pane.classList.remove('active'); + }); + + // Add active class to clicked button + event.currentTarget.classList.add('active'); + + // Show corresponding tab content + const tabId = event.currentTarget.getAttribute('data-tab'); + document.getElementById(tabId).classList.add('active'); + }); + }); + } + + /** + * Open the help modal + */ + openHelpModal() { + // Use modalManager to open the help modal + if (window.modalManager) { + window.modalManager.toggleModal('helpModal'); + + // Update the last viewed timestamp + this.markContentAsViewed(); + + // Hide the badge + this.hideHelpBadge(); + } + } + + /** + * Mark content as viewed by saving current timestamp + */ + markContentAsViewed() { + this.lastViewedTimestamp = Date.now(); + setStorageItem('help_last_viewed', this.lastViewedTimestamp); + } + + /** + * Fetch latest video data (could be implemented to actually fetch from a remote source) + */ + fetchLatestVideoData() { + // In a real implementation, you'd fetch this from your server + // For now, we'll just use the hardcoded data from constructor + + // Update the timestamp with the latest data + this.latestContentTimestamp = this.latestVideoData.timestamp; + + // Check again if we need to show the badge with this new data + this.updateHelpBadge(); + } + + /** + * Update help badge visibility based on timestamps + */ + updateHelpBadge() { + if (this.hasNewContent()) { + this.showHelpBadge(); + } else { + this.hideHelpBadge(); + } + } + + /** + * Check if there's new content the user hasn't seen + */ + hasNewContent() { + // If user has never viewed the help, or the content is newer than last viewed + return this.lastViewedTimestamp === 0 || this.latestContentTimestamp > this.lastViewedTimestamp; + } + + /** + * Show the help badge + */ + showHelpBadge() { + const helpBadge = document.querySelector('#helpToggleBtn .update-badge'); + if (helpBadge) { + helpBadge.classList.add('visible'); + } + } + + /** + * Hide the help badge + */ + hideHelpBadge() { + const helpBadge = document.querySelector('#helpToggleBtn .update-badge'); + if (helpBadge) { + helpBadge.classList.remove('visible'); + } + } +} + +// Create singleton instance +export const helpManager = new HelpManager(); \ No newline at end of file diff --git a/static/js/managers/ModalManager.js b/static/js/managers/ModalManager.js index fa196dc6..d4ed36af 100644 --- a/static/js/managers/ModalManager.js +++ b/static/js/managers/ModalManager.js @@ -207,11 +207,18 @@ export class ModalManager { } }); } - - // Set up event listeners for modal toggles - const supportToggle = document.getElementById('supportToggleBtn'); - if (supportToggle) { - supportToggle.addEventListener('click', () => this.toggleModal('supportModal')); + + // Add helpModal registration + const helpModal = document.getElementById('helpModal'); + if (helpModal) { + this.registerModal('helpModal', { + element: helpModal, + onClose: () => { + this.getModal('helpModal').element.style.display = 'none'; + document.body.classList.remove('modal-open'); + }, + closeOnOutsideClick: true + }); } document.addEventListener('keydown', this.boundHandleEscape); diff --git a/static/js/managers/UpdateService.js b/static/js/managers/UpdateService.js index 1aa1074f..c08e354c 100644 --- a/static/js/managers/UpdateService.js +++ b/static/js/managers/UpdateService.js @@ -119,7 +119,6 @@ export class UpdateService { updateBadgeVisibility() { const updateToggle = document.querySelector('.update-toggle'); const updateBadge = document.querySelector('.update-toggle .update-badge'); - const cornerBadge = document.querySelector('.corner-badge'); if (updateToggle) { updateToggle.title = this.updateNotificationsEnabled && this.updateAvailable @@ -134,11 +133,6 @@ export class UpdateService { updateBadge.classList.toggle('hidden', !shouldShow); console.log("Update badge visibility:", !shouldShow ? "hidden" : "visible"); } - - if (cornerBadge) { - cornerBadge.classList.toggle('hidden', !shouldShow); - console.log("Corner badge visibility:", !shouldShow ? "hidden" : "visible"); - } } updateModalContent() { diff --git a/templates/components/header.html b/templates/components/header.html index 51a7ba06..91373d15 100644 --- a/templates/components/header.html +++ b/templates/components/header.html @@ -43,6 +43,10 @@