From eaea2c4edb438906981577351427b15b0681cb75 Mon Sep 17 00:00:00 2001 From: Steven Polley Date: Sun, 11 Jan 2026 21:40:48 -0700 Subject: [PATCH] move sorting to the database --- frontend/app.js | 63 +++++++++++++++++++++-- frontend/index.html | 10 +++- server/kml.go | 123 +++++++++++++++++++++++++++++--------------- 3 files changed, 150 insertions(+), 46 deletions(-) diff --git a/frontend/app.js b/frontend/app.js index f112b55..d1a71b9 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -25,6 +25,10 @@ let viewMode = 0; // 0: Street View, 1: Map View, 2: Satellite View let targetSyncTime = null; let idleAnimationId = null; +// Pagination state +let currentMyPage = 1; +let currentPublicPage = 1; +const KML_PAGE_LIMIT = 10; let apiKey = null; window.isGeneratingRoute = false; @@ -156,8 +160,38 @@ function setupKMLBrowser() { document.getElementById('kmlUploadInput').addEventListener('change', handleKMLUpload); // Sort controls - document.getElementById('myFilesSortSelect').addEventListener('change', () => loadKMLFiles()); - document.getElementById('publicFilesSortSelect').addEventListener('change', () => loadKMLFiles()); + document.getElementById('myFilesSortSelect').addEventListener('change', () => { + currentMyPage = 1; + loadKMLFiles(); + }); + document.getElementById('publicFilesSortSelect').addEventListener('change', () => { + currentPublicPage = 1; + loadKMLFiles(); + }); + + // Pagination buttons (My Files) + document.getElementById('prevMyPage').addEventListener('click', () => { + if (currentMyPage > 1) { + currentMyPage--; + loadKMLFiles(); + } + }); + document.getElementById('nextMyPage').addEventListener('click', () => { + currentMyPage++; + loadKMLFiles(); + }); + + // Pagination buttons (Public Files) + document.getElementById('prevPublicPage').addEventListener('click', () => { + if (currentPublicPage > 1) { + currentPublicPage--; + loadKMLFiles(); + } + }); + document.getElementById('nextPublicPage').addEventListener('click', () => { + currentPublicPage++; + loadKMLFiles(); + }); } function openKMLBrowser() { @@ -205,7 +239,19 @@ async function handleKMLUpload(e) { async function loadKMLFiles() { try { - const response = await fetch('/api/kml/list'); + const mySort = document.getElementById('myFilesSortSelect').value; + const publicSort = document.getElementById('publicFilesSortSelect').value; + + const params = new URLSearchParams({ + my_page: currentMyPage, + my_limit: KML_PAGE_LIMIT, + my_sort_by: mySort, + public_page: currentPublicPage, + public_limit: KML_PAGE_LIMIT, + public_sort_by: publicSort + }); + + const response = await fetch(`/api/kml/list?${params.toString()}`); if (!response.ok) { console.error('Failed to load KML files'); return; @@ -216,8 +262,19 @@ async function loadKMLFiles() { // Render my files renderKMLFiles('myFilesList', data.my_files || [], true); + // Update my pagination UI + document.getElementById('myPageNum').textContent = `Page ${data.my_page}`; + document.getElementById('prevMyPage').disabled = data.my_page <= 1; + document.getElementById('nextMyPage').disabled = (data.my_page * KML_PAGE_LIMIT) >= data.my_total; + // Render public files renderKMLFiles('publicFilesList', data.public_files || [], false); + + // Update public pagination UI + document.getElementById('publicPageNum').textContent = `Page ${data.public_page}`; + document.getElementById('prevPublicPage').disabled = data.public_page <= 1; + document.getElementById('nextPublicPage').disabled = (data.public_page * KML_PAGE_LIMIT) >= data.public_total; + } catch (error) { console.error('Error loading KML files:', error); } diff --git a/frontend/index.html b/frontend/index.html index 23571fc..bd7a51a 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -153,14 +153,21 @@

No KML files uploaded yet

+ +
+ + Page 1 + +
@@ -170,6 +177,7 @@ diff --git a/server/kml.go b/server/kml.go index 7661c3f..5aa3e63 100644 --- a/server/kml.go +++ b/server/kml.go @@ -222,55 +222,43 @@ func HandleKMLList(w http.ResponseWriter, r *http.Request) { return } - // Parse pagination parameters - limit, _ := strconv.Atoi(r.URL.Query().Get("limit")) - if limit <= 0 { - limit = 10 + // Parse parameters for My Files + myLimit, _ := strconv.Atoi(r.URL.Query().Get("my_limit")) + if myLimit <= 0 { + myLimit = 10 } - page, _ := strconv.Atoi(r.URL.Query().Get("page")) - if page <= 0 { - page = 1 + myPage, _ := strconv.Atoi(r.URL.Query().Get("my_page")) + if myPage <= 0 { + myPage = 1 } - offset := (page - 1) * limit - - sortBy := r.URL.Query().Get("sort_by") - order := r.URL.Query().Get("order") - if order != "ASC" { - order = "DESC" + mySortBy := r.URL.Query().Get("my_sort_by") + if mySortBy == "" { + mySortBy = "date" } - // 1. Get my files - myFiles, err := queryKMLMetadata(` - SELECT m.filename, m.user_id, u.display_name, m.distance, m.is_public, m.uploaded_at, - (SELECT COALESCE(SUM(v.vote_value), 0) FROM kml_votes v WHERE v.kml_id = m.id) as votes - FROM kml_metadata m - JOIN users u ON m.user_id = u.fitbit_user_id - WHERE m.user_id = ? - ORDER BY m.uploaded_at DESC - `, userID) + // Parse parameters for Public Files + publicLimit, _ := strconv.Atoi(r.URL.Query().Get("public_limit")) + if publicLimit <= 0 { + publicLimit = 10 + } + publicPage, _ := strconv.Atoi(r.URL.Query().Get("public_page")) + if publicPage <= 0 { + publicPage = 1 + } + publicSortBy := r.URL.Query().Get("public_sort_by") + if publicSortBy == "" { + publicSortBy = "votes" + } + + // 1. Get My Files + myFiles, myTotal, err := fetchKMLList(userID, true, myPage, myLimit, mySortBy) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } - // 2. Get public files (with pagination and sorting) - sortClause := "votes" - switch sortBy { - case "date": - sortClause = "m.uploaded_at" - case "distance": - sortClause = "m.distance" - } - - publicFiles, err := queryKMLMetadata(fmt.Sprintf(` - SELECT m.filename, m.user_id, u.display_name, m.distance, m.is_public, m.uploaded_at, - (SELECT COALESCE(SUM(v.vote_value), 0) FROM kml_votes v WHERE v.kml_id = m.id) as votes - FROM kml_metadata m - JOIN users u ON m.user_id = u.fitbit_user_id - WHERE m.is_public = 1 - ORDER BY %s %s - LIMIT ? OFFSET ? - `, sortClause, order), limit, offset) + // 2. Get Public Files + publicFiles, publicTotal, err := fetchKMLList(userID, false, publicPage, publicLimit, publicSortBy) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -279,12 +267,63 @@ func HandleKMLList(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "my_files": myFiles, + "my_total": myTotal, + "my_page": myPage, "public_files": publicFiles, - "page": page, - "limit": limit, + "public_total": publicTotal, + "public_page": publicPage, + "limit": 10, // Default limit reference for UI }) } +func fetchKMLList(userID string, mineOnly bool, page, limit int, sortBy string) ([]KMLMetadata, int, error) { + offset := (page - 1) * limit + + var whereClause string + var args []interface{} + if mineOnly { + whereClause = "WHERE m.user_id = ?" + args = append(args, userID) + } else { + whereClause = "WHERE m.is_public = 1" + } + + // Get total count first + var total int + err := db.QueryRow(fmt.Sprintf("SELECT COUNT(*) FROM kml_metadata m %s", whereClause), args...).Scan(&total) + if err != nil { + return nil, 0, err + } + + sortClause := "votes" + order := "DESC" + switch sortBy { + case "date": + sortClause = "m.uploaded_at" + case "distance": + sortClause = "m.distance" + case "votes": + sortClause = "votes" + case "filename": + sortClause = "m.filename" + order = "ASC" + } + + query := fmt.Sprintf(` + SELECT m.filename, m.user_id, u.display_name, m.distance, m.is_public, m.uploaded_at, + (SELECT COALESCE(SUM(v.vote_value), 0) FROM kml_votes v WHERE v.kml_id = m.id) as votes + FROM kml_metadata m + JOIN users u ON m.user_id = u.fitbit_user_id + %s + ORDER BY %s %s + LIMIT ? OFFSET ? + `, whereClause, sortClause, order) + + args = append(args, limit, offset) + files, err := queryKMLMetadata(query, args...) + return files, total, err +} + func queryKMLMetadata(query string, args ...interface{}) ([]KMLMetadata, error) { rows, err := db.Query(query, args...) if err != nil {