feat: add flyout click handler, Add to View submenu with toggle
This commit is contained in:
@@ -1633,6 +1633,158 @@ function _flyoutOutsideClickHandler(e) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delegated click handler for the flyout menu.
|
||||||
|
* Dispatches based on data-action attribute.
|
||||||
|
* @param {MouseEvent} e
|
||||||
|
*/
|
||||||
|
function _handleFlyoutClick(e) {
|
||||||
|
var item = e.target.closest('[data-action]');
|
||||||
|
if (!item) return;
|
||||||
|
|
||||||
|
var action = item.dataset.action;
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case 'add-to-view':
|
||||||
|
case 'unhide-add-to-view':
|
||||||
|
_openFlyoutSubmenu(item, action === 'unhide-add-to-view');
|
||||||
|
break;
|
||||||
|
case 'remove-from-view':
|
||||||
|
_doRemoveFromView();
|
||||||
|
break;
|
||||||
|
case 'hide':
|
||||||
|
_doHideSession();
|
||||||
|
break;
|
||||||
|
case 'unhide':
|
||||||
|
_doUnhideSession();
|
||||||
|
break;
|
||||||
|
case 'kill':
|
||||||
|
_doKillSessionInline(item);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open the "Add to View" submenu next to a flyout menu item.
|
||||||
|
* Lists all user-created views with checkmarks for views the session is already in.
|
||||||
|
* Clicking a view toggles membership immediately via PATCH /api/settings.
|
||||||
|
* The flyout stays open after submenu actions.
|
||||||
|
* @param {HTMLElement} triggerItem - The menu item that triggered the submenu
|
||||||
|
* @param {boolean} unhideFirst - If true, also unhide the session (for "Unhide & Add to View")
|
||||||
|
*/
|
||||||
|
function _openFlyoutSubmenu(triggerItem, unhideFirst) {
|
||||||
|
// Close existing submenu
|
||||||
|
if (_flyoutSubmenuEl) {
|
||||||
|
_flyoutSubmenuEl.remove();
|
||||||
|
_flyoutSubmenuEl = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var views = (_serverSettings && _serverSettings.views) || [];
|
||||||
|
if (views.length === 0) {
|
||||||
|
showToast('No user views. Create one from the header dropdown.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sessionKey = _flyoutSessionKey;
|
||||||
|
var html = '';
|
||||||
|
for (var i = 0; i < views.length; i++) {
|
||||||
|
var v = views[i];
|
||||||
|
var isIn = (v.sessions || []).indexOf(sessionKey) !== -1;
|
||||||
|
html += '<button class="flyout-submenu__item" role="menuitem" data-view-index="' + i + '">';
|
||||||
|
html += '<span class="flyout-submenu__check">' + (isIn ? '\u2713' : '') + '</span>';
|
||||||
|
html += escapeHtml(v.name);
|
||||||
|
html += '</button>';
|
||||||
|
}
|
||||||
|
|
||||||
|
var submenu = document.createElement('div');
|
||||||
|
submenu.className = 'flyout-submenu';
|
||||||
|
submenu.setAttribute('role', 'menu');
|
||||||
|
submenu.innerHTML = html;
|
||||||
|
document.body.appendChild(submenu);
|
||||||
|
_flyoutSubmenuEl = submenu;
|
||||||
|
|
||||||
|
// Position to the right of the trigger item (or left if no space)
|
||||||
|
if (_flyoutMenuEl) {
|
||||||
|
var menuRect = _flyoutMenuEl.getBoundingClientRect();
|
||||||
|
var subWidth = submenu.offsetWidth;
|
||||||
|
var subHeight = submenu.offsetHeight;
|
||||||
|
var itemRect = triggerItem.getBoundingClientRect();
|
||||||
|
|
||||||
|
var left = menuRect.right + 4;
|
||||||
|
if (left + subWidth > window.innerWidth - 8) {
|
||||||
|
left = menuRect.left - subWidth - 4;
|
||||||
|
}
|
||||||
|
var top = itemRect.top;
|
||||||
|
if (top + subHeight > window.innerHeight - 8) {
|
||||||
|
top = window.innerHeight - subHeight - 8;
|
||||||
|
}
|
||||||
|
if (top < 8) top = 8;
|
||||||
|
|
||||||
|
submenu.style.top = top + 'px';
|
||||||
|
submenu.style.left = left + 'px';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Click handler — toggle view membership via PATCH /api/settings
|
||||||
|
submenu.addEventListener('click', function(e) {
|
||||||
|
var btn = e.target.closest('[data-view-index]');
|
||||||
|
if (!btn) return;
|
||||||
|
var idx = parseInt(btn.dataset.viewIndex, 10);
|
||||||
|
|
||||||
|
var updatedViews = JSON.parse(JSON.stringify((_serverSettings && _serverSettings.views) || []));
|
||||||
|
var view = updatedViews[idx];
|
||||||
|
if (!view) return;
|
||||||
|
|
||||||
|
var sessions = view.sessions || [];
|
||||||
|
var pos = sessions.indexOf(sessionKey);
|
||||||
|
if (pos !== -1) {
|
||||||
|
sessions.splice(pos, 1);
|
||||||
|
} else {
|
||||||
|
sessions.push(sessionKey);
|
||||||
|
}
|
||||||
|
view.sessions = sessions;
|
||||||
|
|
||||||
|
var patch = { views: updatedViews };
|
||||||
|
|
||||||
|
if (unhideFirst) {
|
||||||
|
var hidden = (_serverSettings && _serverSettings.hidden_sessions) || [];
|
||||||
|
var hiddenIdx = hidden.indexOf(sessionKey);
|
||||||
|
if (hiddenIdx !== -1) {
|
||||||
|
var updatedHidden = hidden.slice();
|
||||||
|
updatedHidden.splice(hiddenIdx, 1);
|
||||||
|
patch.hidden_sessions = updatedHidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
api('PATCH', '/api/settings', patch)
|
||||||
|
.then(function() {
|
||||||
|
if (_serverSettings) {
|
||||||
|
_serverSettings.views = updatedViews;
|
||||||
|
if (patch.hidden_sessions) _serverSettings.hidden_sessions = patch.hidden_sessions;
|
||||||
|
}
|
||||||
|
// Update checkmarks in submenu
|
||||||
|
if (_flyoutSubmenuEl) {
|
||||||
|
var checkItems = _flyoutSubmenuEl.querySelectorAll('[data-view-index]');
|
||||||
|
for (var ci = 0; ci < checkItems.length; ci++) {
|
||||||
|
var vi = parseInt(checkItems[ci].dataset.viewIndex, 10);
|
||||||
|
var checkEl = checkItems[ci].querySelector('.flyout-submenu__check');
|
||||||
|
if (checkEl && updatedViews[vi]) {
|
||||||
|
checkEl.textContent = (updatedViews[vi].sessions || []).indexOf(sessionKey) !== -1 ? '\u2713' : '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (unhideFirst) {
|
||||||
|
renderGrid(_currentSessions || []);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
showToast('Couldn\u2019t save \u2014 try again');
|
||||||
|
console.warn('[_openFlyoutSubmenu] PATCH failed:', err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// ─── Notification permission ────────────────────────────────────────────────
|
// ─── Notification permission ────────────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3788,3 +3788,36 @@ def test_build_flyout_uses_menu_map() -> None:
|
|||||||
assert "FLYOUT_MENU_MAP" in fn_body, (
|
assert "FLYOUT_MENU_MAP" in fn_body, (
|
||||||
"_buildFlyoutMenuItems must reference FLYOUT_MENU_MAP (data-driven, not if/else)"
|
"_buildFlyoutMenuItems must reference FLYOUT_MENU_MAP (data-driven, not if/else)"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ─── Task 5: Flyout click handler + Add to View submenu ──────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def test_handle_flyout_click_function_exists() -> None:
|
||||||
|
"""app.js must define a _handleFlyoutClick function."""
|
||||||
|
assert "function _handleFlyoutClick" in _JS, (
|
||||||
|
"app.js must contain a _handleFlyoutClick function"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_handle_flyout_click_dispatches_actions() -> None:
|
||||||
|
"""_handleFlyoutClick must check data-action for dispatching."""
|
||||||
|
fn_body = _JS.split("function _handleFlyoutClick")[1].split("\nfunction ")[0]
|
||||||
|
assert "data-action" in fn_body or "dataset.action" in fn_body, (
|
||||||
|
"_handleFlyoutClick must read data-action from the clicked element"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_open_flyout_submenu_function_exists() -> None:
|
||||||
|
"""app.js must define a _openFlyoutSubmenu function."""
|
||||||
|
assert "function _openFlyoutSubmenu" in _JS, (
|
||||||
|
"app.js must contain a _openFlyoutSubmenu function"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_submenu_toggles_view_membership() -> None:
|
||||||
|
"""_openFlyoutSubmenu must PATCH /api/settings to toggle view membership."""
|
||||||
|
fn_body = _JS.split("function _openFlyoutSubmenu")[1].split("\nfunction ")[0]
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user