feat: add Sessions settings tab with server-side persistence

This commit is contained in:
Brian Krabach
2026-03-29 23:57:22 -07:00
parent f380307f08
commit c955aadad1
6 changed files with 500 additions and 1 deletions
+127
View File
@@ -132,6 +132,7 @@ var _previewSessionName = null; // track by NAME, not DOM element
// ─── Settings state ───────────────────────────────────────────────────────────
let _settingsOpen = false;
let _serverSettings = null;
const DISPLAY_SETTINGS_KEY = 'muxplex.display';
const DISPLAY_DEFAULTS = {
fontSize: 14,
@@ -909,6 +910,42 @@ function _setViewingSession(name) {
_viewingSession = name;
}
// ─── Server settings ─────────────────────────────────────────────────────────
/**
* Load server settings from GET /api/settings and cache in _serverSettings.
* Always resolves — errors are logged as warnings.
* @returns {Promise<object>}
*/
async function loadServerSettings() {
try {
const res = await api('GET', '/api/settings');
_serverSettings = await res.json();
} catch (err) {
console.warn('[loadServerSettings] failed:', err);
if (!_serverSettings) _serverSettings = {};
}
return _serverSettings;
}
/**
* Send a PATCH to /api/settings with a single key/value update.
* Shows a toast on success or failure.
* @param {string} key
* @param {*} value
* @returns {Promise<void>}
*/
async function patchServerSetting(key, value) {
try {
await api('PATCH', '/api/settings', { [key]: value });
_serverSettings = Object.assign({}, _serverSettings, { [key]: value });
showToast('Setting saved');
} catch (err) {
showToast('Failed to save setting');
console.warn('[patchServerSetting] failed:', err);
}
}
// ─── Settings dialog ──────────────────────────────────────────────────────────
/**
@@ -1000,6 +1037,61 @@ function openSettings() {
if (hoverDelayEl) hoverDelayEl.value = String(settings.hoverPreviewDelay);
const gridColumnsEl = $('setting-grid-columns');
if (gridColumnsEl) gridColumnsEl.value = String(settings.gridColumns);
// Populate Sessions tab from server settings
loadServerSettings().then(function(ss) {
// Default session dropdown
const defaultSessionEl = $('setting-default-session');
if (defaultSessionEl) {
// Rebuild options from current sessions
defaultSessionEl.innerHTML = '<option value="">(none)</option>';
(_currentSessions || []).forEach(function(s) {
const opt = document.createElement('option');
opt.value = s.name || '';
opt.textContent = s.name || '';
if (ss && ss.default_session === s.name) opt.selected = true;
defaultSessionEl.appendChild(opt);
});
}
// Sort order
const sortOrderEl = $('setting-sort-order');
if (sortOrderEl && ss && ss.sort_order) {
sortOrderEl.value = ss.sort_order;
}
// Hidden sessions checkboxes
const hiddenSessionsEl = $('setting-hidden-sessions');
if (hiddenSessionsEl) {
hiddenSessionsEl.innerHTML = '';
const hiddenList = (ss && ss.hidden_sessions) || [];
(_currentSessions || []).forEach(function(s) {
const name = s.name || '';
const item = document.createElement('label');
item.className = 'settings-checkbox-item';
const cb = document.createElement('input');
cb.type = 'checkbox';
cb.className = 'settings-checkbox';
cb.value = name;
cb.checked = hiddenList.includes(name);
item.appendChild(cb);
item.appendChild(document.createTextNode(' ' + name));
hiddenSessionsEl.appendChild(item);
});
}
// Window size largest
const windowSizeEl = $('setting-window-size-largest');
if (windowSizeEl) {
windowSizeEl.checked = !!(ss && ss.window_size_largest);
}
// Auto-open
const autoOpenEl = $('setting-auto-open');
if (autoOpenEl) {
autoOpenEl.checked = ss && ss.auto_open !== undefined ? !!ss.auto_open : true;
}
});
}
/**
@@ -1205,6 +1297,38 @@ function bindStaticEventListeners() {
on($('setting-font-size'), 'change', onDisplaySettingChange);
on($('setting-hover-delay'), 'change', onDisplaySettingChange);
on($('setting-grid-columns'), 'change', onDisplaySettingChange);
// Sessions settings — bind change events for server-side persistence
on($('setting-default-session'), 'change', function() {
var el = $('setting-default-session');
if (el) patchServerSetting('default_session', el.value);
});
on($('setting-sort-order'), 'change', function() {
var el = $('setting-sort-order');
if (el) patchServerSetting('sort_order', el.value);
});
on($('setting-window-size-largest'), 'change', function() {
var el = $('setting-window-size-largest');
if (el) patchServerSetting('window_size_largest', el.checked);
});
on($('setting-auto-open'), 'change', function() {
var el = $('setting-auto-open');
if (el) patchServerSetting('auto_open', el.checked);
});
// Hidden sessions — delegated handler on container (checkboxes are dynamic)
var hiddenSessionsContainer = $('setting-hidden-sessions');
if (hiddenSessionsContainer) {
hiddenSessionsContainer.addEventListener('change', function(e) {
var cb = e.target.closest('input[type="checkbox"]');
if (!cb) return;
var hidden = [];
hiddenSessionsContainer.querySelectorAll('input[type="checkbox"]').forEach(function(c) {
if (c.checked) hidden.push(c.value);
});
patchServerSetting('hidden_sessions', hidden);
});
}
}
// ─── Test-only helpers ────────────────────────────────────────────────────────
@@ -1291,6 +1415,9 @@ if (typeof module !== 'undefined' && module.exports) {
openSettings,
closeSettings,
switchSettingsTab,
// Server settings
loadServerSettings,
patchServerSetting,
// Test-only helpers
_setCurrentSessions,
_setViewMode,
+28 -1
View File
@@ -111,7 +111,34 @@
</select>
</div>
</div>
<div class="settings-panel hidden" data-tab="sessions"></div>
<div class="settings-panel hidden" data-tab="sessions">
<div class="settings-field">
<label class="settings-label" for="setting-default-session">Default Session</label>
<select id="setting-default-session" class="settings-select">
<option value="">(none)</option>
</select>
</div>
<div class="settings-field">
<label class="settings-label" for="setting-sort-order">Sort Order</label>
<select id="setting-sort-order" class="settings-select">
<option value="manual">Manual</option>
<option value="alphabetical">Alphabetical</option>
<option value="recent">Recent</option>
</select>
</div>
<div class="settings-field settings-field--column">
<label class="settings-label">Hidden Sessions</label>
<div id="setting-hidden-sessions" class="settings-checkbox-list"></div>
</div>
<div class="settings-field">
<label class="settings-label" for="setting-window-size-largest">Auto-set window size (largest)</label>
<input type="checkbox" id="setting-window-size-largest" class="settings-checkbox" />
</div>
<div class="settings-field">
<label class="settings-label" for="setting-auto-open">Auto-open created sessions</label>
<input type="checkbox" id="setting-auto-open" class="settings-checkbox" checked />
</div>
</div>
<div class="settings-panel hidden" data-tab="notifications"></div>
<div class="settings-panel hidden" data-tab="new-session"></div>
</div>
+32
View File
@@ -1059,6 +1059,38 @@ body {
}
}
/* ============================================================
Sessions tab controls
============================================================ */
.settings-field--column {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.settings-checkbox-list {
display: flex;
flex-direction: column;
gap: 6px;
width: 100%;
}
.settings-checkbox-item {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
color: var(--text);
}
.settings-checkbox {
accent-color: var(--accent);
width: 16px;
height: 16px;
cursor: pointer;
}
/* ============================================================
Responsive overlay sidebar at <960px
============================================================ */