feat: wire Display tab — font size, hover delay, grid columns with immediate apply
This commit is contained in:
+57
-2
@@ -937,6 +937,51 @@ function saveDisplaySettings(settings) {
|
|||||||
} catch (_) { /* blocked — ok */ }
|
} catch (_) { /* blocked — ok */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply display settings to the live DOM.
|
||||||
|
* Sets --preview-font-size CSS custom property and updates #session-grid
|
||||||
|
* grid-template-columns based on the gridColumns setting.
|
||||||
|
* @param {object} ds - display settings object
|
||||||
|
*/
|
||||||
|
function applyDisplaySettings(ds) {
|
||||||
|
// Apply font size as CSS custom property
|
||||||
|
document.documentElement.style.setProperty('--preview-font-size', ds.fontSize + 'px');
|
||||||
|
|
||||||
|
// Apply grid columns
|
||||||
|
var grid = document.getElementById('session-grid');
|
||||||
|
if (grid) {
|
||||||
|
if (ds.gridColumns === 'auto') {
|
||||||
|
grid.style.removeProperty('grid-template-columns');
|
||||||
|
} else {
|
||||||
|
grid.style.gridTemplateColumns = 'repeat(' + ds.gridColumns + ', 1fr)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a change event on any Display settings control.
|
||||||
|
* Reads current values from form elements, saves via saveDisplaySettings,
|
||||||
|
* and applies via applyDisplaySettings immediately.
|
||||||
|
*/
|
||||||
|
function onDisplaySettingChange() {
|
||||||
|
var ds = loadDisplaySettings();
|
||||||
|
|
||||||
|
var fontSizeEl = document.getElementById('setting-font-size');
|
||||||
|
if (fontSizeEl) ds.fontSize = parseInt(fontSizeEl.value, 10) || ds.fontSize;
|
||||||
|
|
||||||
|
var hoverDelayEl = document.getElementById('setting-hover-delay');
|
||||||
|
if (hoverDelayEl) ds.hoverPreviewDelay = parseInt(hoverDelayEl.value, 10);
|
||||||
|
|
||||||
|
var gridColumnsEl = document.getElementById('setting-grid-columns');
|
||||||
|
if (gridColumnsEl) {
|
||||||
|
var raw = gridColumnsEl.value;
|
||||||
|
ds.gridColumns = raw === 'auto' ? 'auto' : parseInt(raw, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
saveDisplaySettings(ds);
|
||||||
|
applyDisplaySettings(ds);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open the settings dialog.
|
* Open the settings dialog.
|
||||||
* Sets _settingsOpen, calls dialog.showModal(), removes hidden from backdrop,
|
* Sets _settingsOpen, calls dialog.showModal(), removes hidden from backdrop,
|
||||||
@@ -1126,7 +1171,8 @@ function bindStaticEventListeners() {
|
|||||||
if (!tile) return;
|
if (!tile) return;
|
||||||
if (_previewTimer) { clearTimeout(_previewTimer); _previewTimer = null; }
|
if (_previewTimer) { clearTimeout(_previewTimer); _previewTimer = null; }
|
||||||
var name = tile.dataset.session;
|
var name = tile.dataset.session;
|
||||||
_previewTimer = setTimeout(function () { showPreview(name); }, 1500);
|
var delay = loadDisplaySettings().hoverPreviewDelay;
|
||||||
|
if (delay > 0) _previewTimer = setTimeout(function () { showPreview(name); }, delay);
|
||||||
}, true); // useCapture: true for delegation with mouseenter
|
}, true); // useCapture: true for delegation with mouseenter
|
||||||
|
|
||||||
gridEl.addEventListener('mouseleave', function (e) {
|
gridEl.addEventListener('mouseleave', function (e) {
|
||||||
@@ -1144,7 +1190,8 @@ function bindStaticEventListeners() {
|
|||||||
if (!item) return;
|
if (!item) return;
|
||||||
if (_previewTimer) { clearTimeout(_previewTimer); _previewTimer = null; }
|
if (_previewTimer) { clearTimeout(_previewTimer); _previewTimer = null; }
|
||||||
var name = item.dataset.session;
|
var name = item.dataset.session;
|
||||||
_previewTimer = setTimeout(function () { showPreview(name); }, 1500);
|
var delay = loadDisplaySettings().hoverPreviewDelay;
|
||||||
|
if (delay > 0) _previewTimer = setTimeout(function () { showPreview(name); }, delay);
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
sidebarListEl.addEventListener('mouseleave', function (e) {
|
sidebarListEl.addEventListener('mouseleave', function (e) {
|
||||||
@@ -1153,6 +1200,11 @@ function bindStaticEventListeners() {
|
|||||||
hidePreview();
|
hidePreview();
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Display settings — bind change events for immediate apply
|
||||||
|
on($('setting-font-size'), 'change', onDisplaySettingChange);
|
||||||
|
on($('setting-hover-delay'), 'change', onDisplaySettingChange);
|
||||||
|
on($('setting-grid-columns'), 'change', onDisplaySettingChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Test-only helpers ────────────────────────────────────────────────────────
|
// ─── Test-only helpers ────────────────────────────────────────────────────────
|
||||||
@@ -1169,6 +1221,7 @@ function _setViewMode(mode) {
|
|||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
initDeviceId();
|
initDeviceId();
|
||||||
|
applyDisplaySettings(loadDisplaySettings());
|
||||||
document.addEventListener('keydown', trackInteraction);
|
document.addEventListener('keydown', trackInteraction);
|
||||||
document.addEventListener('click', trackInteraction);
|
document.addEventListener('click', trackInteraction);
|
||||||
document.addEventListener('touchstart', trackInteraction);
|
document.addEventListener('touchstart', trackInteraction);
|
||||||
@@ -1233,6 +1286,8 @@ if (typeof module !== 'undefined' && module.exports) {
|
|||||||
// Settings
|
// Settings
|
||||||
loadDisplaySettings,
|
loadDisplaySettings,
|
||||||
saveDisplaySettings,
|
saveDisplaySettings,
|
||||||
|
applyDisplaySettings,
|
||||||
|
onDisplaySettingChange,
|
||||||
openSettings,
|
openSettings,
|
||||||
closeSettings,
|
closeSettings,
|
||||||
switchSettingsTab,
|
switchSettingsTab,
|
||||||
|
|||||||
Binary file not shown.
@@ -845,3 +845,274 @@ def test_exports_switch_settings_tab() -> None:
|
|||||||
assert "switchSettingsTab" in exports, (
|
assert "switchSettingsTab" in exports, (
|
||||||
"module.exports must export switchSettingsTab"
|
"module.exports must export switchSettingsTab"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ── Display tab wiring (task-8) ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def test_apply_display_settings_function_exists() -> None:
|
||||||
|
"""applyDisplaySettings function must exist."""
|
||||||
|
assert "function applyDisplaySettings" in _JS, (
|
||||||
|
"applyDisplaySettings must be defined in app.js"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_apply_display_settings_sets_font_size_css_property() -> None:
|
||||||
|
"""applyDisplaySettings must set --preview-font-size CSS custom property."""
|
||||||
|
match = re.search(
|
||||||
|
r"function applyDisplaySettings\s*\(\w+\)\s*\{(.*?)(?=\nfunction |\n// )",
|
||||||
|
_JS,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, "applyDisplaySettings function not found"
|
||||||
|
body = match.group(1)
|
||||||
|
assert "--preview-font-size" in body, (
|
||||||
|
"applyDisplaySettings must set --preview-font-size CSS property"
|
||||||
|
)
|
||||||
|
assert "documentElement" in body, (
|
||||||
|
"applyDisplaySettings must call setProperty on document.documentElement"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_apply_display_settings_sets_grid_columns_repeat() -> None:
|
||||||
|
"""applyDisplaySettings must set repeat(N, 1fr) for numeric grid columns."""
|
||||||
|
match = re.search(
|
||||||
|
r"function applyDisplaySettings\s*\(\w+\)\s*\{(.*?)(?=\nfunction |\n// )",
|
||||||
|
_JS,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, "applyDisplaySettings function not found"
|
||||||
|
body = match.group(1)
|
||||||
|
assert "repeat" in body and "1fr" in body, (
|
||||||
|
"applyDisplaySettings must set repeat(N, 1fr) for numeric grid columns"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_apply_display_settings_handles_auto_grid_columns() -> None:
|
||||||
|
"""applyDisplaySettings must handle 'auto' gridColumns by removing inline style."""
|
||||||
|
match = re.search(
|
||||||
|
r"function applyDisplaySettings\s*\(\w+\)\s*\{(.*?)(?=\nfunction |\n// )",
|
||||||
|
_JS,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, "applyDisplaySettings function not found"
|
||||||
|
body = match.group(1)
|
||||||
|
assert "auto" in body, (
|
||||||
|
"applyDisplaySettings must handle 'auto' gridColumns"
|
||||||
|
)
|
||||||
|
assert "session-grid" in body or "gridTemplateColumns" in body, (
|
||||||
|
"applyDisplaySettings must update session-grid or gridTemplateColumns"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_on_display_setting_change_function_exists() -> None:
|
||||||
|
"""onDisplaySettingChange function must exist."""
|
||||||
|
assert "function onDisplaySettingChange" in _JS, (
|
||||||
|
"onDisplaySettingChange must be defined in app.js"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_on_display_setting_change_reads_font_size() -> None:
|
||||||
|
"""onDisplaySettingChange must read from setting-font-size element."""
|
||||||
|
match = re.search(
|
||||||
|
r"function onDisplaySettingChange\s*\(\s*\)\s*\{(.*?)(?=\nfunction |\n// )",
|
||||||
|
_JS,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, "onDisplaySettingChange function not found"
|
||||||
|
body = match.group(1)
|
||||||
|
assert "setting-font-size" in body, (
|
||||||
|
"onDisplaySettingChange must read from setting-font-size"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_on_display_setting_change_reads_hover_delay() -> None:
|
||||||
|
"""onDisplaySettingChange must read from setting-hover-delay element."""
|
||||||
|
match = re.search(
|
||||||
|
r"function onDisplaySettingChange\s*\(\s*\)\s*\{(.*?)(?=\nfunction |\n// )",
|
||||||
|
_JS,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, "onDisplaySettingChange function not found"
|
||||||
|
body = match.group(1)
|
||||||
|
assert "setting-hover-delay" in body, (
|
||||||
|
"onDisplaySettingChange must read from setting-hover-delay"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_on_display_setting_change_reads_grid_columns() -> None:
|
||||||
|
"""onDisplaySettingChange must read from setting-grid-columns element."""
|
||||||
|
match = re.search(
|
||||||
|
r"function onDisplaySettingChange\s*\(\s*\)\s*\{(.*?)(?=\nfunction |\n// )",
|
||||||
|
_JS,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, "onDisplaySettingChange function not found"
|
||||||
|
body = match.group(1)
|
||||||
|
assert "setting-grid-columns" in body, (
|
||||||
|
"onDisplaySettingChange must read from setting-grid-columns"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_on_display_setting_change_calls_save_display_settings() -> None:
|
||||||
|
"""onDisplaySettingChange must call saveDisplaySettings."""
|
||||||
|
match = re.search(
|
||||||
|
r"function onDisplaySettingChange\s*\(\s*\)\s*\{(.*?)(?=\nfunction |\n// )",
|
||||||
|
_JS,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, "onDisplaySettingChange function not found"
|
||||||
|
body = match.group(1)
|
||||||
|
assert "saveDisplaySettings" in body, (
|
||||||
|
"onDisplaySettingChange must call saveDisplaySettings"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_on_display_setting_change_calls_apply_display_settings() -> None:
|
||||||
|
"""onDisplaySettingChange must call applyDisplaySettings."""
|
||||||
|
match = re.search(
|
||||||
|
r"function onDisplaySettingChange\s*\(\s*\)\s*\{(.*?)(?=\nfunction |\n// )",
|
||||||
|
_JS,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, "onDisplaySettingChange function not found"
|
||||||
|
body = match.group(1)
|
||||||
|
assert "applyDisplaySettings" in body, (
|
||||||
|
"onDisplaySettingChange must call applyDisplaySettings"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_hover_preview_no_hardcoded_1500() -> None:
|
||||||
|
"""Hover preview delays must not be hardcoded 1500ms — must use loadDisplaySettings."""
|
||||||
|
match = re.search(
|
||||||
|
r"function bindStaticEventListeners\s*\(\s*\)\s*\{(.*?)\n\}",
|
||||||
|
_JS,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, "bindStaticEventListeners function not found"
|
||||||
|
body = match.group(1)
|
||||||
|
# Count occurrences of hardcoded 1500 in the body
|
||||||
|
hardcoded_count = body.count(", 1500)")
|
||||||
|
assert hardcoded_count == 0, (
|
||||||
|
f"Hover preview must not use hardcoded 1500ms delay (found {hardcoded_count} occurrences). "
|
||||||
|
"Use loadDisplaySettings().hoverPreviewDelay instead."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_hover_preview_reads_delay_from_settings() -> None:
|
||||||
|
"""Hover preview must read delay from loadDisplaySettings().hoverPreviewDelay."""
|
||||||
|
match = re.search(
|
||||||
|
r"function bindStaticEventListeners\s*\(\s*\)\s*\{(.*?)\n\}",
|
||||||
|
_JS,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, "bindStaticEventListeners function not found"
|
||||||
|
body = match.group(1)
|
||||||
|
assert "loadDisplaySettings" in body and "hoverPreviewDelay" in body, (
|
||||||
|
"bindStaticEventListeners hover preview must read delay from loadDisplaySettings().hoverPreviewDelay"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_hover_preview_skips_when_delay_zero() -> None:
|
||||||
|
"""Hover preview must check delay > 0 before setting setTimeout."""
|
||||||
|
match = re.search(
|
||||||
|
r"function bindStaticEventListeners\s*\(\s*\)\s*\{(.*?)\n\}",
|
||||||
|
_JS,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, "bindStaticEventListeners function not found"
|
||||||
|
body = match.group(1)
|
||||||
|
# The body must have a conditional guard on the delay value
|
||||||
|
assert "delay > 0" in body or "> 0" in body, (
|
||||||
|
"bindStaticEventListeners hover preview must guard: if (delay > 0) before setTimeout"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_bind_static_event_listeners_binds_font_size_change() -> None:
|
||||||
|
"""bindStaticEventListeners must bind change event on setting-font-size."""
|
||||||
|
match = re.search(
|
||||||
|
r"function bindStaticEventListeners\s*\(\s*\)\s*\{(.*?)\n\}",
|
||||||
|
_JS,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, "bindStaticEventListeners function not found"
|
||||||
|
body = match.group(1)
|
||||||
|
assert "setting-font-size" in body, (
|
||||||
|
"bindStaticEventListeners must reference setting-font-size for change binding"
|
||||||
|
)
|
||||||
|
assert "onDisplaySettingChange" in body, (
|
||||||
|
"bindStaticEventListeners must call onDisplaySettingChange on change"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_bind_static_event_listeners_binds_hover_delay_change() -> None:
|
||||||
|
"""bindStaticEventListeners must bind change event on setting-hover-delay."""
|
||||||
|
match = re.search(
|
||||||
|
r"function bindStaticEventListeners\s*\(\s*\)\s*\{(.*?)\n\}",
|
||||||
|
_JS,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, "bindStaticEventListeners function not found"
|
||||||
|
body = match.group(1)
|
||||||
|
assert "setting-hover-delay" in body, (
|
||||||
|
"bindStaticEventListeners must reference setting-hover-delay for change binding"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_bind_static_event_listeners_binds_grid_columns_change() -> None:
|
||||||
|
"""bindStaticEventListeners must bind change event on setting-grid-columns."""
|
||||||
|
match = re.search(
|
||||||
|
r"function bindStaticEventListeners\s*\(\s*\)\s*\{(.*?)\n\}",
|
||||||
|
_JS,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, "bindStaticEventListeners function not found"
|
||||||
|
body = match.group(1)
|
||||||
|
assert "setting-grid-columns" in body, (
|
||||||
|
"bindStaticEventListeners must reference setting-grid-columns for change binding"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_dom_content_loaded_calls_apply_display_settings() -> None:
|
||||||
|
"""DOMContentLoaded handler must call applyDisplaySettings(loadDisplaySettings())."""
|
||||||
|
# Find the DOMContentLoaded handler block
|
||||||
|
match = re.search(
|
||||||
|
r"DOMContentLoaded.*?\{(.*?)(?=\}\);?\s*\n// |\}\);\s*$)",
|
||||||
|
_JS,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, "DOMContentLoaded handler not found"
|
||||||
|
body = match.group(1)
|
||||||
|
assert "applyDisplaySettings" in body, (
|
||||||
|
"DOMContentLoaded handler must call applyDisplaySettings(loadDisplaySettings())"
|
||||||
|
)
|
||||||
|
assert "loadDisplaySettings" in body, (
|
||||||
|
"DOMContentLoaded handler must call applyDisplaySettings(loadDisplaySettings())"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_exports_apply_display_settings() -> None:
|
||||||
|
"""module.exports must export applyDisplaySettings."""
|
||||||
|
match = re.search(
|
||||||
|
r"module\.exports\s*=\s*\{(.*?)\};",
|
||||||
|
_JS,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, "module.exports block not found"
|
||||||
|
exports = match.group(1)
|
||||||
|
assert "applyDisplaySettings" in exports, (
|
||||||
|
"module.exports must export applyDisplaySettings"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_exports_on_display_setting_change() -> None:
|
||||||
|
"""module.exports must export onDisplaySettingChange."""
|
||||||
|
match = re.search(
|
||||||
|
r"module\.exports\s*=\s*\{(.*?)\};",
|
||||||
|
_JS,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, "module.exports block not found"
|
||||||
|
exports = match.group(1)
|
||||||
|
assert "onDisplaySettingChange" in exports, (
|
||||||
|
"module.exports must export onDisplaySettingChange"
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user