Move Order and Display mode to the Photos tab
Build and push server image / build-and-push (push) Successful in 53s

Both are photos-specific settings (rotation order, how a photo's aspect
ratio gets reconciled with the panel), not device-wide configuration --
they belonged on the Photos tab next to the album/queue settings, not
Configuration. No backend change: both already save through the shared
partial-update /config endpoint regardless of which tab's form sends
them.
This commit is contained in:
2026-07-23 09:07:49 -04:00
parent db9a6f1875
commit 14cf212a60
5 changed files with 37 additions and 33 deletions
+4 -2
View File
@@ -42,7 +42,10 @@ def _frame_page(request: Request, db: Session, frame_id: int, template: str, tab
@router.get("/frames/{frame_id}", response_class=HTMLResponse)
def frame_photos_page(frame_id: int, request: Request, db: Session = Depends(get_db)):
return _frame_page(request, db, frame_id, "frame_photos.html", "photos")
return _frame_page(
request, db, frame_id, "frame_photos.html", "photos",
display_mode_labels=DISPLAY_MODE_LABELS,
)
def _user_available_calendars(user: User) -> list[dict]:
@@ -122,7 +125,6 @@ def frame_config_page(frame_id: int, request: Request, db: Session = Depends(get
palette_labels=PALETTE_LABELS,
default_palette_rgb=DEFAULT_PALETTE_RGB,
palette_to_hex=palette_to_hex,
display_mode_labels=DISPLAY_MODE_LABELS,
)
+7 -7
View File
@@ -1,17 +1,17 @@
// Configuration tab: frame settings + firmware card + take control.
// Frame name/mode live in the page header now (frame_header.js) and
// Calendar settings have their own tab (frame_calendar.js). window.
// FRAME_API is set by the template. Checkboxes are always sent
// explicitly as "true"/"false" -- the server treats absent fields as
// "leave unchanged", so a checkbox must never be simply omitted.
// Frame name/mode live in the page header now (frame_header.js), Calendar
// settings have their own tab (frame_calendar.js), and Order/Display mode
// live on the Photos tab (frame_photos.js) -- photos-specific settings,
// not device-wide configuration. window.FRAME_API is set by the template.
// Checkboxes are always sent explicitly as "true"/"false" -- the server
// treats absent fields as "leave unchanged", so a checkbox must never be
// simply omitted.
async function saveConfig() {
const minutes = parseInt(document.getElementById('refresh_interval_minutes').value, 10) || 60;
const body = new URLSearchParams({
order: document.getElementById('order').value,
orientation: document.getElementById('orientation').value,
refresh_interval_s: String(minutes * 60),
display_mode: document.getElementById('display_mode').value,
quiet_hours_enabled: String(document.getElementById('quiet_hours_enabled').checked),
quiet_hours_start: document.getElementById('quiet_hours_start').value || '22:00',
quiet_hours_end: document.getElementById('quiet_hours_end').value || '07:00',
+5 -3
View File
@@ -1,6 +1,6 @@
// Photos tab: now-displaying, album picker, and the upcoming grid
// (rendering/drag logic in queue.js). window.FRAME_API is set by the
// template.
// Photos tab: now-displaying, album picker, order/display-mode settings,
// and the upcoming grid (rendering/drag logic in queue.js). window.
// FRAME_API is set by the template.
function renderControlBanner(control) {
const banner = document.getElementById('control-banner');
@@ -74,6 +74,8 @@ async function savePhotoSettings() {
const body = new URLSearchParams({
album_id: document.getElementById('album_id').value || '',
queue_target_len: document.getElementById('queue_target_len').value,
order: document.getElementById('order').value,
display_mode: document.getElementById('display_mode').value,
});
const resp = await fetch(`${window.FRAME_API}/config`, {
method: 'POST',
-21
View File
@@ -18,12 +18,6 @@
<section class="card">
<h2 class="card-title">Display settings</h2>
<form id="config-form">
<label>Order
<select id="order">
<option value="sequential" {% if frame.order == "sequential" %}selected{% endif %}>Sequential</option>
<option value="shuffle" {% if frame.order == "shuffle" %}selected{% endif %}>Shuffle</option>
</select>
</label>
<label>Orientation
<select id="orientation">
<option value="landscape" {% if frame.orientation == "landscape" %}selected{% endif %}>Landscape</option>
@@ -36,21 +30,6 @@
<input type="number" id="refresh_interval_minutes" min="1" max="1440"
value="{{ (frame.refresh_interval_s // 60) or 60 }}" required>
</label>
<label>Display mode
<select id="display_mode">
{% for mode, label in display_mode_labels.items() %}
<option value="{{ mode }}" {% if frame.display_mode == mode %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</label>
<p class="sub" style="margin-top: 8px;">How a photo's aspect ratio
is reconciled with the panel's: <strong>Crop to fill</strong>
trims the excess; <strong>Crop to faces</strong> does the same
but shifts the crop to keep people on screen; <strong>Stretch to
fill</strong> fills the panel exactly without cropping (photos
not matching the panel's aspect ratio look stretched);
<strong>Shrink to fit</strong> shows the whole photo, letterboxed
if needed.</p>
<div class="checkbox-row">
<input type="checkbox" id="quiet_hours_enabled" {% if frame.quiet_hours_enabled %}checked{% endif %}>
<label for="quiet_hours_enabled">Quiet hours (don't wake overnight)</label>
+21
View File
@@ -30,6 +30,27 @@
{% endfor %}
</select>
</label>
<label>Order
<select id="order">
<option value="sequential" {% if frame.order == "sequential" %}selected{% endif %}>Sequential</option>
<option value="shuffle" {% if frame.order == "shuffle" %}selected{% endif %}>Shuffle</option>
</select>
</label>
<label>Display mode
<select id="display_mode">
{% for mode, label in display_mode_labels.items() %}
<option value="{{ mode }}" {% if frame.display_mode == mode %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</label>
<p class="sub" style="margin-top: 8px;">How a photo's aspect ratio
is reconciled with the panel's: <strong>Crop to fill</strong>
trims the excess; <strong>Crop to faces</strong> does the same
but shifts the crop to keep people on screen; <strong>Stretch to
fill</strong> fills the panel exactly without cropping (photos
not matching the panel's aspect ratio look stretched);
<strong>Shrink to fit</strong> shows the whole photo, letterboxed
if needed.</p>
<button type="button" class="secondary" id="load-albums">Load Albums</button>
<button type="submit">Save</button>
</form>