From 51770b961ee1f72f83e315e9967636160359ee61 Mon Sep 17 00:00:00 2001 From: Brian Krabach Date: Wed, 15 Apr 2026 21:05:15 -0700 Subject: [PATCH] feat: add hide, unhide, and remove-from-view flyout actions --- muxplex/frontend/app.js | 105 ++++++++++++++++++++++++++++++ muxplex/tests/test_frontend_js.py | 35 ++++++++++ 2 files changed, 140 insertions(+) diff --git a/muxplex/frontend/app.js b/muxplex/frontend/app.js index c898b6f..17e71c7 100644 --- a/muxplex/frontend/app.js +++ b/muxplex/frontend/app.js @@ -1785,6 +1785,111 @@ function _openFlyoutSubmenu(triggerItem, unhideFirst) { }); } +/** + * Hide a session: add to hidden_sessions and remove from ALL views. + * Closes the flyout and re-renders the grid. + */ +function _doHideSession() { + var sessionKey = _flyoutSessionKey; + if (!sessionKey) return; + + var hidden = (_serverSettings && _serverSettings.hidden_sessions) || []; + var views = (_serverSettings && _serverSettings.views) || []; + + // Add to hidden_sessions + var updatedHidden = hidden.slice(); + if (updatedHidden.indexOf(sessionKey) === -1) { + updatedHidden.push(sessionKey); + } + + // Remove from all views (mutual exclusion) + var updatedViews = JSON.parse(JSON.stringify(views)); + for (var i = 0; i < updatedViews.length; i++) { + var sessions = updatedViews[i].sessions || []; + var idx = sessions.indexOf(sessionKey); + if (idx !== -1) sessions.splice(idx, 1); + } + + closeFlyoutMenu(); + + api('PATCH', '/api/settings', { hidden_sessions: updatedHidden, views: updatedViews }) + .then(function() { + if (_serverSettings) { + _serverSettings.hidden_sessions = updatedHidden; + _serverSettings.views = updatedViews; + } + renderGrid(_currentSessions || []); + renderViewDropdown(); + }) + .catch(function(err) { + showToast('Couldn\u2019t save \u2014 try again'); + console.warn('[_doHideSession] PATCH failed:', err); + }); +} + +/** + * Unhide a session: remove from hidden_sessions. + * Closes the flyout and re-renders the grid. + */ +function _doUnhideSession() { + var sessionKey = _flyoutSessionKey; + if (!sessionKey) return; + + var hidden = (_serverSettings && _serverSettings.hidden_sessions) || []; + var idx = hidden.indexOf(sessionKey); + if (idx === -1) { closeFlyoutMenu(); return; } + + var updatedHidden = hidden.slice(); + updatedHidden.splice(idx, 1); + + closeFlyoutMenu(); + + api('PATCH', '/api/settings', { hidden_sessions: updatedHidden }) + .then(function() { + if (_serverSettings) _serverSettings.hidden_sessions = updatedHidden; + renderGrid(_currentSessions || []); + renderViewDropdown(); + }) + .catch(function(err) { + showToast('Couldn\u2019t save \u2014 try again'); + console.warn('[_doUnhideSession] PATCH failed:', err); + }); +} + +/** + * Remove a session from the currently active user view. + * Closes the flyout and re-renders the grid. + */ +function _doRemoveFromView() { + var sessionKey = _flyoutSessionKey; + if (!sessionKey || _activeView === 'all' || _activeView === 'hidden') return; + + var views = (_serverSettings && _serverSettings.views) || []; + var updatedViews = JSON.parse(JSON.stringify(views)); + + // Find the active view and remove the session + for (var i = 0; i < updatedViews.length; i++) { + if (updatedViews[i].name === _activeView) { + var sessions = updatedViews[i].sessions || []; + var idx = sessions.indexOf(sessionKey); + if (idx !== -1) sessions.splice(idx, 1); + break; + } + } + + closeFlyoutMenu(); + + api('PATCH', '/api/settings', { views: updatedViews }) + .then(function() { + if (_serverSettings) _serverSettings.views = updatedViews; + renderGrid(_currentSessions || []); + }) + .catch(function(err) { + showToast('Couldn\u2019t save \u2014 try again'); + console.warn('[_doRemoveFromView] PATCH failed:', err); + }); +} + // ─── Notification permission ──────────────────────────────────────────────── /** diff --git a/muxplex/tests/test_frontend_js.py b/muxplex/tests/test_frontend_js.py index e0e44c6..f0ce336 100644 --- a/muxplex/tests/test_frontend_js.py +++ b/muxplex/tests/test_frontend_js.py @@ -3821,3 +3821,38 @@ def test_submenu_toggles_view_membership() -> None: assert "views" in fn_body and ("PATCH" in fn_body or "api(" in fn_body), ( "_openFlyoutSubmenu must PATCH /api/settings to add/remove session from view" ) + + +# ─── Task 6: Hide/Unhide/Remove actions ────────────────────────────────────── + + +def test_do_hide_session_function_exists() -> None: + """app.js must define a _doHideSession function.""" + assert "function _doHideSession" in _JS, ( + "app.js must contain a _doHideSession function" + ) + + +def test_do_unhide_session_function_exists() -> None: + """app.js must define a _doUnhideSession function.""" + assert "function _doUnhideSession" in _JS, ( + "app.js must contain a _doUnhideSession function" + ) + + +def test_do_remove_from_view_function_exists() -> None: + """app.js must define a _doRemoveFromView function.""" + assert "function _doRemoveFromView" in _JS, ( + "app.js must contain a _doRemoveFromView function" + ) + + +def test_hide_session_removes_from_all_views() -> None: + """_doHideSession must update both hidden_sessions AND views (remove from all views).""" + fn_body = _JS.split("function _doHideSession")[1].split("\nfunction ")[0] + assert "hidden_sessions" in fn_body, ( + "_doHideSession must add session to hidden_sessions" + ) + assert "views" in fn_body, ( + "_doHideSession must remove session from all views (mutual exclusion)" + )