Improved UI
This commit is contained in:
@@ -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 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
|
- **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
|
- **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
|
- **Database Storage**: All selections are saved in a SQLite database
|
||||||
- **Reset Functionality**: Option to clear all selections and start fresh
|
- **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`
|
1. Run the server: `python server.py`
|
||||||
2. Open a web browser and navigate to `http://localhost:8000`
|
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:
|
3. Use the on-screen buttons or swipe gestures to categorize images:
|
||||||
- Left: Discard
|
- Left: Discard
|
||||||
- Right: Keep
|
- Right: Keep
|
||||||
|
|||||||
14
handler.py
14
handler.py
@@ -157,6 +157,7 @@ class ImageSwipeHandler(BaseHTTPRequestHandler):
|
|||||||
search_keywords = [kw.strip() for kw in search_keywords_str.split(',') if kw.strip()]
|
search_keywords = [kw.strip() for kw in search_keywords_str.split(',') if kw.strip()]
|
||||||
actions_str = query_params.get("actions", ["Unactioned"])[0]
|
actions_str = query_params.get("actions", ["Unactioned"])[0]
|
||||||
actions = [a.strip() for a in actions_str.split(',') if a.strip()]
|
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 = sqlite3.connect(DB_PATH)
|
||||||
conn.row_factory = sqlite3.Row # Important to access columns by name
|
conn.row_factory = sqlite3.Row # Important to access columns by name
|
||||||
@@ -209,6 +210,12 @@ class ImageSwipeHandler(BaseHTTPRequestHandler):
|
|||||||
if where_clauses:
|
if where_clauses:
|
||||||
query += " WHERE " + " AND ".join(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)
|
cur.execute(query, params)
|
||||||
rows = cur.fetchall()
|
rows = cur.fetchall()
|
||||||
conn.close()
|
conn.close()
|
||||||
@@ -217,7 +224,12 @@ class ImageSwipeHandler(BaseHTTPRequestHandler):
|
|||||||
self._json_response({"message": "No more images available for this filter."})
|
self._json_response({"message": "No more images available for this filter."})
|
||||||
return
|
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
|
# Convert row to a dictionary for easier access
|
||||||
row_dict = dict(row)
|
row_dict = dict(row)
|
||||||
|
|
||||||
|
|||||||
17
index.html
17
index.html
@@ -36,9 +36,18 @@
|
|||||||
<div id="keyword-pills-container" class="keyword-pills-container"></div>
|
<div id="keyword-pills-container" class="keyword-pills-container"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="filter-controls card">
|
<div class="sort-controls card">
|
||||||
|
<label for="sort-order">Sort Order:</label>
|
||||||
<div class="filter-buttons orientation-filters">
|
<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 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="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>
|
<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">
|
<button id="btn-down" class="action-btn" aria-label="Review">
|
||||||
<img src="static/icons/review.svg" alt="Review" class="action">
|
<img src="static/icons/review.svg" alt="Review" class="action">
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
<!-- History button -->
|
<!-- History button -->
|
||||||
<a href="/history.html" id="btn-history" class="action-btn" aria-label="History">
|
<a href="/history.html" id="btn-history" class="action-btn" aria-label="History">
|
||||||
<img src="static/icons/history.svg" alt="History" class="action">
|
<img src="static/icons/history.svg" alt="History" class="action">
|
||||||
</a>
|
</a>
|
||||||
|
</div>
|
||||||
<div class="status-area" aria-live="polite">
|
<div class="status-area" aria-live="polite">
|
||||||
|
|
||||||
<p id="image-resolution">Resolution: Loading...</p>
|
<p id="image-resolution">Resolution: Loading...</p>
|
||||||
|
|||||||
22
js/main.js
22
js/main.js
@@ -8,6 +8,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
previousOrientation: ['all'],
|
previousOrientation: ['all'],
|
||||||
allowNsfw: false,
|
allowNsfw: false,
|
||||||
searchKeywords: [],
|
searchKeywords: [],
|
||||||
|
sortOrder: 'random', // Added sortOrder to state
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
isDragging: false,
|
isDragging: false,
|
||||||
startX: 0,
|
startX: 0,
|
||||||
@@ -84,13 +85,16 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
// NSFW param
|
// NSFW param
|
||||||
params.append('allow_nsfw', state.allowNsfw ? '1' : '0');
|
params.append('allow_nsfw', state.allowNsfw ? '1' : '0');
|
||||||
|
|
||||||
if (state.searchKeywords.length > 0) {
|
if (state.searchKeywords.length > 0) {
|
||||||
params.append('search', state.searchKeywords.join(','));
|
params.append('search', state.searchKeywords.join(','));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.currentActions.length > 0) {
|
if (state.currentActions.length > 0) {
|
||||||
params.append('actions', state.currentActions.join(','));
|
params.append('actions', state.currentActions.join(','));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add sort order parameter
|
||||||
|
params.append('sort', state.sortOrder);
|
||||||
|
|
||||||
fetch(`/random-image?${params.toString()}`)
|
fetch(`/random-image?${params.toString()}`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
@@ -400,5 +404,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
loadNewImage();
|
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
|
loadNewImage(); // Always load an image on startup
|
||||||
});
|
});
|
||||||
|
|||||||
76
styles.css
76
styles.css
@@ -809,33 +809,55 @@ html, body {
|
|||||||
content: "↓";
|
content: "↓";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* ---------------- MOBILE VIEW TWEAKS ---------------- */
|
/* ---------------- MOBILE VIEW TWEAKS ---------------- */
|
||||||
@media (max-width: 767px) {
|
@media (max-width: 991px) {
|
||||||
/* tighter spacing between panels */
|
/* tighter spacing between panels */
|
||||||
.side-panel {
|
.side-panel {
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
.filter-controls,
|
.filter-controls,
|
||||||
.action-buttons,
|
.action-buttons,
|
||||||
.status-area {
|
.status-area {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
/* combined action buttons */
|
/* combined action buttons */
|
||||||
.action-buttons {
|
.action-buttons {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 0;
|
gap: 0;
|
||||||
}
|
order: 1; /* First element in side panel */
|
||||||
.action-buttons .action-btn {
|
flex-wrap: wrap;
|
||||||
flex: 1;
|
}
|
||||||
border-radius: 0;
|
.action-buttons .action-btn {
|
||||||
padding: 16px 0;
|
flex: 1;
|
||||||
}
|
min-width: 25%;
|
||||||
/* minimal spacing between blocks */
|
border-radius: 0;
|
||||||
.side-panel > * + * {
|
padding: 16px 0;
|
||||||
margin-top: 4px;
|
}
|
||||||
border-top: 1px solid #ccc;
|
/* 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 ---------------- */
|
/* ---------------- DESKTOP 2x2 ACTION GRID ---------------- */
|
||||||
@media (min-width: 992px) {
|
@media (min-width: 992px) {
|
||||||
.action-buttons {
|
.action-buttons {
|
||||||
|
|||||||
Reference in New Issue
Block a user