import { api } from '../api.js';
import { showToast } from './toast.js';
import { t, tn } from '../i18n.js';
// Reusable resource-count formatter. Returns localized "1 device" / "N devices"
// / "No devices" based on n. Generic so the same shape can wire users /
// playlists / schedules counts later without refactor - caller supplies the
// i18n key bases.
// keyBase: e.g. 'switcher.devices_count' (looks up _one / _other variants via tn)
// zeroKey: e.g. 'switcher.no_devices' (direct lookup for n === 0)
function formatResourceCount(n, keyBase, zeroKey) {
if (n === undefined || n === null) return '';
if (n === 0) return t(zeroKey);
return tn(keyBase, n);
}
// Admin affordances shown beside a workspace: manage members + rename. Returns
// '' for non-admins. Shared by the single-workspace view and the multi-workspace
// dropdown items so the two never drift - #19: the single view was missing these,
// locking single-workspace users out of org settings (invite users, perms, slug).
function adminIconsHtml(w) {
if (!w.can_admin) return '';
return `
`;
}
// Wire the manage-members + rename buttons within `scope`. `list` resolves a
// workspace id to its object (for the rename modal). stopPropagation so a click
// on an icon never triggers the row's switch handler.
function wireAdminIcons(scope, list) {
scope.querySelectorAll('.workspace-switcher-pencil').forEach(btn => {
btn.addEventListener('click', async (e) => {
e.stopPropagation();
const ws = list.find(w => w.id === btn.dataset.renameId);
if (!ws) return;
scope.classList.remove('open');
const { openWorkspaceRenameModal } = await import('./workspace-rename-modal.js');
openWorkspaceRenameModal(ws);
});
});
scope.querySelectorAll('.workspace-switcher-members').forEach(btn => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
scope.classList.remove('open');
window.location.hash = `#/workspace/${btn.dataset.membersId}/members`;
});
});
}
// Render the workspace switcher inside #workspaceSwitcher based on the
// /api/auth/me response. Three modes:
// - 0 accessible workspaces: muted "No workspace" placeholder
// - 1 accessible workspace: workspace name as static text
// - >1 accessible workspaces: dropdown button + menu with click-to-switch
export function renderWorkspaceSwitcher(me) {
const container = document.getElementById('workspaceSwitcher');
if (!container) return;
const list = Array.isArray(me?.accessible_workspaces) ? me.accessible_workspaces : [];
const currentId = me?.current_workspace_id || null;
if (list.length === 0) {
container.classList.remove('open');
container.innerHTML = `No workspace`;
return;
}
if (list.length === 1) {
// #19: a single workspace still needs its admin affordances (manage members /
// rename + slug). Render the name as before, plus the inline manage icons
// when the user can administer it - no dropdown for one item.
container.classList.remove('open');
const only = list[0];
container.innerHTML = `
${esc(only.name)}
${adminIconsHtml(only)}
`;
wireAdminIcons(container, [only]);
return;
}
// >1: dropdown. Alpha sort by workspace name for MVP (no recently-used yet).
const sorted = [...list].sort((a, b) => a.name.localeCompare(b.name));
const current = sorted.find(w => w.id === currentId) || sorted[0];
// Issue #16: show a type-to-filter search box once the list is big enough to
// be painful to scroll (MSPs run 100+ orgs). Below the threshold a plain list
// is fine. The full list is already loaded from /me, so filtering is client-side.
const SHOW_SEARCH_THRESHOLD = 8;
const showSearch = sorted.length >= SHOW_SEARCH_THRESHOLD;
container.innerHTML = `