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
+
+
@@ -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 {