Move Order and Display mode to the Photos tab
Build and push server image / build-and-push (push) Successful in 53s
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:
@@ -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)
|
@router.get("/frames/{frame_id}", response_class=HTMLResponse)
|
||||||
def frame_photos_page(frame_id: int, request: Request, db: Session = Depends(get_db)):
|
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]:
|
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,
|
palette_labels=PALETTE_LABELS,
|
||||||
default_palette_rgb=DEFAULT_PALETTE_RGB,
|
default_palette_rgb=DEFAULT_PALETTE_RGB,
|
||||||
palette_to_hex=palette_to_hex,
|
palette_to_hex=palette_to_hex,
|
||||||
display_mode_labels=DISPLAY_MODE_LABELS,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
// Configuration tab: frame settings + firmware card + take control.
|
// Configuration tab: frame settings + firmware card + take control.
|
||||||
// Frame name/mode live in the page header now (frame_header.js) and
|
// Frame name/mode live in the page header now (frame_header.js), Calendar
|
||||||
// Calendar settings have their own tab (frame_calendar.js). window.
|
// settings have their own tab (frame_calendar.js), and Order/Display mode
|
||||||
// FRAME_API is set by the template. Checkboxes are always sent
|
// live on the Photos tab (frame_photos.js) -- photos-specific settings,
|
||||||
// explicitly as "true"/"false" -- the server treats absent fields as
|
// not device-wide configuration. window.FRAME_API is set by the template.
|
||||||
// "leave unchanged", so a checkbox must never be simply omitted.
|
// 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() {
|
async function saveConfig() {
|
||||||
const minutes = parseInt(document.getElementById('refresh_interval_minutes').value, 10) || 60;
|
const minutes = parseInt(document.getElementById('refresh_interval_minutes').value, 10) || 60;
|
||||||
const body = new URLSearchParams({
|
const body = new URLSearchParams({
|
||||||
order: document.getElementById('order').value,
|
|
||||||
orientation: document.getElementById('orientation').value,
|
orientation: document.getElementById('orientation').value,
|
||||||
refresh_interval_s: String(minutes * 60),
|
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_enabled: String(document.getElementById('quiet_hours_enabled').checked),
|
||||||
quiet_hours_start: document.getElementById('quiet_hours_start').value || '22:00',
|
quiet_hours_start: document.getElementById('quiet_hours_start').value || '22:00',
|
||||||
quiet_hours_end: document.getElementById('quiet_hours_end').value || '07:00',
|
quiet_hours_end: document.getElementById('quiet_hours_end').value || '07:00',
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Photos tab: now-displaying, album picker, and the upcoming grid
|
// Photos tab: now-displaying, album picker, order/display-mode settings,
|
||||||
// (rendering/drag logic in queue.js). window.FRAME_API is set by the
|
// and the upcoming grid (rendering/drag logic in queue.js). window.
|
||||||
// template.
|
// FRAME_API is set by the template.
|
||||||
|
|
||||||
function renderControlBanner(control) {
|
function renderControlBanner(control) {
|
||||||
const banner = document.getElementById('control-banner');
|
const banner = document.getElementById('control-banner');
|
||||||
@@ -74,6 +74,8 @@ async function savePhotoSettings() {
|
|||||||
const body = new URLSearchParams({
|
const body = new URLSearchParams({
|
||||||
album_id: document.getElementById('album_id').value || '',
|
album_id: document.getElementById('album_id').value || '',
|
||||||
queue_target_len: document.getElementById('queue_target_len').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`, {
|
const resp = await fetch(`${window.FRAME_API}/config`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|||||||
@@ -18,12 +18,6 @@
|
|||||||
<section class="card">
|
<section class="card">
|
||||||
<h2 class="card-title">Display settings</h2>
|
<h2 class="card-title">Display settings</h2>
|
||||||
<form id="config-form">
|
<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
|
<label>Orientation
|
||||||
<select id="orientation">
|
<select id="orientation">
|
||||||
<option value="landscape" {% if frame.orientation == "landscape" %}selected{% endif %}>Landscape</option>
|
<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"
|
<input type="number" id="refresh_interval_minutes" min="1" max="1440"
|
||||||
value="{{ (frame.refresh_interval_s // 60) or 60 }}" required>
|
value="{{ (frame.refresh_interval_s // 60) or 60 }}" required>
|
||||||
</label>
|
</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">
|
<div class="checkbox-row">
|
||||||
<input type="checkbox" id="quiet_hours_enabled" {% if frame.quiet_hours_enabled %}checked{% endif %}>
|
<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>
|
<label for="quiet_hours_enabled">Quiet hours (don't wake overnight)</label>
|
||||||
|
|||||||
@@ -30,6 +30,27 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</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="button" class="secondary" id="load-albums">Load Albums</button>
|
||||||
<button type="submit">Save</button>
|
<button type="submit">Save</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
Reference in New Issue
Block a user