Roll out "modern" HTML/CSS render style to every widget except photos
Extends weather's experimental Chromium+Jinja2 render style to battery, text, tasks, static image, whiteboard, and calendar (all four view modes -- agenda/today_tomorrow/week/month), and gives the photos widget its own genuinely independent palette + dithering strength. Photos: Frame.photo_palette_rgb/photo_dither_strength (mirroring the existing palette_rgb/dither_strength), with a second "Photos configuration" card in Advanced Configuration. widgets/photos.py's render() quantizes itself against these before returning -- no render_panel changes needed, since photos is the only widget that genuinely needs a different reference palette and can carry that itself, the same way modern-style widgets already self-dither via ordered_dither. Battery/text/tasks/static image/whiteboard: same render_style pattern weather established (render_style column, html_render.py build function, Jinja2 template, dialog toggle). Static image/whiteboard get their first-ever visual chrome (a rounded-corner shadowed card, shared framed_image.html.jinja) since classic draws them with zero frame at all. Fixed the same "preview endpoint bypasses render_style" bug weather originally shipped with, for tasks/static/whiteboard/ calendar's preview endpoints. Calendar: own module (app/calendar_html_render.py, mirroring calendar_render.py's separation from the simpler widgets) covering all four view modes, not just agenda -- reuses calendar_render's own private helpers so event colors/times/weather/month-grid math match classic exactly. Found and fixed two real cross-day layout bugs along the way: a per-day header height that varied based on whether that specific day had a weather entry (misaligning where every other day's event rows started across the week/month grid), and regular-weight small text being fragile under Bayer ordered dithering (out-of-month day numbers degraded into unrecognizable speckle) -- fixed by using bold everywhere and de-emphasizing via size instead of weight/gray, since gray text has the same dithering fragility this project's PIL renderers already avoid for exactly this reason. Migrations 32-38 (Frame's two new columns, then one render_style column per widget config table). 452 tests passing, including new dispatch/ migration coverage per widget type and a dedicated photos test proving photo_palette_rgb produces genuinely independent quantization from the frame's main palette_rgb.
This commit is contained in:
@@ -114,7 +114,7 @@ async function loadControl() {
|
||||
|
||||
document.getElementById('take-control').addEventListener('click', takeControl);
|
||||
|
||||
// ---- Advanced configuration: color palette ----
|
||||
// ---- Advanced configuration: color palette(s) ----
|
||||
//
|
||||
// Hex field and R/G/B number fields are kept in sync live, both
|
||||
// directions -- editing either updates the other plus the preview
|
||||
@@ -122,9 +122,15 @@ document.getElementById('take-control').addEventListener('click', takeControl);
|
||||
// the server already validates as #rrggbb); the R/G/B fields are purely
|
||||
// an alternate, more precise way to arrive at the same value than
|
||||
// eyeballing a color-picker swatch.
|
||||
//
|
||||
// Parameterized by classPrefix ("palette" for the main one, "photo-
|
||||
// palette" for the photos-only one added alongside it) rather than
|
||||
// duplicated wholesale -- there are exactly two real instances of this,
|
||||
// not a speculative future one, and the two would otherwise be ~90
|
||||
// near-identical lines apart.
|
||||
|
||||
function paletteHexInputs() {
|
||||
return Array.from(document.querySelectorAll('.palette-hex'))
|
||||
function paletteHexInputs(classPrefix) {
|
||||
return Array.from(document.querySelectorAll(`.${classPrefix}-hex`))
|
||||
.sort((a, b) => Number(a.dataset.index) - Number(b.dataset.index));
|
||||
}
|
||||
|
||||
@@ -140,45 +146,53 @@ function rgbFromHex(hex) {
|
||||
return [(n >> 16) & 255, (n >> 8) & 255, n & 255];
|
||||
}
|
||||
|
||||
function paletteFieldsFor(index) {
|
||||
function paletteFieldsFor(classPrefix, index) {
|
||||
const at = (cls) => document.querySelector(`.${cls}[data-index="${index}"]`);
|
||||
return { hex: at('palette-hex'), r: at('palette-r'), g: at('palette-g'), b: at('palette-b'), swatch: at('palette-swatch-preview') };
|
||||
return {
|
||||
hex: at(`${classPrefix}-hex`), r: at(`${classPrefix}-r`), g: at(`${classPrefix}-g`), b: at(`${classPrefix}-b`),
|
||||
swatch: at(`${classPrefix}-swatch-preview`),
|
||||
};
|
||||
}
|
||||
|
||||
function syncPaletteFromHex(index) {
|
||||
const f = paletteFieldsFor(index);
|
||||
function syncPaletteFromHex(classPrefix, index) {
|
||||
const f = paletteFieldsFor(classPrefix, index);
|
||||
const rgb = rgbFromHex(f.hex.value);
|
||||
if (!rgb) return;
|
||||
[f.r.value, f.g.value, f.b.value] = rgb;
|
||||
f.swatch.style.background = f.hex.value;
|
||||
}
|
||||
|
||||
function syncPaletteFromRgb(index) {
|
||||
const f = paletteFieldsFor(index);
|
||||
function syncPaletteFromRgb(classPrefix, index) {
|
||||
const f = paletteFieldsFor(classPrefix, index);
|
||||
const hex = hexFromRgb(f.r.value, f.g.value, f.b.value);
|
||||
f.hex.value = hex;
|
||||
f.swatch.style.background = hex;
|
||||
}
|
||||
|
||||
const palettePickerCount = paletteHexInputs().length;
|
||||
for (let i = 0; i < palettePickerCount; i++) {
|
||||
const f = paletteFieldsFor(i);
|
||||
f.hex.addEventListener('input', () => syncPaletteFromHex(i));
|
||||
[f.r, f.g, f.b].forEach((el) => el.addEventListener('input', () => syncPaletteFromRgb(i)));
|
||||
function wirePaletteInputs(classPrefix) {
|
||||
const count = paletteHexInputs(classPrefix).length;
|
||||
for (let i = 0; i < count; i++) {
|
||||
const f = paletteFieldsFor(classPrefix, i);
|
||||
f.hex.addEventListener('input', () => syncPaletteFromHex(classPrefix, i));
|
||||
[f.r, f.g, f.b].forEach((el) => el.addEventListener('input', () => syncPaletteFromRgb(classPrefix, i)));
|
||||
}
|
||||
}
|
||||
|
||||
wirePaletteInputs('palette');
|
||||
wirePaletteInputs('photo-palette');
|
||||
|
||||
// Sliders: live numeric readout next to each, no save until the button
|
||||
// below is clicked.
|
||||
['color_boost', 'contrast_boost', 'dither_strength'].forEach((id) => {
|
||||
['color_boost', 'contrast_boost', 'dither_strength', 'photo_dither_strength'].forEach((id) => {
|
||||
const input = document.getElementById(id);
|
||||
const readout = document.getElementById(`${id}_value`);
|
||||
input.addEventListener('input', () => { readout.textContent = Number(input.value).toFixed(2); });
|
||||
});
|
||||
|
||||
async function savePalette(extra) {
|
||||
async function savePalette(classPrefix, paletteFormKey, extra) {
|
||||
const body = new URLSearchParams(extra || {});
|
||||
for (const input of paletteHexInputs()) {
|
||||
body.append('palette', input.value);
|
||||
for (const input of paletteHexInputs(classPrefix)) {
|
||||
body.append(paletteFormKey, input.value);
|
||||
}
|
||||
try {
|
||||
const resp = await fetch(`${window.FRAME_API}/config`, {
|
||||
@@ -195,7 +209,7 @@ async function savePalette(extra) {
|
||||
}
|
||||
|
||||
document.getElementById('palette-save').addEventListener('click', () => {
|
||||
savePalette({
|
||||
savePalette('palette', 'palette', {
|
||||
color_boost: document.getElementById('color_boost').value,
|
||||
contrast_boost: document.getElementById('contrast_boost').value,
|
||||
dither_strength: document.getElementById('dither_strength').value,
|
||||
@@ -203,16 +217,16 @@ document.getElementById('palette-save').addEventListener('click', () => {
|
||||
});
|
||||
|
||||
document.getElementById('palette-reset').addEventListener('click', () => {
|
||||
const inputs = paletteHexInputs();
|
||||
const inputs = paletteHexInputs('palette');
|
||||
window.DEFAULT_PALETTE_HEX.forEach((hex, i) => {
|
||||
inputs[i].value = hex;
|
||||
syncPaletteFromHex(i);
|
||||
syncPaletteFromHex('palette', i);
|
||||
});
|
||||
['color_boost', 'contrast_boost', 'dither_strength'].forEach((id) => {
|
||||
document.getElementById(id).value = '1';
|
||||
document.getElementById(`${id}_value`).textContent = '1.00';
|
||||
});
|
||||
savePalette({ palette_reset: 'true', color_boost: '1', contrast_boost: '1', dither_strength: '1' });
|
||||
savePalette('palette', 'palette', { palette_reset: 'true', color_boost: '1', contrast_boost: '1', dither_strength: '1' });
|
||||
});
|
||||
|
||||
// Fills the table with a community-measured starting point (see the
|
||||
@@ -220,13 +234,32 @@ document.getElementById('palette-reset').addEventListener('click', () => {
|
||||
// editing the hex/RGB fields by hand; the user still clicks Save (or
|
||||
// Reset) to commit or discard it.
|
||||
document.getElementById('palette-load-calibrated').addEventListener('click', () => {
|
||||
const inputs = paletteHexInputs();
|
||||
const inputs = paletteHexInputs('palette');
|
||||
window.CALIBRATED_SPECTRA6_HEX.forEach((hex, i) => {
|
||||
inputs[i].value = hex;
|
||||
syncPaletteFromHex(i);
|
||||
syncPaletteFromHex('palette', i);
|
||||
});
|
||||
});
|
||||
|
||||
// ---- Photos configuration: its own separate palette/dithering ----
|
||||
|
||||
document.getElementById('photo-palette-save').addEventListener('click', () => {
|
||||
savePalette('photo-palette', 'photo_palette', {
|
||||
photo_dither_strength: document.getElementById('photo_dither_strength').value,
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('photo-palette-reset').addEventListener('click', () => {
|
||||
const inputs = paletteHexInputs('photo-palette');
|
||||
window.DEFAULT_PALETTE_HEX.forEach((hex, i) => {
|
||||
inputs[i].value = hex;
|
||||
syncPaletteFromHex('photo-palette', i);
|
||||
});
|
||||
document.getElementById('photo_dither_strength').value = '1';
|
||||
document.getElementById('photo_dither_strength_value').textContent = '1.00';
|
||||
savePalette('photo-palette', 'photo_palette', { photo_palette_reset: 'true', photo_dither_strength: '1' });
|
||||
});
|
||||
|
||||
// ---- Preview: current photo vs. how it renders with saved settings ----
|
||||
// Scoped to this frame's photo widget (window.PHOTO_WIDGET_PREVIEW_API,
|
||||
// set by the template) rather than window.FRAME_API -- palette/color/
|
||||
|
||||
Reference in New Issue
Block a user