fix: remove duplicate mute/deafen buttons from user bar, disable browser context menu

- Remove microphone and headphone buttons from UserBar (already in VoiceWidget)
- Disable default browser right-click menu globally so only custom context menus show
This commit is contained in:
jevb
2026-03-18 11:33:59 +01:00
parent f36fb1ffdc
commit e1659fcfd2
4 changed files with 13 additions and 80 deletions
+2 -25
View File
@@ -8,10 +8,7 @@ import type { MountableComponent } from "@lib/safe-render";
import { authStore } from "@stores/auth.store";
import { openSettings } from "@stores/ui.store";
export interface UserBarOptions {
readonly onMuteToggle?: () => void;
readonly onDeafenToggle?: () => void;
}
export type UserBarOptions = Record<string, never>;
export function createUserBar(options?: UserBarOptions): MountableComponent {
const ac = new AbortController();
@@ -63,32 +60,12 @@ export function createUserBar(options?: UserBarOptions): MountableComponent {
const buttons = createElement("div", { class: "ub-controls" });
const muteBtn = createElement(
"button",
{ title: "Mute", "aria-label": "Mute" },
"\uD83C\uDFA4",
);
const deafenBtn = createElement(
"button",
{ title: "Deafen", "aria-label": "Deafen" },
"\uD83C\uDFA7",
);
const settingsBtn = createElement(
"button",
{ title: "Settings", "aria-label": "Settings" },
"\u2699",
);
if (options?.onMuteToggle) {
muteBtn.addEventListener("click", options.onMuteToggle, { signal: ac.signal });
}
if (options?.onDeafenToggle) {
deafenBtn.addEventListener("click", options.onDeafenToggle, { signal: ac.signal });
}
settingsBtn.addEventListener(
"click",
() => {
@@ -97,7 +74,7 @@ export function createUserBar(options?: UserBarOptions): MountableComponent {
{ signal: ac.signal },
);
appendChildren(buttons, muteBtn, deafenBtn, settingsBtn);
buttons.appendChild(settingsBtn);
appendChildren(root, avatarEl, info, buttons);
// Initial render
+7
View File
@@ -27,6 +27,13 @@ import type { CertTofuEvent } from "@lib/ws";
const log = createLogger("main");
// Disable the default browser context menu globally.
// Custom context menus (e.g. channel edit/delete) call e.preventDefault()
// themselves before the event reaches this handler.
document.addEventListener("contextmenu", (e) => {
e.preventDefault();
});
// Install global error handlers first
installGlobalErrorHandlers();
+1 -16
View File
@@ -561,22 +561,7 @@ export function createMainPage(options: MainPageOptions): MountableComponent {
// User bar
const userBarSlot = createElement("div", {});
const userBar = createUserBar({
onMuteToggle: () => {
if (voiceStore.getState().currentChannelId === null) return;
if (!limiters.voice.tryConsume()) return;
const next = !voiceStore.getState().localMuted;
voiceSessionSetMuted(next);
ws.send({ type: "voice_mute", payload: { muted: next } });
},
onDeafenToggle: () => {
if (voiceStore.getState().currentChannelId === null) return;
if (!limiters.voice.tryConsume()) return;
const next = !voiceStore.getState().localDeafened;
voiceSessionSetDeafened(next);
ws.send({ type: "voice_deafen", payload: { deafened: next } });
},
});
const userBar = createUserBar();
userBar.mount(userBarSlot);
children.push(userBar);
sidebarWrapper.appendChild(userBarSlot);
@@ -101,49 +101,13 @@ describe("UserBar", () => {
expect(openSettings).toHaveBeenCalledOnce();
});
it("calls onMuteToggle when mute button clicked", () => {
setAuthState({ username: "alice" }, true);
const onMuteToggle = vi.fn();
comp = createUserBar({ onMuteToggle });
comp.mount(container);
const muteBtn = container.querySelector('[title="Mute"]') as HTMLButtonElement;
muteBtn.click();
expect(onMuteToggle).toHaveBeenCalledOnce();
});
it("calls onDeafenToggle when deafen button clicked", () => {
setAuthState({ username: "alice" }, true);
const onDeafenToggle = vi.fn();
comp = createUserBar({ onDeafenToggle });
comp.mount(container);
const deafenBtn = container.querySelector('[title="Deafen"]') as HTMLButtonElement;
deafenBtn.click();
expect(onDeafenToggle).toHaveBeenCalledOnce();
});
it("does not throw when mute/deafen clicked without callbacks", () => {
it("does not render mute or deafen buttons", () => {
setAuthState({ username: "alice" }, true);
comp = createUserBar();
comp.mount(container);
const muteBtn = container.querySelector('[title="Mute"]') as HTMLButtonElement;
const deafenBtn = container.querySelector('[title="Deafen"]') as HTMLButtonElement;
expect(() => muteBtn.click()).not.toThrow();
expect(() => deafenBtn.click()).not.toThrow();
});
it("stops responding to clicks after destroy", () => {
setAuthState({ username: "alice" }, true);
const onMuteToggle = vi.fn();
comp = createUserBar({ onMuteToggle });
comp.mount(container);
const muteBtn = container.querySelector('[title="Mute"]') as HTMLButtonElement;
comp.destroy?.();
muteBtn.click();
expect(onMuteToggle).not.toHaveBeenCalled();
expect(container.querySelector('[title="Mute"]')).toBeNull();
expect(container.querySelector('[title="Deafen"]')).toBeNull();
});
it("destroy removes DOM and unsubscribes", () => {