move sorting to the database
All checks were successful
pedestrian-simulator / build (push) Successful in 1m2s

This commit is contained in:
2026-01-11 21:40:48 -07:00
parent 89816a5a8c
commit eaea2c4edb
3 changed files with 150 additions and 46 deletions

View File

@@ -25,6 +25,10 @@ let viewMode = 0; // 0: Street View, 1: Map View, 2: Satellite View
let targetSyncTime = null; let targetSyncTime = null;
let idleAnimationId = null; let idleAnimationId = null;
// Pagination state
let currentMyPage = 1;
let currentPublicPage = 1;
const KML_PAGE_LIMIT = 10;
let apiKey = null; let apiKey = null;
window.isGeneratingRoute = false; window.isGeneratingRoute = false;
@@ -156,8 +160,38 @@ function setupKMLBrowser() {
document.getElementById('kmlUploadInput').addEventListener('change', handleKMLUpload); document.getElementById('kmlUploadInput').addEventListener('change', handleKMLUpload);
// Sort controls // Sort controls
document.getElementById('myFilesSortSelect').addEventListener('change', () => loadKMLFiles()); document.getElementById('myFilesSortSelect').addEventListener('change', () => {
document.getElementById('publicFilesSortSelect').addEventListener('change', () => loadKMLFiles()); 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() { function openKMLBrowser() {
@@ -205,7 +239,19 @@ async function handleKMLUpload(e) {
async function loadKMLFiles() { async function loadKMLFiles() {
try { 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) { if (!response.ok) {
console.error('Failed to load KML files'); console.error('Failed to load KML files');
return; return;
@@ -216,8 +262,19 @@ async function loadKMLFiles() {
// Render my files // Render my files
renderKMLFiles('myFilesList', data.my_files || [], true); 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 // Render public files
renderKMLFiles('publicFilesList', data.public_files || [], false); 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) { } catch (error) {
console.error('Error loading KML files:', error); console.error('Error loading KML files:', error);
} }

View File

@@ -153,14 +153,21 @@
<div class="sort-controls"> <div class="sort-controls">
<label>Sort by:</label> <label>Sort by:</label>
<select id="myFilesSortSelect"> <select id="myFilesSortSelect">
<option value="date">Newest First</option> <option value="date">Most Recent</option>
<option value="distance">Distance</option> <option value="distance">Distance</option>
<option value="filename">Filename</option>
</select> </select>
</div> </div>
<div id="myFilesList" class="kml-file-list"> <div id="myFilesList" class="kml-file-list">
<p class="empty-message">No KML files uploaded yet</p> <p class="empty-message">No KML files uploaded yet</p>
</div> </div>
<div class="pagination-controls">
<button id="prevMyPage" class="pagination-btn" disabled>&larr; Previous</button>
<span id="myPageNum">Page 1</span>
<button id="nextMyPage" class="pagination-btn">Next &rarr;</button>
</div>
</div> </div>
<!-- Public Files Tab --> <!-- Public Files Tab -->
@@ -170,6 +177,7 @@
<select id="publicFilesSortSelect"> <select id="publicFilesSortSelect">
<option value="votes">Highest Votes</option> <option value="votes">Highest Votes</option>
<option value="date">Most Recent</option> <option value="date">Most Recent</option>
<option value="distance">Distance</option>
</select> </select>
</div> </div>

View File

@@ -222,55 +222,43 @@ func HandleKMLList(w http.ResponseWriter, r *http.Request) {
return return
} }
// Parse pagination parameters // Parse parameters for My Files
limit, _ := strconv.Atoi(r.URL.Query().Get("limit")) myLimit, _ := strconv.Atoi(r.URL.Query().Get("my_limit"))
if limit <= 0 { if myLimit <= 0 {
limit = 10 myLimit = 10
} }
page, _ := strconv.Atoi(r.URL.Query().Get("page")) myPage, _ := strconv.Atoi(r.URL.Query().Get("my_page"))
if page <= 0 { if myPage <= 0 {
page = 1 myPage = 1
} }
offset := (page - 1) * limit mySortBy := r.URL.Query().Get("my_sort_by")
if mySortBy == "" {
sortBy := r.URL.Query().Get("sort_by") mySortBy = "date"
order := r.URL.Query().Get("order")
if order != "ASC" {
order = "DESC"
} }
// 1. Get my files // Parse parameters for Public Files
myFiles, err := queryKMLMetadata(` publicLimit, _ := strconv.Atoi(r.URL.Query().Get("public_limit"))
SELECT m.filename, m.user_id, u.display_name, m.distance, m.is_public, m.uploaded_at, if publicLimit <= 0 {
(SELECT COALESCE(SUM(v.vote_value), 0) FROM kml_votes v WHERE v.kml_id = m.id) as votes publicLimit = 10
FROM kml_metadata m }
JOIN users u ON m.user_id = u.fitbit_user_id publicPage, _ := strconv.Atoi(r.URL.Query().Get("public_page"))
WHERE m.user_id = ? if publicPage <= 0 {
ORDER BY m.uploaded_at DESC publicPage = 1
`, userID) }
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 { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
// 2. Get public files (with pagination and sorting) // 2. Get Public Files
sortClause := "votes" publicFiles, publicTotal, err := fetchKMLList(userID, false, publicPage, publicLimit, publicSortBy)
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)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@@ -279,12 +267,63 @@ func HandleKMLList(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{ json.NewEncoder(w).Encode(map[string]interface{}{
"my_files": myFiles, "my_files": myFiles,
"my_total": myTotal,
"my_page": myPage,
"public_files": publicFiles, "public_files": publicFiles,
"page": page, "public_total": publicTotal,
"limit": limit, "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) { func queryKMLMetadata(query string, args ...interface{}) ([]KMLMetadata, error) {
rows, err := db.Query(query, args...) rows, err := db.Query(query, args...)
if err != nil { if err != nil {