From 522e1bfe2d827300decdf9b98fb3f0b7bf9bb81d Mon Sep 17 00:00:00 2001 From: Tomek Zebrowski Date: Sat, 31 Jan 2026 18:38:32 +0100 Subject: [PATCH] refactor: convert drive into class --- src/drive.js | 238 ++++++++++++++++++++++++--------------------------- 1 file changed, 114 insertions(+), 124 deletions(-) diff --git a/src/drive.js b/src/drive.js index 5c2aaec..644fe52 100644 --- a/src/drive.js +++ b/src/drive.js @@ -7,21 +7,94 @@ import { debounce } from './debounce.js'; /** * Drive Module - Handles Google Drive file interactions, filtering, and UI rendering. */ -export const Drive = { - activeLoadToken: 0, - PATH_CONFIG: { root: 'mygiulia', sub: 'trips' }, - - // Data Store - fileData: [], // Stores objects: { file, meta } - - _state: { - sortOrder: 'desc', - filters: { term: '', start: null, end: null }, - pagination: { - currentPage: 1, - itemsPerPage: 10, // Adjust this number to change page size - }, - }, +class DriveManager { + constructor() { + this.activeLoadToken = 0; + this.PATH_CONFIG = { root: 'mygiulia', sub: 'trips' }; + + // Data Store + this.fileData = []; // Stores objects: { file, meta, timestamp } + + this._state = { + sortOrder: 'desc', + filters: { term: '', start: null, end: null }, + pagination: { + currentPage: 1, + itemsPerPage: 10, + }, + }; + + // HTML Templates + this.TEMPLATES = { + searchInterface: () => ` +
+
+ + + +
+
+ + to + + +
+
+
+ +
+
+
Searching for logs...
+ `, + fileCard: (file, meta) => ` +
+
+
+
${file.name}
+
+
${meta?.date || 'N/A'}
+
${meta?.length || 'N/A'}s
+
${file.size ? (file.size / 1024).toFixed(0) : '?'} KB
+
+
+
+ `, + monthGroup: (monthYear) => ` +
+ ${monthYear} +
+
+ `, + recentSectionHeader: () => ` +
+ Recently Viewed + + Clear + +
+
+ `, + sortBtnContent: (order) => ` + + ${order === 'desc' ? 'Newest' : 'Oldest'} + `, + paginationControls: (current, total, start, end, totalItems) => ` +
+ + + ${start}-${end} of ${totalItems} + + +
+ `, + }; + } // --- Core Drive Operations --- @@ -47,7 +120,7 @@ export const Drive = { console.error(`Drive: Error locating folder "${name}":`, error); return null; } - }, + } async listFiles() { const listEl = DOM.get('driveList'); @@ -71,7 +144,7 @@ export const Drive = { } catch (error) { this.handleApiError(error, document.getElementById('driveFileContainer')); } - }, + } async fetchJsonFiles(folderId) { const listEl = document.getElementById('driveFileContainer'); @@ -85,10 +158,9 @@ export const Drive = { let hasMore = true; try { - // Loop to fetch ALL pages while (hasMore) { const res = await gapi.client.drive.files.list({ - pageSize: 100, // Fetch in larger chunks + pageSize: 100, fields: 'nextPageToken, files(id, name, size, modifiedTime)', q: `'${folderId}' in parents and name contains '.json' and trashed=false`, orderBy: 'modifiedTime desc', @@ -97,11 +169,10 @@ export const Drive = { const files = res.result.files || []; - // Process and store data immediately const processedFiles = files.map((f) => ({ file: f, meta: this.getFileMetadata(f.name), - timestamp: this.extractTimestamp(f.name), // Pre-calculate for sorting + timestamp: this.extractTimestamp(f.name), })); this.fileData = [...this.fileData, ...processedFiles]; @@ -110,7 +181,6 @@ export const Drive = { if (!pageToken) { hasMore = false; } else { - // Optional: Update UI with progress listEl.innerHTML = `
Loaded ${this.fileData.length} logs...
`; } } @@ -124,7 +194,7 @@ export const Drive = { } catch (error) { this.handleApiError(error, listEl); } - }, + } async loadFile(fileName, id, element) { document @@ -136,8 +206,6 @@ export const Drive = { recent = [id, ...recent.filter((i) => i !== id)].slice(0, 3); localStorage.setItem('recent_logs', JSON.stringify(recent)); - // Do NOT full refreshUI here, or we lose page position. - const currentToken = ++this.activeLoadToken; UI.setLoading(true, 'Fetching from Drive...', () => { this.activeLoadToken++; @@ -159,12 +227,11 @@ export const Drive = { } finally { if (currentToken === this.activeLoadToken) UI.setLoading(false); } - }, + } // --- Search & Filtering Logic --- initSearch() { - // Fixed typo here: was 'constinputs', now 'const inputs' const inputs = { text: document.getElementById('driveSearchInput'), clearText: document.getElementById('clearDriveSearchText'), @@ -173,7 +240,6 @@ export const Drive = { sortBtn: document.getElementById('driveSortToggle'), }; - // Helper to wire up events safely const safeAddEvent = (id, event, handler) => { const el = document.getElementById(id); if (el) el.addEventListener(event, handler); @@ -213,7 +279,6 @@ export const Drive = { ? 'block' : 'none'; - // Reset to page 1 on filter change this._state.pagination.currentPage = 1; if (immediate) this.refreshUI(); @@ -246,23 +311,25 @@ export const Drive = { }); this.refreshUI(); - }, + } refreshUI() { const container = document.getElementById('driveFileContainer'); if (!container) return; + // 1. Filter const filtered = this.fileData.filter((item) => this._applyFilters(item)); + // 2. Sort filtered.sort((a, b) => { const diff = a.timestamp - b.timestamp; return this._state.sortOrder === 'desc' ? -diff : diff; }); + // 3. Paginate const { currentPage, itemsPerPage } = this._state.pagination; const totalPages = Math.ceil(filtered.length / itemsPerPage); - // Ensure current page is valid if (currentPage > totalPages && totalPages > 0) this._state.pagination.currentPage = totalPages; if (currentPage < 1) this._state.pagination.currentPage = 1; @@ -270,9 +337,9 @@ export const Drive = { const startIdx = (this._state.pagination.currentPage - 1) * itemsPerPage; const paginatedItems = filtered.slice(startIdx, startIdx + itemsPerPage); + // 4. Render container.innerHTML = ''; - // Only show recent section on first page and if no filters are active const isFiltering = this._state.filters.term || this._state.filters.start || @@ -286,7 +353,7 @@ export const Drive = { const countEl = document.getElementById('driveResultCount'); if (countEl) countEl.innerText = `Found ${filtered.length} logs`; - }, + } _applyFilters(item) { const { term, start, end } = this._state.filters; @@ -298,7 +365,7 @@ export const Drive = { (!start || fileDate >= start) && (!end || fileDate <= end); return matchesText && matchesDate; - }, + } // --- Rendering Helpers --- @@ -314,9 +381,7 @@ export const Drive = { items.forEach((item) => { const dateObj = new Date(item.timestamp); - // Fallback if timestamp is invalid const validDate = isNaN(dateObj.getTime()) ? new Date() : dateObj; - const monthYear = validDate.toLocaleString('en-US', { month: 'long', year: 'numeric', @@ -328,21 +393,18 @@ export const Drive = { lastMonth = monthYear; } - const cardHtml = this.TEMPLATES.fileCard(item.file, item.meta); - // We need to convert string to DOM element to append const tempDiv = document.createElement('div'); - tempDiv.innerHTML = cardHtml; + tempDiv.innerHTML = this.TEMPLATES.fileCard(item.file, item.meta); const cardEl = tempDiv.firstElementChild; currentGroup.querySelector('.month-list').appendChild(cardEl); }); - }, + } renderRecentSection(container) { const recentIds = JSON.parse(localStorage.getItem('recent_logs') || '[]'); if (recentIds.length === 0) return; - // Find full file objects for recent IDs const recentItems = recentIds .map((id) => this.fileData.find((f) => f.file.id === id)) .filter((item) => item !== undefined); @@ -352,7 +414,7 @@ export const Drive = { const section = document.createElement('div'); section.className = 'recent-section'; section.innerHTML = this.TEMPLATES.recentSectionHeader(); - const list = section.querySelector('.recent-list-container') || section; // Fallback + const list = section.querySelector('.recent-list-container') || section; recentItems.forEach((item) => { const tempDiv = document.createElement('div'); @@ -371,7 +433,7 @@ export const Drive = { e.stopPropagation(); this.clearRecentHistory(); }); - }, + } renderPaginationControls(container, totalItems, totalPages) { if (totalItems === 0) return; @@ -390,7 +452,6 @@ export const Drive = { ); container.appendChild(navDiv); - // Bind Events navDiv.querySelector('#prevPageBtn')?.addEventListener('click', () => { if (currentPage > 1) { this._state.pagination.currentPage--; @@ -404,7 +465,7 @@ export const Drive = { this.refreshUI(); } }); - }, + } createMonthGroup(monthYear) { const group = document.createElement('div'); @@ -421,7 +482,7 @@ export const Drive = { : 'fas fa-chevron-right toggle-icon'; }; return group; - }, + } // --- Utilities & Metadata --- @@ -430,12 +491,12 @@ export const Drive = { if (!match) return { date: 'Unknown', length: '?' }; const date = new Date(parseInt(match[1])); return { date: date.toISOString(), length: match[2] }; - }, + } extractTimestamp(fileName) { const match = fileName.match(/-(\d+)-(\d+)\.json$/); return match ? parseInt(match[1]) : 0; - }, + } handleApiError(error, listEl) { if (error.status === 401 || error.status === 403) @@ -445,90 +506,19 @@ export const Drive = { error.result?.error?.message || error.message || 'Unknown error'; listEl.innerHTML = `
Drive error: ${error.status === 401 ? 'Session expired' : msg}
`; } - }, + } _renderError(msg) { const container = document.getElementById('driveFileContainer'); if (container) container.innerHTML = `
${msg}
`; - }, + } clearRecentHistory() { if (confirm('Clear recently viewed history?')) { localStorage.removeItem('recent_logs'); - // No need to fetch again, just refresh UI this.refreshUI(); } - }, - - // --- HTML Templates --- + } +} - TEMPLATES: { - searchInterface: () => ` -
-
- - - -
-
- - to - - -
-
-
- -
-
-
Searching for logs...
- `, - fileCard: (file, meta) => ` -
-
-
-
${file.name}
-
-
${meta?.date || 'N/A'}
-
${meta?.length || 'N/A'}s
-
${file.size ? (file.size / 1024).toFixed(0) : '?'} KB
-
-
-
- `, - monthGroup: (monthYear) => ` -
- ${monthYear} -
-
- `, - recentSectionHeader: () => ` -
- Recently Viewed - - Clear - -
-
- `, - sortBtnContent: (order) => ` - - ${order === 'desc' ? 'Newest' : 'Oldest'} - `, - paginationControls: (current, total, start, end, totalItems) => ` -
- - - ${start}-${end} of ${totalItems} - - -
- `, - }, -}; +export const Drive = new DriveManager();