Add recording sorting with multiple options
Added comprehensive sorting functionality for recordings: Backend changes: - Updated get_recordings() to accept sort_by and sort_order parameters - Sort options: 'date', 'name', 'duration', 'size' - Sort order: 'asc' (ascending) or 'desc' (descending) - Added timestamp field to recordings for accurate date sorting - Default sort: by date, descending (newest first) Frontend changes (template v1.8.0): - Added sort controls above recordings list - Two dropdowns: sort field and sort direction - Visual styling with emojis for each option: 📅 Date - Sort by recording date/time 📝 Name - Sort alphabetically by filename ⏱️ Duration - Sort by recording length 💾 Size - Sort by file size ⬇️ Descending / ⬆️ Ascending - updateSort() JavaScript function reloads page with params - Preserves selected sort options via query params URL parameters: - ?sort=date&order=desc (default) - ?sort=name&order=asc (alphabetical A-Z) - ?sort=duration&order=desc (longest first) - ?sort=size&order=asc (smallest first) This makes it easy to find specific recordings or organize them by different criteria depending on what you need. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -151,7 +151,7 @@ BACKUP_ON_WRITE = BACKUP_CONFIG.get('backup_on_write', True)
|
|||||||
WEB_PORT = SYS_CONFIG['web']['port']
|
WEB_PORT = SYS_CONFIG['web']['port']
|
||||||
|
|
||||||
# Template version - increment this when HTML template changes
|
# Template version - increment this when HTML template changes
|
||||||
TEMPLATE_VERSION = "1.7.0" # Updated: Separate volume controls for greeting, button, and beep
|
TEMPLATE_VERSION = "1.8.0" # Updated: Added recording sorting by date, name, duration, size
|
||||||
|
|
||||||
# Flask app
|
# Flask app
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
@@ -834,7 +834,11 @@ phone = RotaryPhone()
|
|||||||
@app.route('/')
|
@app.route('/')
|
||||||
def index():
|
def index():
|
||||||
"""Main page"""
|
"""Main page"""
|
||||||
recordings = get_recordings()
|
# Get sort parameters from query string
|
||||||
|
sort_by = request.args.get('sort', 'date')
|
||||||
|
sort_order = request.args.get('order', 'desc')
|
||||||
|
|
||||||
|
recordings = get_recordings(sort_by=sort_by, sort_order=sort_order)
|
||||||
greetings = get_greetings()
|
greetings = get_greetings()
|
||||||
status = phone.get_status()
|
status = phone.get_status()
|
||||||
active_greeting = phone.config.get("active_greeting", "dialtone.wav")
|
active_greeting = phone.config.get("active_greeting", "dialtone.wav")
|
||||||
@@ -860,7 +864,9 @@ def index():
|
|||||||
volume_greeting=volume_greeting,
|
volume_greeting=volume_greeting,
|
||||||
volume_button=volume_button,
|
volume_button=volume_button,
|
||||||
volume_beep=volume_beep,
|
volume_beep=volume_beep,
|
||||||
greeting_delay=greeting_delay)
|
greeting_delay=greeting_delay,
|
||||||
|
sort_by=sort_by,
|
||||||
|
sort_order=sort_order)
|
||||||
|
|
||||||
@app.route('/api/status')
|
@app.route('/api/status')
|
||||||
def api_status():
|
def api_status():
|
||||||
@@ -1221,15 +1227,20 @@ def get_greetings():
|
|||||||
})
|
})
|
||||||
return greetings
|
return greetings
|
||||||
|
|
||||||
def get_recordings():
|
def get_recordings(sort_by='date', sort_order='desc'):
|
||||||
"""Get list of all recordings with metadata"""
|
"""Get list of all recordings with metadata
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sort_by: Sort field - 'date', 'name', 'duration', or 'size'
|
||||||
|
sort_order: Sort order - 'asc' or 'desc'
|
||||||
|
"""
|
||||||
recordings = []
|
recordings = []
|
||||||
if os.path.exists(OUTPUT_DIR):
|
if os.path.exists(OUTPUT_DIR):
|
||||||
for filename in sorted(os.listdir(OUTPUT_DIR), reverse=True):
|
for filename in os.listdir(OUTPUT_DIR):
|
||||||
if filename.endswith('.wav'):
|
if filename.endswith('.wav'):
|
||||||
filepath = os.path.join(OUTPUT_DIR, filename)
|
filepath = os.path.join(OUTPUT_DIR, filename)
|
||||||
stat = os.stat(filepath)
|
stat = os.stat(filepath)
|
||||||
|
|
||||||
# Get duration from WAV file
|
# Get duration from WAV file
|
||||||
try:
|
try:
|
||||||
wf = wave.open(filepath, 'rb')
|
wf = wave.open(filepath, 'rb')
|
||||||
@@ -1239,14 +1250,28 @@ def get_recordings():
|
|||||||
wf.close()
|
wf.close()
|
||||||
except:
|
except:
|
||||||
duration = 0
|
duration = 0
|
||||||
|
|
||||||
recordings.append({
|
recordings.append({
|
||||||
"filename": filename,
|
"filename": filename,
|
||||||
"size": stat.st_size,
|
"size": stat.st_size,
|
||||||
"size_mb": stat.st_size / (1024 * 1024),
|
"size_mb": stat.st_size / (1024 * 1024),
|
||||||
"date": datetime.fromtimestamp(stat.st_mtime).strftime('%Y-%m-%d %H:%M:%S'),
|
"date": datetime.fromtimestamp(stat.st_mtime).strftime('%Y-%m-%d %H:%M:%S'),
|
||||||
|
"timestamp": stat.st_mtime, # For sorting
|
||||||
"duration": duration
|
"duration": duration
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# Sort recordings based on parameters
|
||||||
|
reverse = (sort_order == 'desc')
|
||||||
|
|
||||||
|
if sort_by == 'name':
|
||||||
|
recordings.sort(key=lambda x: x['filename'].lower(), reverse=reverse)
|
||||||
|
elif sort_by == 'duration':
|
||||||
|
recordings.sort(key=lambda x: x['duration'], reverse=reverse)
|
||||||
|
elif sort_by == 'size':
|
||||||
|
recordings.sort(key=lambda x: x['size'], reverse=reverse)
|
||||||
|
else: # default to date
|
||||||
|
recordings.sort(key=lambda x: x['timestamp'], reverse=reverse)
|
||||||
|
|
||||||
return recordings
|
return recordings
|
||||||
|
|
||||||
def get_local_ip():
|
def get_local_ip():
|
||||||
@@ -1995,6 +2020,23 @@ def main():
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if recordings %}
|
{% if recordings %}
|
||||||
|
<!-- Sort Controls -->
|
||||||
|
<div style="margin-bottom: 20px; padding: 15px; background: #f9fafb; border-radius: 8px; border: 1px solid #e5e7eb;">
|
||||||
|
<div style="display: flex; align-items: center; gap: 15px; flex-wrap: wrap;">
|
||||||
|
<span style="font-weight: 600; color: #374151;">🔄 Sort by:</span>
|
||||||
|
<select id="sort-by" onchange="updateSort()" style="padding: 8px 12px; border: 1px solid #d1d5db; border-radius: 6px; background: white; cursor: pointer; font-size: 0.95em;">
|
||||||
|
<option value="date" {% if sort_by == 'date' %}selected{% endif %}>📅 Date</option>
|
||||||
|
<option value="name" {% if sort_by == 'name' %}selected{% endif %}>📝 Name</option>
|
||||||
|
<option value="duration" {% if sort_by == 'duration' %}selected{% endif %}>⏱️ Duration</option>
|
||||||
|
<option value="size" {% if sort_by == 'size' %}selected{% endif %}>💾 Size</option>
|
||||||
|
</select>
|
||||||
|
<select id="sort-order" onchange="updateSort()" style="padding: 8px 12px; border: 1px solid #d1d5db; border-radius: 6px; background: white; cursor: pointer; font-size: 0.95em;">
|
||||||
|
<option value="desc" {% if sort_order == 'desc' %}selected{% endif %}>⬇️ Descending</option>
|
||||||
|
<option value="asc" {% if sort_order == 'asc' %}selected{% endif %}>⬆️ Ascending</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="stats">
|
<div class="stats">
|
||||||
<div class="stat-box">
|
<div class="stat-box">
|
||||||
<div class="stat-value">{{ recordings|length }}</div>
|
<div class="stat-value">{{ recordings|length }}</div>
|
||||||
@@ -2242,6 +2284,12 @@ def main():
|
|||||||
window.location.href = '/download_all';
|
window.location.href = '/download_all';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateSort() {
|
||||||
|
const sortBy = document.getElementById('sort-by').value;
|
||||||
|
const sortOrder = document.getElementById('sort-order').value;
|
||||||
|
window.location.href = `/?sort=${sortBy}&order=${sortOrder}`;
|
||||||
|
}
|
||||||
|
|
||||||
function deleteRecording(filename, index) {
|
function deleteRecording(filename, index) {
|
||||||
if (confirm('Delete this recording?')) {
|
if (confirm('Delete this recording?')) {
|
||||||
fetch('/delete/' + filename, { method: 'POST' })
|
fetch('/delete/' + filename, { method: 'POST' })
|
||||||
|
|||||||
Reference in New Issue
Block a user