Merge pull request 'Improved UI' (#1) from ui-tweaks-1 into main

Reviewed-on: #1
This commit is contained in:
2025-07-20 01:08:32 +00:00
5 changed files with 96 additions and 38 deletions

View File

@@ -10,6 +10,7 @@ A web application for sorting and organizing images using swipe gestures, simila
- **NSFW Filtering**: Toggle to include/exclude NSFW images on both the main swiper and history pages
- **NSFW Blur**: Optional blur for NSFW thumbnails on the history page with a toolbar toggle
- **Orientation & Action Filters**: Filter results by orientation (portrait/landscape/square) and by action taken
- **Image Sorting**: Choose to display images in random order (default), oldest first, or newest first
- **Database Storage**: All selections are saved in a SQLite database
- **Reset Functionality**: Option to clear all selections and start fresh
@@ -29,6 +30,10 @@ A web application for sorting and organizing images using swipe gestures, simila
1. Run the server: `python server.py`
2. Open a web browser and navigate to `http://localhost:8000`
### Main Page Features
- **Sort Order**: Use the dropdown in the sidebar to select image display order (Random, Oldest to Newest, or Newest to Oldest)
- **Mobile View**: On smaller screens, action buttons appear directly below the swipe window for easy access
3. Use the on-screen buttons or swipe gestures to categorize images:
- Left: Discard
- Right: Keep

View File

@@ -157,6 +157,7 @@ class ImageSwipeHandler(BaseHTTPRequestHandler):
search_keywords = [kw.strip() for kw in search_keywords_str.split(',') if kw.strip()]
actions_str = query_params.get("actions", ["Unactioned"])[0]
actions = [a.strip() for a in actions_str.split(',') if a.strip()]
sort_order = query_params.get("sort", ["random"])[0] # Get sort order parameter
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row # Important to access columns by name
@@ -209,6 +210,12 @@ class ImageSwipeHandler(BaseHTTPRequestHandler):
if where_clauses:
query += " WHERE " + " AND ".join(where_clauses)
# Add sorting based on sort_order
if sort_order == "oldest":
query += " ORDER BY meta.creation_date ASC"
elif sort_order == "newest":
query += " ORDER BY meta.creation_date DESC"
cur.execute(query, params)
rows = cur.fetchall()
conn.close()
@@ -217,7 +224,12 @@ class ImageSwipeHandler(BaseHTTPRequestHandler):
self._json_response({"message": "No more images available for this filter."})
return
row = random.choice(rows)
# For random order, select a random row from the results
if sort_order == "random":
row = random.choice(rows)
else:
# For oldest/newest, use the first row in the sorted results
row = rows[0]
# Convert row to a dictionary for easier access
row_dict = dict(row)

View File

@@ -36,9 +36,18 @@
<div id="keyword-pills-container" class="keyword-pills-container"></div>
</div>
<div class="filter-controls card">
<div class="filter-buttons orientation-filters">
<div class="sort-controls card">
<label for="sort-order">Sort Order:</label>
<select id="sort-order">
<option value="random">Random (default)</option>
<option value="oldest">Oldest to Newest</option>
<option value="newest">Newest to Oldest</option>
</select>
</div>
<div class="filter-controls card">
<div class="filter-buttons orientation-filters">
<button class="filter-btn active" data-orientation="all"><img src="static/icons/all.svg" alt="All" class="orientation"></button>
<button class="filter-btn" data-orientation="portrait"><img src="static/icons/portrait.svg" alt="Portrait" class="orientation"></button>
<button class="filter-btn" data-orientation="landscape"><img src="static/icons/landscape.svg" alt="Landscape" class="orientation"></button>
@@ -68,11 +77,11 @@
<button id="btn-down" class="action-btn" aria-label="Review">
<img src="static/icons/review.svg" alt="Review" class="action">
</button>
</div>
<!-- History button -->
<a href="/history.html" id="btn-history" class="action-btn" aria-label="History">
<img src="static/icons/history.svg" alt="History" class="action">
</a>
</div>
<div class="status-area" aria-live="polite">
<p id="image-resolution">Resolution: Loading...</p>

View File

@@ -8,6 +8,7 @@ document.addEventListener('DOMContentLoaded', () => {
previousOrientation: ['all'],
allowNsfw: false,
searchKeywords: [],
sortOrder: 'random', // Added sortOrder to state
isLoading: false,
isDragging: false,
startX: 0,
@@ -84,13 +85,16 @@ document.addEventListener('DOMContentLoaded', () => {
// NSFW param
params.append('allow_nsfw', state.allowNsfw ? '1' : '0');
if (state.searchKeywords.length > 0) {
params.append('search', state.searchKeywords.join(','));
}
if (state.searchKeywords.length > 0) {
params.append('search', state.searchKeywords.join(','));
}
if (state.currentActions.length > 0) {
params.append('actions', state.currentActions.join(','));
}
if (state.currentActions.length > 0) {
params.append('actions', state.currentActions.join(','));
}
// Add sort order parameter
params.append('sort', state.sortOrder);
fetch(`/random-image?${params.toString()}`)
.then(response => response.json())
@@ -400,5 +404,11 @@ document.addEventListener('DOMContentLoaded', () => {
loadNewImage();
});
// Add event listener for sort order change
document.getElementById('sort-order').addEventListener('change', (e) => {
state.sortOrder = e.target.value;
loadNewImage();
});
loadNewImage(); // Always load an image on startup
});

View File

@@ -809,33 +809,55 @@ html, body {
content: "↓";
}
}
/* ---------------- MOBILE VIEW TWEAKS ---------------- */
@media (max-width: 767px) {
/* tighter spacing between panels */
.side-panel {
gap: 10px;
}
.filter-controls,
.action-buttons,
.status-area {
padding: 10px;
}
/* combined action buttons */
.action-buttons {
flex-direction: row;
gap: 0;
}
.action-buttons .action-btn {
flex: 1;
border-radius: 0;
padding: 16px 0;
}
/* minimal spacing between blocks */
.side-panel > * + * {
margin-top: 4px;
border-top: 1px solid #ccc;
}
}
/* ---------------- MOBILE VIEW TWEAKS ---------------- */
@media (max-width: 991px) {
/* tighter spacing between panels */
.side-panel {
gap: 10px;
}
.filter-controls,
.action-buttons,
.status-area {
padding: 10px;
}
/* combined action buttons */
.action-buttons {
flex-direction: row;
gap: 0;
order: 1; /* First element in side panel */
flex-wrap: wrap;
}
.action-buttons .action-btn {
flex: 1;
min-width: 25%;
border-radius: 0;
padding: 16px 0;
}
/* Ensure all other elements come after action buttons */
.search-controls,
.sort-controls,
.filter-controls,
.status-area,
#btn-history {
order: 2;
}
/* minimal spacing between blocks */
.side-panel > * + * {
margin-top: 4px;
border-top: 1px solid #ccc;
}
/* Move action buttons below swipe window */
.main-section {
flex-direction: column;
}
.swipe-container {
order: 1;
}
.side-panel {
order: 2;
}
}
/* ---------------- DESKTOP 2x2 ACTION GRID ---------------- */
@media (min-width: 992px) {
.action-buttons {