From 5e6cce936d3cf4eef410b4d80e43de815f47600a Mon Sep 17 00:00:00 2001 From: Will Miao <13051207myq@gmail.com> Date: Wed, 5 Mar 2025 22:03:54 +0800 Subject: [PATCH] Add update notifications --- static/css/components/modal.css | 230 +++++++++++++++++++++++++- static/css/layout.css | 113 ++++++++++++- static/js/managers/ModalManager.js | 90 ++++++++++ static/js/managers/SettingsManager.js | 7 - templates/components/modals.html | 60 +++++++ templates/loras.html | 26 ++- 6 files changed, 503 insertions(+), 23 deletions(-) diff --git a/static/css/components/modal.css b/static/css/components/modal.css index f056dd16..4f318f37 100644 --- a/static/css/components/modal.css +++ b/static/css/components/modal.css @@ -535,7 +535,7 @@ body.modal-open { background: var(--bg-color); border: 1px solid var(--border-color); border-radius: var(--border-radius-xs); - color: var(--text-color); + color: var (--text-color); font-family: monospace; font-size: 0.9em; overflow-x: auto; @@ -1131,4 +1131,232 @@ body.modal-open { .support-links { flex-direction: column; } +} + +/* Update Modal Styles */ +.update-modal { + max-width: 600px; +} + +.update-header { + display: flex; + align-items: center; + gap: var(--space-2); + margin-bottom: var(--space-3); + padding-bottom: var(--space-2); + border-bottom: 1px solid var(--lora-border); +} + +.update-icon { + font-size: 1.8em; + color: var(--lora-accent); + animation: bounce 1.5s infinite; +} + +@keyframes bounce { + 0%, 100% { + transform: translateY(0); + } + 50% { + transform: translateY(-5px); + } +} + +.update-content { + display: flex; + flex-direction: column; + gap: var(--space-3); +} + +.update-info { + display: flex; + justify-content: space-between; + align-items: center; + background: var(--bg-color); + border: 1px solid var(--lora-border); + border-radius: var(--border-radius-sm); + padding: var(--space-2); +} + +.version-info { + display: flex; + flex-direction: column; + gap: 8px; +} + +.current-version, .new-version { + display: flex; + align-items: center; + gap: 10px; +} + +.label { + font-size: 0.9em; + color: var(--text-color); + opacity: 0.8; +} + +.version-number { + font-family: monospace; + font-weight: 600; +} + +.new-version .version-number { + color: var(--lora-accent); +} + +.update-link { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 16px; + background: var(--lora-surface); + border: 1px solid var(--lora-border); + border-radius: var(--border-radius-sm); + text-decoration: none; + color: var(--text-color); + transition: all 0.2s ease; +} + +.update-link:hover { + background: var(--lora-accent); + color: white; + transform: translateY(-2px); +} + +.changelog-section, .update-instructions { + background: var(--bg-color); + border: 1px solid var(--lora-border); + border-radius: var(--border-radius-sm); + padding: var(--space-2); +} + +.changelog-section h3, .update-instructions h3 { + margin-top: 0; + margin-bottom: var(--space-2); + color: var(--lora-accent); + font-size: 1.1em; +} + +.changelog-content { + max-height: 200px; + overflow-y: auto; +} + +.changelog-item { + margin-bottom: var(--space-2); + padding-bottom: var(--space-2); + border-bottom: 1px solid var(--lora-border); +} + +.changelog-item:last-child { + margin-bottom: 0; + padding-bottom: 0; + border-bottom: none; +} + +.changelog-item h4 { + margin-top: 0; + margin-bottom: 8px; + font-size: 1em; + color: var(--text-color); +} + +.changelog-item ul { + margin: 0; + padding-left: 20px; +} + +.changelog-item li { + margin-bottom: 4px; + color: var(--text-color); +} + +.code-block { + background: var(--lora-surface); + border: 1px solid var(--lora-border); + border-radius: var(--border-radius-xs); + padding: var(--space-1) var(--space-2); + margin: var(--space-1) 0; + font-family: monospace; + color: var(--text-color); + overflow-x: auto; +} + +@media (max-width: 480px) { + .update-info { + flex-direction: column; + gap: var(--space-2); + } + + .version-info { + width: 100%; + } +} + +/* Update preferences section */ +.update-preferences { + border-top: 1px solid var(--lora-border); + margin-top: var(--space-2); + padding-top: var(--space-2); +} + +/* Toggle switch styles */ +.toggle-switch { + display: flex; + align-items: center; + gap: 12px; + cursor: pointer; + user-select: none; +} + +.toggle-switch input { + opacity: 0; + width: 0; + height: 0; + position: absolute; +} + +.toggle-slider { + position: relative; + display: inline-block; + width: 40px; + height: 20px; + background-color: var(--border-color); + border-radius: 20px; + transition: .4s; + flex-shrink: 0; +} + +.toggle-slider:before { + position: absolute; + content: ""; + height: 16px; + width: 16px; + left: 2px; + bottom: 2px; + background-color: white; + border-radius: 50%; + transition: .4s; +} + +input:checked + .toggle-slider { + background-color: var(--lora-accent); +} + +input:checked + .toggle-slider:before { + transform: translateX(20px); +} + +.toggle-label { + font-size: 0.9em; + color: var(--text-color); +} + +.preference-note { + margin-top: 4px; + font-size: 0.8em; + color: var(--text-color); + opacity: 0.7; + margin-left: 52px; } \ No newline at end of file diff --git a/static/css/layout.css b/static/css/layout.css index 990f5198..cebaf311 100644 --- a/static/css/layout.css +++ b/static/css/layout.css @@ -162,16 +162,109 @@ font-weight: bold; } +/* Update corner-controls for collapsible behavior */ .corner-controls { position: fixed; top: 20px; right: 20px; - display: flex; - gap: 10px; z-index: var(--z-overlay); + display: flex; + flex-direction: column; + align-items: center; + transition: all 0.3s ease; +} + +.corner-controls-toggle { + width: 36px; + height: 36px; + border-radius: 50%; + background: var(--card-bg); + border: 1px solid var(--border-color); + color: var(--text-color); + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.2s ease; + z-index: 2; + margin-bottom: 10px; +} + +.corner-controls-toggle:hover { + background: var(--lora-accent); + color: white; + transform: translateY(-2px); +} + +.corner-controls-items { + display: flex; + flex-direction: column; + gap: 10px; + opacity: 0; + transform: translateY(-10px) scale(0.9); + transition: all 0.3s ease; + pointer-events: none; +} + +/* Expanded state */ +.corner-controls.expanded .corner-controls-items { + opacity: 1; + transform: translateY(0) scale(1); + pointer-events: all; +} + +/* Expanded state - only expand on hover if not already expanded by click */ +.corner-controls:hover:not(.expanded) .corner-controls-items { + opacity: 1; + transform: translateY(0) scale(1); + pointer-events: all; +} + +/* Ensure hidden class works properly */ +.hidden { + display: none !important; +} + +/* Update toggle button styles */ +.update-toggle { + width: 36px; + height: 36px; + border-radius: 50%; + background: var(--card-bg); + border: 1px solid var(--border-color); + color: var(--text-color); /* Changed from var(--lora-accent) to match other toggles */ + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.2s ease; + position: relative; +} + +.update-toggle:hover { + background: var(--lora-accent); + color: white; + transform: translateY(-2px); +} + +/* Update badge styles */ +.update-badge { + position: absolute; + top: -3px; + right: -3px; + background-color: var(--lora-error); + width: 8px; + height: 8px; + border-radius: 50%; + box-shadow: 0 0 0 2px var(--card-bg); +} + +/* Badge on corner toggle */ +.corner-badge { + top: 0; + right: 0; } -/* Folder Tags Container */ .folder-tags-container { position: relative; width: 100%; @@ -560,10 +653,16 @@ } .corner-controls { - /* Keep the fixed positioning even on mobile */ - position: fixed; - top: 20px; - right: 20px; + top: 10px; + right: 10px; + } + + .corner-controls-items { + display: none; + } + + .corner-controls.expanded .corner-controls-items { + display: flex; } .back-to-top { diff --git a/static/js/managers/ModalManager.js b/static/js/managers/ModalManager.js index 38451052..5f0077eb 100644 --- a/static/js/managers/ModalManager.js +++ b/static/js/managers/ModalManager.js @@ -2,6 +2,7 @@ export class ModalManager { constructor() { this.modals = new Map(); this.scrollPosition = 0; + this.updateAvailable = false; } initialize() { @@ -62,8 +63,21 @@ export class ModalManager { } }); + // Add updateModal registration + this.registerModal('updateModal', { + element: document.getElementById('updateModal'), + onClose: () => { + this.getModal('updateModal').element.style.display = 'none'; + document.body.classList.remove('modal-open'); + } + }); + document.addEventListener('keydown', this.boundHandleEscape); this.initialized = true; + + // Initialize corner controls and update modal + this.initCornerControls(); + this.initUpdateModal(); } registerModal(id, config) { @@ -144,6 +158,82 @@ export class ModalManager { } } } + + // Add method to initialize corner controls behavior + initCornerControls() { + const cornerControls = document.querySelector('.corner-controls'); + const cornerControlsToggle = document.querySelector('.corner-controls-toggle'); + + if(cornerControls && cornerControlsToggle) { + // Check for updates (mock implementation) + this.checkForUpdates(); + + // Apply the initial badge state based on localStorage + const showUpdates = localStorage.getItem('show_update_notifications'); + if (showUpdates === 'false') { + this.updateBadgeVisibility(false); + } + } + } + + // Modified update checker + checkForUpdates() { + // First check if user has disabled update notifications + const showUpdates = localStorage.getItem('show_update_notifications'); + + // For demo purposes, we'll simulate an update being available + setTimeout(() => { + // We have an update available (mock) + this.updateAvailable = true; + + // Only show badges if notifications are enabled + const shouldShow = showUpdates !== 'false'; + this.updateBadgeVisibility(shouldShow); + }, 2000); + } + + // Add method to initialize update modal + initUpdateModal() { + const updateModal = document.getElementById('updateModal'); + if (!updateModal) return; + + const checkbox = updateModal.querySelector('#updateNotifications'); + if (!checkbox) return; + + // Set initial state from localStorage or default to true + const showUpdates = localStorage.getItem('show_update_notifications'); + checkbox.checked = showUpdates === null || showUpdates === 'true'; + + // Apply the initial badge visibility based on checkbox state + this.updateBadgeVisibility(checkbox.checked); + + // Add event listener for changes + checkbox.addEventListener('change', (e) => { + localStorage.setItem('show_update_notifications', e.target.checked); + + // Immediately update badge visibility based on the new setting + this.updateBadgeVisibility(e.target.checked); + }); + } + + // Enhanced helper method to update badge visibility + updateBadgeVisibility(show) { + const updateToggle = document.querySelector('.update-toggle'); + const updateBadge = document.querySelector('.update-toggle .update-badge'); + const cornerBadge = document.querySelector('.corner-badge'); + + if (updateToggle) { + updateToggle.title = show && this.updateAvailable ? "Update Available" : "Check Updates"; + } + + if (updateBadge) { + updateBadge.classList.toggle('hidden', !(show && this.updateAvailable)); + } + + if (cornerBadge) { + cornerBadge.classList.toggle('hidden', !(show && this.updateAvailable)); + } + } } // Create and export a singleton instance diff --git a/static/js/managers/SettingsManager.js b/static/js/managers/SettingsManager.js index d3fbd6b1..0d3fd39d 100644 --- a/static/js/managers/SettingsManager.js +++ b/static/js/managers/SettingsManager.js @@ -16,13 +16,6 @@ export class SettingsManager { this.isOpen = !this.isOpen; } - /* - showSettings() { - console.log('Opening settings modal...'); // Debug log - modalManager.showModal('settingsModal'); - } - */ - async saveSettings() { const apiKey = document.getElementById('civitaiApiKey').value; diff --git a/templates/components/modals.html b/templates/components/modals.html index a0726e32..bef795b8 100644 --- a/templates/components/modals.html +++ b/templates/components/modals.html @@ -215,4 +215,64 @@ + + + +