From 1bcb9e38b40371b9983e4b29e24a4dd604b65f3f Mon Sep 17 00:00:00 2001 From: retoor Date: Sat, 3 Jan 2026 22:15:15 +0100 Subject: [PATCH] Update. --- static/css/billing.css | 84 +++ static/css/code-editor-view.css | 54 ++ static/css/file-upload-view.css | 61 ++ static/css/mobile.css | 702 +++++++++++++++++++++++ static/css/style.css | 49 +- static/index.html | 1 + static/js/components/file-list.js | 121 ++++ static/js/components/file-preview.js | 21 + static/js/components/file-upload-view.js | 19 + static/js/components/mywebdav-app.js | 77 ++- static/js/components/photo-gallery.js | 34 ++ static/js/components/share-modal.js | 21 + static/js/gesture-handler.js | 362 ++++++++++++ 13 files changed, 1554 insertions(+), 52 deletions(-) create mode 100644 static/css/mobile.css create mode 100644 static/js/gesture-handler.js diff --git a/static/css/billing.css b/static/css/billing.css index 7f05cc8..ba739a9 100644 --- a/static/css/billing.css +++ b/static/css/billing.css @@ -384,3 +384,87 @@ border: 1px solid #e5e7eb; border-radius: 8px; } + +@media (max-width: 767px) { + .billing-dashboard, + .admin-billing { + padding: 16px; + } + + .billing-header { + flex-direction: column; + align-items: flex-start; + gap: 12px; + } + + .billing-cards { + grid-template-columns: 1fr; + gap: 16px; + } + + .stats-cards { + grid-template-columns: 1fr; + gap: 16px; + } + + .estimated-cost { + font-size: 1.5rem; + } + + .stat-value { + font-size: 1.5rem; + } + + .invoices-section, + .payment-methods-section, + .pricing-config-section, + .invoice-generation-section { + padding: 16px; + } + + .invoices-table { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } + + .invoices-table table { + min-width: 600px; + } + + .pricing-table { + min-width: 500px; + } + + .invoice-gen-form { + flex-direction: column; + align-items: stretch; + } + + .invoice-gen-form label { + width: 100%; + } + + .invoice-gen-form input { + width: 100%; + } + + .modal-content { + width: 100%; + height: 100%; + max-width: none; + max-height: none; + border-radius: 0; + } + + .modal-actions { + flex-direction: column; + } + + .modal-actions .button { + width: 100%; + } + + .payment-methods-section .button { + width: 100%; + } +} diff --git a/static/css/code-editor-view.css b/static/css/code-editor-view.css index 4098f5f..ce64320 100644 --- a/static/css/code-editor-view.css +++ b/static/css/code-editor-view.css @@ -88,3 +88,57 @@ .code-editor-body textarea { display: none; } + +@media (max-width: 767px) { + .code-editor-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 200; + } + + .code-editor-header { + padding: 12px 16px; + padding-top: calc(12px + env(safe-area-inset-top, 0)); + flex-wrap: wrap; + gap: 8px; + } + + .code-editor-header .header-left { + gap: 8px; + flex: 1; + min-width: 0; + } + + .code-editor-header .preview-actions { + gap: 4px; + } + + .code-editor-header .button { + min-width: 44px; + min-height: 44px; + padding: 8px 12px; + font-size: 14px; + } + + .editor-filename { + font-size: 14px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .code-editor-body { + padding-bottom: env(safe-area-inset-bottom, 0); + } + + .code-editor-body .CodeMirror { + font-size: 13px; + } + + .code-editor-body .CodeMirror-linenumber { + padding: 0 4px; + } +} diff --git a/static/css/file-upload-view.css b/static/css/file-upload-view.css index 966a510..ca6a915 100644 --- a/static/css/file-upload-view.css +++ b/static/css/file-upload-view.css @@ -143,3 +143,64 @@ color: #dc3545; font-weight: 500; } + +@media (max-width: 767px) { + .file-upload-view { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 200; + } + + .file-upload-header { + padding: 12px 16px; + padding-top: calc(12px + env(safe-area-inset-top, 0)); + } + + .file-upload-header .header-left { + gap: 12px; + } + + .file-upload-header h2 { + font-size: 16px; + } + + .file-upload-header .button { + min-width: 44px; + min-height: 44px; + } + + .file-upload-body { + padding: 16px; + gap: 16px; + padding-bottom: calc(16px + env(safe-area-inset-bottom, 0)); + } + + .drop-zone { + padding: 32px 16px; + } + + .drop-zone-icon { + font-size: 48px; + } + + .drop-zone h3 { + font-size: 16px; + } + + .upload-item { + padding: 12px; + } + + .upload-item-info { + flex-direction: column; + align-items: flex-start; + gap: 4px; + } + + .upload-item-name { + word-break: break-all; + } +} diff --git a/static/css/mobile.css b/static/css/mobile.css new file mode 100644 index 0000000..7737bd9 --- /dev/null +++ b/static/css/mobile.css @@ -0,0 +1,702 @@ +/* retoor */ +/* Mobile-First Responsive Styles */ + +:root { + --touch-target-min: 44px; + --touch-target-comfortable: 48px; + --sidebar-width-mobile: 280px; + --header-height-mobile: 56px; + --safe-area-inset-top: env(safe-area-inset-top, 0); + --safe-area-inset-bottom: env(safe-area-inset-bottom, 0); + --safe-area-inset-left: env(safe-area-inset-left, 0); + --safe-area-inset-right: env(safe-area-inset-right, 0); +} + +/* Hamburger Button */ +.hamburger-btn { + display: none; + flex-direction: column; + justify-content: center; + align-items: center; + width: var(--touch-target-min); + height: var(--touch-target-min); + padding: 8px; + background: transparent; + border: none; + cursor: pointer; + gap: 5px; + margin-right: 8px; + border-radius: 8px; + -webkit-tap-highlight-color: transparent; +} + +.hamburger-btn span { + display: block; + width: 24px; + height: 2px; + background-color: var(--primary-color); + border-radius: 2px; + transition: transform 0.3s ease, opacity 0.3s ease; +} + +.hamburger-btn.active span:nth-child(1) { + transform: translateY(7px) rotate(45deg); +} + +.hamburger-btn.active span:nth-child(2) { + opacity: 0; +} + +.hamburger-btn.active span:nth-child(3) { + transform: translateY(-7px) rotate(-45deg); +} + +.hamburger-btn:active { + background-color: rgba(0, 51, 153, 0.1); +} + +/* Sidebar Overlay */ +.sidebar-overlay { + display: none; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + z-index: 149; + opacity: 0; + transition: opacity 0.3s ease; + -webkit-tap-highlight-color: transparent; +} + +.sidebar-overlay.visible { + opacity: 1; +} + +/* Pull to Refresh Indicator */ +.pull-to-refresh-indicator { + display: flex; + align-items: center; + justify-content: center; + gap: 12px; + height: 0; + overflow: hidden; + background-color: var(--background-color); + transition: height 0.2s ease; + opacity: 0; +} + +.pull-spinner { + width: 24px; + height: 24px; + border: 2px solid var(--border-color); + border-top-color: var(--primary-color); + border-radius: 50%; +} + +.pull-to-refresh-indicator.refreshing .pull-spinner { + animation: spin 0.8s linear infinite; +} + +.pull-to-refresh-indicator.ready .pull-spinner { + border-color: var(--primary-color); + border-top-color: var(--primary-color); +} + +.pull-text { + font-size: 0.875rem; + color: var(--text-color-light); +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +/* Context Menu */ +.context-menu { + position: fixed; + min-width: 180px; + max-width: 280px; + background-color: var(--accent-color); + border-radius: 12px; + box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15); + z-index: 1000; + padding: 8px 0; + opacity: 0; + transform: scale(0.95); + transition: opacity 0.2s ease, transform 0.2s ease; + overflow: hidden; +} + +.context-menu.visible { + opacity: 1; + transform: scale(1); +} + +.context-menu-item { + display: flex; + align-items: center; + gap: 12px; + width: 100%; + padding: 12px 16px; + background: none; + border: none; + font-size: 1rem; + color: var(--text-color); + text-align: left; + cursor: pointer; + min-height: var(--touch-target-min); + -webkit-tap-highlight-color: transparent; +} + +.context-menu-item:active { + background-color: rgba(0, 51, 153, 0.1); +} + +.context-menu-item.destructive { + color: #d32f2f; +} + +.context-menu-icon { + width: 20px; + text-align: center; +} + +.context-menu-separator { + height: 1px; + background-color: var(--border-color); + margin: 8px 0; +} + +/* Mobile Base Styles (320px+) */ +@media (max-width: 767px) { + .hamburger-btn { + display: flex; + } + + .sidebar-overlay { + display: block; + } + + .app-header { + height: var(--header-height-mobile); + padding: 0 8px; + padding-top: var(--safe-area-inset-top); + } + + .app-title { + font-size: 1.25rem; + } + + .header-center { + flex: 1; + max-width: none; + margin: 0 8px; + } + + .search-input { + height: var(--touch-target-min); + font-size: 16px; + } + + .header-right { + gap: 8px; + } + + .header-right .user-info { + display: none; + } + + .header-right .button { + min-width: var(--touch-target-min); + min-height: var(--touch-target-min); + padding: 8px 12px; + } + + .app-body { + flex-direction: column; + } + + .app-sidebar { + position: fixed; + top: 0; + left: 0; + width: var(--sidebar-width-mobile); + height: 100vh; + height: 100dvh; + transform: translateX(-100%); + transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); + z-index: 150; + background-color: var(--accent-color); + box-shadow: none; + padding-top: var(--safe-area-inset-top); + overflow-y: auto; + } + + .app-sidebar.open { + transform: translateX(0); + box-shadow: 4px 0 24px rgba(0, 0, 0, 0.15); + } + + .sidebar-nav { + padding: 16px; + } + + .nav-title { + font-size: 0.75rem; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--text-color-light); + margin: 16px 0 8px; + } + + .nav-title:first-child { + margin-top: 0; + } + + .nav-list { + list-style: none; + padding: 0; + margin: 0; + } + + .nav-link { + display: flex; + align-items: center; + min-height: var(--touch-target-comfortable); + padding: 12px 16px; + color: var(--text-color); + text-decoration: none; + border-radius: 8px; + font-size: 1rem; + -webkit-tap-highlight-color: transparent; + } + + .nav-link:active { + background-color: rgba(0, 51, 153, 0.1); + } + + .nav-link.active { + background-color: rgba(0, 51, 153, 0.1); + color: var(--primary-color); + font-weight: 500; + } + + .app-main { + flex: 1; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + } + + .app-footer { + padding: 16px; + padding-bottom: calc(16px + var(--safe-area-inset-bottom)); + } + + .footer-links { + flex-wrap: wrap; + gap: 8px 16px; + justify-content: center; + } + + .footer-links li a { + font-size: 0.75rem; + min-height: var(--touch-target-min); + display: flex; + align-items: center; + } + + /* File List Mobile */ + .file-list-header { + padding: 12px; + flex-wrap: wrap; + gap: 8px; + } + + .file-list-title { + font-size: 1.25rem; + } + + .file-actions { + display: flex; + flex-wrap: wrap; + gap: 8px; + width: 100%; + } + + .file-actions .button { + min-height: var(--touch-target-min); + flex: 1; + min-width: calc(50% - 4px); + justify-content: center; + } + + .file-grid { + grid-template-columns: 1fr; + gap: 1px; + background-color: var(--border-color); + padding: 0; + } + + .file-item { + min-height: 64px; + padding: 12px 16px; + background-color: var(--accent-color); + gap: 12px; + } + + .file-item-checkbox { + width: 24px; + height: 24px; + min-width: 24px; + } + + .file-item-checkbox::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: var(--touch-target-min); + height: var(--touch-target-min); + } + + .file-item-icon { + width: 40px; + height: 40px; + } + + .file-item-info { + flex: 1; + min-width: 0; + } + + .file-item-name { + font-size: 1rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .file-item-meta { + font-size: 0.75rem; + color: var(--text-color-light); + } + + .file-item-actions { + display: flex; + gap: 4px; + } + + .file-item-actions .action-btn { + width: var(--touch-target-min); + height: var(--touch-target-min); + min-width: var(--touch-target-min); + } + + /* Breadcrumb Mobile */ + .breadcrumb { + padding: 8px 12px; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + white-space: nowrap; + } + + .breadcrumb-item { + min-height: var(--touch-target-min); + display: inline-flex; + align-items: center; + padding: 0 8px; + } + + /* Overlays & Modals Mobile */ + .modal-overlay, + .file-upload-overlay, + .file-preview-overlay, + .code-editor-overlay { + padding: 0; + } + + .modal-content, + .upload-content, + .preview-content, + .editor-content { + width: 100%; + height: 100%; + max-width: none; + max-height: none; + border-radius: 0; + margin: 0; + } + + /* Bottom Sheet Style Modal */ + .bottom-sheet { + position: fixed; + bottom: 0; + left: 0; + right: 0; + max-height: 90vh; + border-radius: 16px 16px 0 0; + padding-bottom: var(--safe-area-inset-bottom); + transform: translateY(100%); + transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); + } + + .bottom-sheet.visible { + transform: translateY(0); + } + + .bottom-sheet-handle { + width: 36px; + height: 4px; + background-color: var(--border-color); + border-radius: 2px; + margin: 8px auto 16px; + } + + /* Login View Mobile */ + .login-container { + padding: 16px; + padding-top: calc(16px + var(--safe-area-inset-top)); + } + + .auth-tabs { + min-height: var(--touch-target-comfortable); + } + + .auth-tab { + min-height: var(--touch-target-comfortable); + font-size: 1rem; + } + + .auth-form input { + height: var(--touch-target-comfortable); + font-size: 16px; + padding: 12px 16px; + } + + .auth-form .button { + min-height: var(--touch-target-comfortable); + font-size: 1rem; + } + + /* Photo Gallery Mobile */ + .gallery-grid { + grid-template-columns: repeat(2, 1fr); + gap: 2px; + } + + .gallery-item { + aspect-ratio: 1; + } + + /* Toast Mobile */ + .toast-notification { + left: 16px; + right: 16px; + bottom: calc(16px + var(--safe-area-inset-bottom)); + max-width: none; + transform: translateY(100px); + } + + .toast-notification.visible { + transform: translateY(0); + } + + /* Billing Dashboard Mobile */ + .billing-container { + padding: 16px; + } + + .billing-cards { + flex-direction: column; + } + + .billing-card { + width: 100%; + } + + .invoice-table-container { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } + + .invoice-table { + min-width: 500px; + } + + /* Admin Dashboard Mobile */ + .admin-container { + padding: 16px; + } + + .user-table-container { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } + + .user-table { + min-width: 600px; + } + + /* User Settings Mobile */ + .settings-container { + padding: 16px; + } + + .settings-section .button { + width: 100%; + min-height: var(--touch-target-comfortable); + } + + /* Share Modal Mobile - Bottom Sheet */ + .share-modal { + top: auto; + bottom: 0; + left: 0; + right: 0; + transform: translateY(100%); + max-height: 85vh; + border-radius: 16px 16px 0 0; + padding-bottom: var(--safe-area-inset-bottom); + } + + .share-modal.visible { + transform: translateY(0); + } + + .share-modal-content { + padding: 16px; + } + + .share-form input, + .share-form select { + height: var(--touch-target-comfortable); + font-size: 16px; + } + + .share-form .button { + min-height: var(--touch-target-comfortable); + width: 100%; + } + + /* Cookie Consent Mobile */ + .cookie-consent { + left: 8px; + right: 8px; + bottom: calc(8px + var(--safe-area-inset-bottom)); + padding: 16px; + } + + .cookie-consent-buttons { + flex-direction: column; + gap: 8px; + } + + .cookie-consent-buttons .button { + width: 100%; + min-height: var(--touch-target-min); + } +} + +/* Mobile Landscape (480px+) */ +@media (min-width: 480px) and (max-width: 767px) { + .gallery-grid { + grid-template-columns: repeat(3, 1fr); + } + + .file-actions .button { + min-width: auto; + flex: 0 1 auto; + } +} + +/* Tablet (768px+) */ +@media (min-width: 768px) { + .hamburger-btn { + display: none; + } + + .sidebar-overlay { + display: none; + } + + .app-sidebar { + position: relative; + transform: none; + width: var(--sidebar-width); + } + + .file-grid { + grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); + } + + .gallery-grid { + grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); + } +} + +/* Desktop (1024px+) */ +@media (min-width: 1024px) { + .file-grid { + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + } + + .gallery-grid { + grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); + } +} + +/* Touch-specific styles */ +@media (hover: none) and (pointer: coarse) { + .button:hover, + .nav-link:hover, + .file-item:hover { + background-color: inherit; + } + + .button:active, + .nav-link:active { + background-color: rgba(0, 51, 153, 0.1); + } + + .file-item:active { + background-color: rgba(0, 51, 153, 0.05); + } + + * { + -webkit-tap-highlight-color: transparent; + } +} + +/* Dark Mode Mobile Adjustments */ +body.dark-mode .hamburger-btn span { + background-color: var(--text-color); +} + +body.dark-mode .hamburger-btn:active { + background-color: rgba(255, 255, 255, 0.1); +} + +body.dark-mode .nav-link:active, +body.dark-mode .button:active { + background-color: rgba(255, 255, 255, 0.1); +} + +body.dark-mode .context-menu { + background-color: #333; + box-shadow: 0 4px 24px rgba(0, 0, 0, 0.4); +} + +body.dark-mode .context-menu-item:active { + background-color: rgba(255, 255, 255, 0.1); +} + +body.dark-mode .pull-to-refresh-indicator { + background-color: var(--background-color); +} + +body.dark-mode .file-item { + background-color: var(--background-color); +} diff --git a/static/css/style.css b/static/css/style.css index 0653449..246bd7e 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -579,53 +579,6 @@ body { font-size: 1.25rem; } -@media (max-width: 768px) { - .app-header { - flex-direction: column; - height: auto; - padding: var(--spacing-unit); - } - - .header-center { - width: 100%; - max-width: none; - margin: var(--spacing-unit) 0; - } - - .header-right { - width: 100%; - justify-content: space-between; - } - - .app-body { - flex-direction: column; - } - - .app-sidebar { - width: 100%; - border-right: none; - border-bottom: 1px solid var(--border-color); - max-height: 200px; - } - - .file-grid { - grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); - } -} - -@media (max-width: 480px) { - .file-grid { - grid-template-columns: 1fr; - } - - .file-actions { - flex-direction: column; - } - - .file-actions button { - width: 100%; - } -} body.dark-mode { --background-color: #222222; @@ -993,7 +946,7 @@ body.dark-mode { border-color: var(--primary-color); background-color: rgba(0, 51, 153, 0.05); } --e + .shared-items-container { background-color: var(--accent-color); border-radius: 8px; diff --git a/static/index.html b/static/index.html index 9eba412..fadde8b 100644 --- a/static/index.html +++ b/static/index.html @@ -52,6 +52,7 @@ + diff --git a/static/js/components/file-list.js b/static/js/components/file-list.js index f731758..2454661 100644 --- a/static/js/components/file-list.js +++ b/static/js/components/file-list.js @@ -1,4 +1,5 @@ import { api } from '../api.js'; +import { GestureHandler, PullToRefreshIndicator, ContextMenu, isMobile } from '../gesture-handler.js'; export class FileList extends HTMLElement { constructor() { @@ -12,6 +13,9 @@ export class FileList extends HTMLElement { this.boundHandleClick = this.handleClick.bind(this); this.boundHandleDblClick = this.handleDblClick.bind(this); this.boundHandleChange = this.handleChange.bind(this); + this.gestureHandler = null; + this.pullIndicator = null; + this.contextMenu = new ContextMenu(); } async connectedCallback() { @@ -27,6 +31,12 @@ export class FileList extends HTMLElement { this.removeEventListener('click', this.boundHandleClick); this.removeEventListener('dblclick', this.boundHandleDblClick); this.removeEventListener('change', this.boundHandleChange); + if (this.gestureHandler) { + this.gestureHandler.destroy(); + } + if (this.pullIndicator) { + this.pullIndicator.destroy(); + } } async loadContents(folderId) { @@ -305,6 +315,117 @@ export class FileList extends HTMLElement { attachListeners() { this.updateBatchActionVisibility(); + this.initGestures(); + } + + initGestures() { + if (this.gestureHandler) { + this.gestureHandler.destroy(); + } + if (this.pullIndicator) { + this.pullIndicator.destroy(); + } + + const container = this.querySelector('.file-list-container'); + if (!container) return; + + this.pullIndicator = new PullToRefreshIndicator(container); + this.gestureHandler = new GestureHandler(container); + + this.gestureHandler.on('pullToRefresh', async () => { + this.pullIndicator.showRefreshing(); + await this.loadContents(this.currentFolderId); + this.pullIndicator.hide(); + }); + + this.gestureHandler.on('longPress', (data) => { + if (!isMobile()) return; + + const fileItem = data.target?.closest('.file-item'); + if (!fileItem) return; + + const folderId = fileItem.dataset.folderId; + const fileId = fileItem.dataset.fileId; + + if (folderId) { + const folder = this.folders.find(f => f.id === parseInt(folderId)); + if (folder) { + this.showFolderContextMenu(data.x, data.y, folder); + } + } else if (fileId) { + const file = this.files.find(f => f.id === parseInt(fileId)); + if (file) { + this.showFileContextMenu(data.x, data.y, file); + } + } + }); + + container.addEventListener('pull-progress', (e) => { + this.pullIndicator.setProgress(e.detail.progress); + }); + + container.addEventListener('pull-end', () => { + if (this.pullIndicator) { + this.pullIndicator.hide(); + } + }); + } + + showFileContextMenu(x, y, file) { + const items = [ + { + label: 'Download', + icon: '⬇', + action: () => this.handleAction('download', file.id) + }, + { + label: 'Rename', + icon: '✏', + action: () => this.handleAction('rename', file.id) + }, + { + label: 'Share', + icon: '🔗', + action: () => this.handleAction('share', file.id) + }, + { separator: true }, + { + label: file.is_starred ? 'Unstar' : 'Star', + icon: file.is_starred ? '★' : '☆', + action: () => this.handleAction(file.is_starred ? 'unstar-file' : 'star-file', file.id) + }, + { separator: true }, + { + label: 'Delete', + icon: '🗑', + destructive: true, + action: () => this.handleAction('delete', file.id) + } + ]; + this.contextMenu.show(x, y, items); + } + + showFolderContextMenu(x, y, folder) { + const items = [ + { + label: 'Open', + icon: '📂', + action: () => this.loadContents(folder.id) + }, + { + label: folder.is_starred ? 'Unstar' : 'Star', + icon: folder.is_starred ? '★' : '☆', + action: () => this.handleAction(folder.is_starred ? 'unstar-folder' : 'star-folder', folder.id) + }, + { separator: true }, + { + label: 'Delete', + icon: '🗑', + destructive: true, + action: () => this.handleAction('delete-folder', folder.id) + } + ]; + this.contextMenu.show(x, y, items); } toggleSelectItem(type, id, checked) { diff --git a/static/js/components/file-preview.js b/static/js/components/file-preview.js index 4461902..14dea56 100644 --- a/static/js/components/file-preview.js +++ b/static/js/components/file-preview.js @@ -1,15 +1,36 @@ import { api } from '../api.js'; +import { GestureHandler, isMobile } from '../gesture-handler.js'; class FilePreview extends HTMLElement { constructor() { super(); this.file = null; this.handleEscape = this.handleEscape.bind(this); + this.gestureHandler = null; } connectedCallback() { this.render(); this.setupEventListeners(); + this.initGestures(); + } + + disconnectedCallback() { + if (this.gestureHandler) { + this.gestureHandler.destroy(); + } + } + + initGestures() { + const overlay = this.querySelector('.file-preview-overlay'); + if (!overlay) return; + + this.gestureHandler = new GestureHandler(overlay); + this.gestureHandler.on('swipeDown', () => { + if (isMobile()) { + this.close(); + } + }); } setupEventListeners() { diff --git a/static/js/components/file-upload-view.js b/static/js/components/file-upload-view.js index 8fd1cf6..53491e8 100644 --- a/static/js/components/file-upload-view.js +++ b/static/js/components/file-upload-view.js @@ -1,4 +1,5 @@ import { api } from '../api.js'; +import { GestureHandler, isMobile } from '../gesture-handler.js'; export class FileUploadView extends HTMLElement { constructor() { @@ -6,6 +7,7 @@ export class FileUploadView extends HTMLElement { this.folderId = null; this.handleEscape = this.handleEscape.bind(this); this.uploadItems = new Map(); + this.gestureHandler = null; } connectedCallback() { @@ -14,6 +16,21 @@ export class FileUploadView extends HTMLElement { disconnectedCallback() { document.removeEventListener('keydown', this.handleEscape); + if (this.gestureHandler) { + this.gestureHandler.destroy(); + } + } + + initGestures() { + const view = this.querySelector('.file-upload-view'); + if (!view) return; + + this.gestureHandler = new GestureHandler(view); + this.gestureHandler.on('swipeDown', () => { + if (isMobile()) { + this.close(); + } + }); } setFolder(folderId) { @@ -57,6 +74,8 @@ export class FileUploadView extends HTMLElement { backBtn.addEventListener('click', () => this.close()); } + this.initGestures(); + if (fileInput) { fileInput.addEventListener('change', (e) => { if (e.target.files.length > 0) { diff --git a/static/js/components/mywebdav-app.js b/static/js/components/mywebdav-app.js index a8c229e..8fee5e5 100644 --- a/static/js/components/mywebdav-app.js +++ b/static/js/components/mywebdav-app.js @@ -15,8 +15,9 @@ import './billing-dashboard.js'; import './admin-billing.js'; import './code-editor-view.js'; import './cookie-consent.js'; -import './user-settings.js'; // Import the new user settings component +import './user-settings.js'; import { shortcuts } from '../shortcuts.js'; +import { GestureHandler, isMobile } from '../gesture-handler.js'; const api = app.getAPI(); const logger = app.getLogger(); @@ -31,6 +32,8 @@ export class MyWebdavApp extends HTMLElement { this.boundHandlePopState = this.handlePopState.bind(this); this.popstateAttached = false; this.currentSearchId = 0; + this.gestureHandler = null; + this.sidebarOpen = false; } async connectedCallback() { @@ -127,10 +130,15 @@ export class MyWebdavApp extends HTMLElement {
+

MyWebdav

- +
@@ -139,7 +147,8 @@ export class MyWebdavApp extends HTMLElement {
-