Merge branch 'dev'
CI/CD / deploy (push) Successful in 2m53s Details

This commit is contained in:
Jeff Emmett 2026-04-09 15:56:40 -04:00
commit b5d07fff1f
2 changed files with 83 additions and 0 deletions

View File

@ -1267,6 +1267,42 @@ export function renderShell(opts: ShellOptions): string {
}
});
tabBar.addEventListener('layers-close-all', () => {
// Track all closed modules so server merge doesn't resurrect them
layers.forEach(l => _closedModuleIds.add(l.moduleId));
// Clean up space layer refs
const token = document.cookie.match(/encryptid_token=([^;]+)/)?.[1];
layers.forEach(l => {
if (l.spaceSlug && l._spaceRefId && token) {
fetch('/api/spaces/' + encodeURIComponent(spaceSlug) + '/nest/' + encodeURIComponent(l._spaceRefId), {
method: 'DELETE',
headers: { 'Authorization': 'Bearer ' + token },
}).catch(() => {});
}
});
// Remove all cached panes
if (tabCache) {
layers.forEach(l => tabCache.removePane(l.moduleId));
tabCache.hideAllPanes();
}
layers = [];
saveTabs();
// Show the dashboard
const dashboard = document.querySelector('rstack-user-dashboard');
if (dashboard) {
dashboard.style.display = '';
if (dashboard.refresh) dashboard.refresh();
if (dashboard.setOpenTabs) dashboard.setOpenTabs([]);
}
const app = document.getElementById('app');
if (app) app.classList.remove('canvas-layout');
tabBar.setAttribute('active', '');
tabBar.setLayers([]);
currentModuleId = '';
var dashUrl = window.location.hostname.endsWith('.rspace.online') ? '/' : '/' + spaceSlug;
history.pushState({ dashboard: true, spaceSlug: spaceSlug }, '', dashUrl);
});
tabBar.addEventListener('layer-reorder', (e) => {
const { layerId, newIndex } = e.detail;
const oldIdx = layers.findIndex(l => l.id === layerId);
@ -1482,6 +1518,9 @@ export function renderShell(opts: ShellOptions): string {
tabBar.addEventListener('layer-close', (e) => {
sync.removeLayer(e.detail.layerId);
});
tabBar.addEventListener('layers-close-all', () => {
sync.getLayers().forEach(l => sync.removeLayer(l.id));
});
tabBar.addEventListener('layer-reorder', (e) => {
const { layerId, newIndex } = e.detail;
const all = sync.getLayers(); // already sorted by order
@ -1822,6 +1861,7 @@ export function renderExternalAppShell(opts: ExternalAppShellOptions): string {
tabBar.addEventListener('layer-switch', (e) => { saveTabs(); window.location.href = window.__rspaceNavUrl(spaceSlug, e.detail.moduleId); });
tabBar.addEventListener('layer-add', (e) => { const { moduleId } = e.detail; if (!layers.find(l => l.moduleId === moduleId)) layers.push(makeLayer(moduleId, layers.length)); saveTabs(); window.location.href = window.__rspaceNavUrl(spaceSlug, moduleId); });
tabBar.addEventListener('layer-close', (e) => { const { layerId } = e.detail; tabBar.removeLayer(layerId); layers = layers.filter(l => l.id !== layerId); saveTabs(); if (layerId === 'layer-' + currentModuleId && layers.length > 0) window.location.href = window.__rspaceNavUrl(spaceSlug, layers[0].moduleId); });
tabBar.addEventListener('layers-close-all', () => { layers = []; tabBar.setLayers([]); tabBar.setAttribute('active', ''); saveTabs(); var dashUrl = window.location.hostname.endsWith('.rspace.online') ? '/' : '/' + spaceSlug; window.location.href = dashUrl; });
tabBar.addEventListener('layer-reorder', (e) => { const { layerId, newIndex } = e.detail; const oldIdx = layers.findIndex(l => l.id === layerId); if (oldIdx === -1 || oldIdx === newIndex) return; const [moved] = layers.splice(oldIdx, 1); layers.splice(newIndex, 0, moved); layers.forEach((l, i) => l.order = i); saveTabs(); tabBar.setLayers(layers); });
}
</script>

View File

@ -15,6 +15,7 @@
* layer-switch fired when user clicks a tab { detail: { layerId, moduleId } }
* layer-add fired when user clicks + to add a layer
* layer-close fired when user closes a tab { detail: { layerId } }
* layers-close-all fired when user clicks close-all button (no detail)
* layer-reorder fired on drag reorder { detail: { layerId, newIndex } }
* view-toggle fired when switching flat/stack view { detail: { mode } }
* flow-select fired when a flow is clicked in stack view { detail: { flowId } }
@ -426,6 +427,7 @@ export class RStackTabBar extends HTMLElement {
</div>
<div class="tab-add-wrap">
<button class="tab-add" id="add-btn" title="Add layer">+</button>
${this.#layers.length > 1 ? `<button class="tab-close-all" id="close-all-btn" title="Close all tabs">✕</button>` : ""}
${this.#addMenuOpen ? this.#renderAddMenu() : ""}
</div>
<div class="tab-actions">
@ -1098,6 +1100,20 @@ export class RStackTabBar extends HTMLElement {
addBtn?.addEventListener("click", openAppSwitcher);
addBtn?.addEventListener("touchend", openAppSwitcher);
// Close-all button
const closeAllBtn = this.#shadow.getElementById("close-all-btn");
if (closeAllBtn) {
const handleCloseAll = (e: Event) => {
e.stopPropagation();
e.preventDefault();
const count = this.#layers.length;
if (!confirm(`Close all ${count} rApp tabs?`)) return;
this.dispatchEvent(new CustomEvent("layers-close-all", { bubbles: true }));
};
closeAllBtn.addEventListener("click", handleCloseAll);
closeAllBtn.addEventListener("touchend", handleCloseAll);
}
// Add menu items — click + touch support
this.#shadow.querySelectorAll<HTMLElement>(".add-menu-item").forEach(item => {
const handleSelect = (e: Event) => {
@ -1613,6 +1629,8 @@ const STYLES = `
/* ── Add button ── */
.tab-add-wrap {
display: flex;
align-items: center;
position: relative;
flex-shrink: 0;
margin-left: 2px;
@ -1642,6 +1660,31 @@ const STYLES = `
background: var(--rs-bg-hover);
color: var(--rs-text-primary);
}
.tab-close-all {
display: flex;
align-items: center;
justify-content: center;
padding: 4px 8px;
min-height: 44px;
border: none;
border-radius: 6px 6px 0 0;
background: transparent;
color: var(--rs-text-muted);
font-size: 0.85rem;
cursor: pointer;
transition: background 0.15s, color 0.15s;
flex-shrink: 0;
-webkit-tap-highlight-color: transparent;
touch-action: manipulation;
white-space: nowrap;
user-select: none;
opacity: 0.6;
}
.tab-close-all:hover, .tab-close-all:active {
background: rgba(239,68,68,0.15);
color: #ef4444;
opacity: 1;
}
/* ── Add menu ── */