mirror of
https://github.com/Shik3i/KoalaSync.git
synced 2026-07-26 12:08:15 +00:00
feat(co-host): popup UI — promote/demote, controller badges, controller-aware lock
- updateHostControlUI takes a state object now; role badge shows Host / Controller / Guest; remote-control lock uses amController (a co-host can drive, so their buttons aren't locked). - Peer list: Host/Controller badges and, for the owner, a per-peer Give control / Revoke button (SET_PEER_ROLE). Shown only when the feature is active and the room is host-only or the viewer is the owner — no role noise for guests in normal rooms. Hidden entirely against a relay without the co-host capability. - i18n: BADGE_CONTROLLER, BTN_GIVE_CONTROL, BTN_REVOKE_CONTROL across all 15 locales. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -50,6 +50,9 @@
|
||||
"NOTICE_HOST_CONTROLS": "Der Host steuert die Wiedergabe für alle.",
|
||||
"BADGE_HOST": "Host",
|
||||
"BADGE_GUEST": "Gast",
|
||||
"BADGE_CONTROLLER": "Controller",
|
||||
"BTN_GIVE_CONTROL": "Steuerung geben",
|
||||
"BTN_REVOKE_CONTROL": "Entziehen",
|
||||
"BADGE_DESYNCED": "Solo",
|
||||
"TOOLTIP_PEER_DESYNCED": "Schaut alleine — ignoriert die Befehle des Hosts",
|
||||
"HCM_DIALOG_TITLE": "KoalaSync · Der Host steuert diesen Raum",
|
||||
|
||||
@@ -50,6 +50,9 @@
|
||||
"NOTICE_HOST_CONTROLS": "The host controls playback for everyone.",
|
||||
"BADGE_HOST": "Host",
|
||||
"BADGE_GUEST": "Guest",
|
||||
"BADGE_CONTROLLER": "Controller",
|
||||
"BTN_GIVE_CONTROL": "Give control",
|
||||
"BTN_REVOKE_CONTROL": "Revoke",
|
||||
"BADGE_DESYNCED": "Solo",
|
||||
"TOOLTIP_PEER_DESYNCED": "Watching on their own — host's commands are ignored",
|
||||
"HCM_DIALOG_TITLE": "KoalaSync · Host controls this room",
|
||||
|
||||
@@ -50,6 +50,9 @@
|
||||
"NOTICE_HOST_CONTROLS": "El anfitrión controla la reproducción para todos.",
|
||||
"BADGE_HOST": "Anfitrión",
|
||||
"BADGE_GUEST": "Invitado",
|
||||
"BADGE_CONTROLLER": "Controlador",
|
||||
"BTN_GIVE_CONTROL": "Dar control",
|
||||
"BTN_REVOKE_CONTROL": "Quitar",
|
||||
"BADGE_DESYNCED": "Solo",
|
||||
"TOOLTIP_PEER_DESYNCED": "Viendo por su cuenta — ignora los comandos del anfitrión",
|
||||
"HCM_DIALOG_TITLE": "KoalaSync · El anfitrión controla esta sala",
|
||||
|
||||
@@ -50,6 +50,9 @@
|
||||
"NOTICE_HOST_CONTROLS": "L'hôte contrôle la lecture pour tout le monde.",
|
||||
"BADGE_HOST": "Hôte",
|
||||
"BADGE_GUEST": "Invité",
|
||||
"BADGE_CONTROLLER": "Contrôleur",
|
||||
"BTN_GIVE_CONTROL": "Donner le contrôle",
|
||||
"BTN_REVOKE_CONTROL": "Retirer",
|
||||
"BADGE_DESYNCED": "Solo",
|
||||
"TOOLTIP_PEER_DESYNCED": "Regarde de son côté — ignore les commandes de l'hôte",
|
||||
"HCM_DIALOG_TITLE": "KoalaSync · L'hôte contrôle ce salon",
|
||||
|
||||
@@ -50,6 +50,9 @@
|
||||
"NOTICE_HOST_CONTROLS": "L'host controlla la riproduzione per tutti.",
|
||||
"BADGE_HOST": "Host",
|
||||
"BADGE_GUEST": "Ospite",
|
||||
"BADGE_CONTROLLER": "Controller",
|
||||
"BTN_GIVE_CONTROL": "Dai il controllo",
|
||||
"BTN_REVOKE_CONTROL": "Revoca",
|
||||
"BADGE_DESYNCED": "Solo",
|
||||
"TOOLTIP_PEER_DESYNCED": "Guarda per conto proprio — ignora i comandi dell'host",
|
||||
"HCM_DIALOG_TITLE": "KoalaSync · L'host controlla questa stanza",
|
||||
|
||||
@@ -50,6 +50,9 @@
|
||||
"NOTICE_HOST_CONTROLS": "ホストが全員の再生を操作します。",
|
||||
"BADGE_HOST": "ホスト",
|
||||
"BADGE_GUEST": "ゲスト",
|
||||
"BADGE_CONTROLLER": "操作権あり",
|
||||
"BTN_GIVE_CONTROL": "操作権を付与",
|
||||
"BTN_REVOKE_CONTROL": "取り消す",
|
||||
"BADGE_DESYNCED": "ソロ",
|
||||
"TOOLTIP_PEER_DESYNCED": "単独で視聴中 — ホストのコマンドを無視しています",
|
||||
"HCM_DIALOG_TITLE": "KoalaSync · ホストがこのルームを操作中",
|
||||
|
||||
@@ -50,6 +50,9 @@
|
||||
"NOTICE_HOST_CONTROLS": "호스트가 모두의 재생을 제어합니다.",
|
||||
"BADGE_HOST": "호스트",
|
||||
"BADGE_GUEST": "게스트",
|
||||
"BADGE_CONTROLLER": "제어자",
|
||||
"BTN_GIVE_CONTROL": "제어 권한 부여",
|
||||
"BTN_REVOKE_CONTROL": "취소",
|
||||
"BADGE_DESYNCED": "단독",
|
||||
"TOOLTIP_PEER_DESYNCED": "혼자 시청 중 — 호스트 명령을 무시합니다",
|
||||
"HCM_DIALOG_TITLE": "KoalaSync · 호스트가 이 방을 제어합니다",
|
||||
|
||||
@@ -50,6 +50,9 @@
|
||||
"NOTICE_HOST_CONTROLS": "De host bedient de weergave voor iedereen.",
|
||||
"BADGE_HOST": "Host",
|
||||
"BADGE_GUEST": "Gast",
|
||||
"BADGE_CONTROLLER": "Bediener",
|
||||
"BTN_GIVE_CONTROL": "Bediening geven",
|
||||
"BTN_REVOKE_CONTROL": "Intrekken",
|
||||
"BADGE_DESYNCED": "Solo",
|
||||
"TOOLTIP_PEER_DESYNCED": "Zelfstandig aan het kijken — negeert de commando's van de host",
|
||||
"HCM_DIALOG_TITLE": "KoalaSync · De host bedient deze kamer",
|
||||
|
||||
@@ -50,6 +50,9 @@
|
||||
"NOTICE_HOST_CONTROLS": "Host steruje odtwarzaniem dla wszystkich.",
|
||||
"BADGE_HOST": "Host",
|
||||
"BADGE_GUEST": "Gość",
|
||||
"BADGE_CONTROLLER": "Sterujący",
|
||||
"BTN_GIVE_CONTROL": "Daj sterowanie",
|
||||
"BTN_REVOKE_CONTROL": "Odbierz",
|
||||
"BADGE_DESYNCED": "Solo",
|
||||
"TOOLTIP_PEER_DESYNCED": "Ogląda samodzielnie — ignoruje polecenia hosta",
|
||||
"HCM_DIALOG_TITLE": "KoalaSync · Host steruje tym pokojem",
|
||||
|
||||
@@ -50,6 +50,9 @@
|
||||
"NOTICE_HOST_CONTROLS": "O anfitrião controla a reprodução para todos.",
|
||||
"BADGE_HOST": "Anfitrião",
|
||||
"BADGE_GUEST": "Convidado",
|
||||
"BADGE_CONTROLLER": "Controlador",
|
||||
"BTN_GIVE_CONTROL": "Dar controle",
|
||||
"BTN_REVOKE_CONTROL": "Revogar",
|
||||
"BADGE_DESYNCED": "Solo",
|
||||
"TOOLTIP_PEER_DESYNCED": "Assistindo por conta própria — ignora os comandos do anfitrião",
|
||||
"HCM_DIALOG_TITLE": "KoalaSync · O anfitrião controla esta sala",
|
||||
|
||||
@@ -50,6 +50,9 @@
|
||||
"NOTICE_HOST_CONTROLS": "O anfitrião controla a reprodução para todos.",
|
||||
"BADGE_HOST": "Anfitrião",
|
||||
"BADGE_GUEST": "Convidado",
|
||||
"BADGE_CONTROLLER": "Controlador",
|
||||
"BTN_GIVE_CONTROL": "Dar controlo",
|
||||
"BTN_REVOKE_CONTROL": "Revogar",
|
||||
"BADGE_DESYNCED": "Solo",
|
||||
"TOOLTIP_PEER_DESYNCED": "A ver por conta própria — ignora os comandos do anfitrião",
|
||||
"HCM_DIALOG_TITLE": "KoalaSync · O anfitrião controla esta sala",
|
||||
|
||||
@@ -50,6 +50,9 @@
|
||||
"NOTICE_HOST_CONTROLS": "Ведущий управляет воспроизведением для всех.",
|
||||
"BADGE_HOST": "Ведущий",
|
||||
"BADGE_GUEST": "Гость",
|
||||
"BADGE_CONTROLLER": "Управляющий",
|
||||
"BTN_GIVE_CONTROL": "Дать управление",
|
||||
"BTN_REVOKE_CONTROL": "Отозвать",
|
||||
"BADGE_DESYNCED": "Соло",
|
||||
"TOOLTIP_PEER_DESYNCED": "Смотрит отдельно — игнорирует команды ведущего",
|
||||
"HCM_DIALOG_TITLE": "KoalaSync · Комнатой управляет ведущий",
|
||||
|
||||
@@ -50,6 +50,9 @@
|
||||
"NOTICE_HOST_CONTROLS": "Oynatmayı herkes için sunucu kontrol eder.",
|
||||
"BADGE_HOST": "Sunucu",
|
||||
"BADGE_GUEST": "Misafir",
|
||||
"BADGE_CONTROLLER": "Kontrolcü",
|
||||
"BTN_GIVE_CONTROL": "Kontrol ver",
|
||||
"BTN_REVOKE_CONTROL": "Geri al",
|
||||
"BADGE_DESYNCED": "Solo",
|
||||
"TOOLTIP_PEER_DESYNCED": "Tek başına izliyor — sunucu komutlarını yok sayar",
|
||||
"HCM_DIALOG_TITLE": "KoalaSync · Bu odayı sunucu kontrol ediyor",
|
||||
|
||||
@@ -50,6 +50,9 @@
|
||||
"NOTICE_HOST_CONTROLS": "Ведучий керує відтворенням для всіх.",
|
||||
"BADGE_HOST": "Ведучий",
|
||||
"BADGE_GUEST": "Гість",
|
||||
"BADGE_CONTROLLER": "Керуючий",
|
||||
"BTN_GIVE_CONTROL": "Дати керування",
|
||||
"BTN_REVOKE_CONTROL": "Відкликати",
|
||||
"BADGE_DESYNCED": "Соло",
|
||||
"TOOLTIP_PEER_DESYNCED": "Дивиться окремо — ігнорує команди ведучого",
|
||||
"HCM_DIALOG_TITLE": "KoalaSync · Кімнатою керує ведучий",
|
||||
|
||||
@@ -50,6 +50,9 @@
|
||||
"NOTICE_HOST_CONTROLS": "由主持人为所有人控制播放。",
|
||||
"BADGE_HOST": "主持人",
|
||||
"BADGE_GUEST": "访客",
|
||||
"BADGE_CONTROLLER": "控制者",
|
||||
"BTN_GIVE_CONTROL": "授予控制权",
|
||||
"BTN_REVOKE_CONTROL": "撤销",
|
||||
"BADGE_DESYNCED": "单独",
|
||||
"TOOLTIP_PEER_DESYNCED": "单独观看中 — 忽略主持人的命令",
|
||||
"HCM_DIALOG_TITLE": "KoalaSync · 主持人控制本房间",
|
||||
|
||||
+65
-20
@@ -249,7 +249,7 @@ async function init() {
|
||||
updatePingDisplay(res.ping);
|
||||
updatePeerList(res.peers);
|
||||
lastKnownPeers = res.peers || [];
|
||||
updateHostControlUI(res.controlMode, res.amHost, res.hostControlSupported, res.status === 'connected');
|
||||
updateHostControlUI({ controlMode: res.controlMode, amHost: res.amHost, amController: res.amController, controllers: res.controllers, hostPeerId: res.hostPeerId, hostControlSupported: res.hostControlSupported, coHostSupported: res.coHostSupported, inRoom: res.status === 'connected' });
|
||||
if (res.lastActionState) updateLastActionUI(res.lastActionState, res.peers);
|
||||
|
||||
// If user has a room configured but background is not connected (disconnected or idle),
|
||||
@@ -313,19 +313,37 @@ function toggleUIState(inRoom) {
|
||||
// True when we're a guest in a host-only room → remote-control buttons are locked.
|
||||
let hcmGuestLocked = false;
|
||||
|
||||
function updateHostControlUI(controlMode, amHost, hostControlSupported, inRoom) {
|
||||
// Co-Host state mirrored for the peer-list renderer (promote/demote + role badges).
|
||||
let hcmAmOwner = false;
|
||||
let hcmControllers = [];
|
||||
let hcmCoHostSupported = false;
|
||||
let hcmOwnerPeerId = null;
|
||||
let hcmRoomIsHostOnly = false;
|
||||
|
||||
function updateHostControlUI(state) {
|
||||
const s = state || {};
|
||||
const controlMode = s.controlMode;
|
||||
const amHost = !!s.amHost;
|
||||
const amController = !!s.amController;
|
||||
const hostOnly = controlMode === 'host-only';
|
||||
|
||||
// Stash for the peer-list renderer, then force it to re-render with new roles.
|
||||
hcmAmOwner = amHost;
|
||||
hcmControllers = Array.isArray(s.controllers) ? s.controllers : [];
|
||||
hcmCoHostSupported = !!s.coHostSupported;
|
||||
hcmOwnerPeerId = s.hostPeerId || null;
|
||||
hcmRoomIsHostOnly = hostOnly;
|
||||
lastPeersJson = '';
|
||||
if (activePeers) updatePeerList(activePeers);
|
||||
|
||||
const card = elements.hostControlCard;
|
||||
if (!card) return;
|
||||
const hostOnly = controlMode === 'host-only';
|
||||
// Explicit capability advertised by the relay in ROOM_DATA. Against an older
|
||||
// relay it's false/absent → the feature is unavailable, so hide the card
|
||||
// entirely instead of showing a misleading "Guest".
|
||||
const serverSupportsHostControl = !!hostControlSupported;
|
||||
// Only show the card when it's actually meaningful:
|
||||
// - host: always (so they can enable/disable host-only)
|
||||
// - guest: only while host-only is active (explains why they can't control)
|
||||
// A guest in a normal "everyone" room sees nothing — no confusing noise.
|
||||
const show = inRoom && serverSupportsHostControl && (amHost || hostOnly);
|
||||
// Explicit capability advertised by the relay. Against an older relay it's
|
||||
// false/absent → feature unavailable, so hide the card instead of misleading UI.
|
||||
const serverSupportsHostControl = !!s.hostControlSupported;
|
||||
// Show the card only when meaningful: owner always (to manage); a non-controller
|
||||
// guest only while host-only is active. A guest in a normal room sees nothing.
|
||||
const show = s.inRoom && serverSupportsHostControl && (amHost || hostOnly);
|
||||
if (!show) {
|
||||
card.style.display = 'none';
|
||||
hcmGuestLocked = false;
|
||||
@@ -334,16 +352,18 @@ function updateHostControlUI(controlMode, amHost, hostControlSupported, inRoom)
|
||||
}
|
||||
card.style.display = 'block';
|
||||
if (elements.hostRoleBadge) {
|
||||
elements.hostRoleBadge.textContent = amHost ? (getMessage('BADGE_HOST') || 'Host') : (getMessage('BADGE_GUEST') || 'Guest');
|
||||
elements.hostRoleBadge.style.background = amHost ? 'var(--accent)' : 'var(--text-muted)';
|
||||
const role = amHost ? (getMessage('BADGE_HOST') || 'Host')
|
||||
: amController ? (getMessage('BADGE_CONTROLLER') || 'Controller')
|
||||
: (getMessage('BADGE_GUEST') || 'Guest');
|
||||
elements.hostRoleBadge.textContent = role;
|
||||
elements.hostRoleBadge.style.background = (amHost || amController) ? 'var(--accent)' : 'var(--text-muted)';
|
||||
}
|
||||
if (elements.hostControlToggleRow) elements.hostControlToggleRow.style.display = amHost ? 'flex' : 'none';
|
||||
if (elements.hostControlToggle) elements.hostControlToggle.checked = hostOnly;
|
||||
if (elements.hostControlGuestNote) elements.hostControlGuestNote.style.display = (!amHost && hostOnly) ? 'block' : 'none';
|
||||
if (elements.hostControlGuestNote) elements.hostControlGuestNote.style.display = (!amController && hostOnly) ? 'block' : 'none';
|
||||
|
||||
// A guest in host-only mode can't drive the room → lock the remote controls so
|
||||
// clicks don't silently get gated (and leave the button stuck).
|
||||
hcmGuestLocked = (!amHost && hostOnly);
|
||||
// Only a non-controller is locked out of the remote controls.
|
||||
hcmGuestLocked = (!amController && hostOnly);
|
||||
setRemoteControlsLocked(hcmGuestLocked);
|
||||
}
|
||||
|
||||
@@ -676,6 +696,31 @@ function updatePeerList(peers) {
|
||||
header.appendChild(solo);
|
||||
}
|
||||
|
||||
// Co-Host: role badges + the owner's promote/demote control. Shown when the
|
||||
// feature is active and either the room is host-only or we're the owner
|
||||
// (so a guest in a normal room sees no role noise).
|
||||
const showRoles = !!hcmOwnerPeerId && (hcmRoomIsHostOnly || hcmAmOwner);
|
||||
if (showRoles) {
|
||||
const isOwner = pId === hcmOwnerPeerId;
|
||||
const isController = !isOwner && hcmControllers.includes(pId);
|
||||
if (isOwner || isController) {
|
||||
const roleBadge = document.createElement('span');
|
||||
roleBadge.style.cssText = 'font-size:10px; color:#fff; background:var(--accent); padding:2px 6px; border-radius:6px; font-weight:600;';
|
||||
roleBadge.textContent = isOwner ? (getMessage('BADGE_HOST') || 'Host') : (getMessage('BADGE_CONTROLLER') || 'Controller');
|
||||
header.appendChild(roleBadge);
|
||||
}
|
||||
// Owner can promote/demote any non-owner peer.
|
||||
if (hcmAmOwner && hcmCoHostSupported && !isOwner) {
|
||||
const btn = document.createElement('button');
|
||||
btn.style.cssText = 'font-size:10px; padding:2px 8px; border-radius:6px; cursor:pointer; border:1px solid var(--accent); background:transparent; color:var(--accent); white-space:nowrap;';
|
||||
btn.textContent = isController ? (getMessage('BTN_REVOKE_CONTROL') || 'Revoke') : (getMessage('BTN_GIVE_CONTROL') || 'Give control');
|
||||
btn.addEventListener('click', () => {
|
||||
chrome.runtime.sendMessage({ type: 'SET_PEER_ROLE', peerId: pId, controller: !isController }, () => {});
|
||||
});
|
||||
header.appendChild(btn);
|
||||
}
|
||||
}
|
||||
|
||||
peerItem.appendChild(header);
|
||||
|
||||
// Media Info
|
||||
@@ -1743,7 +1788,7 @@ chrome.runtime.onMessage.addListener((msg) => {
|
||||
if (msg.peers) detectPeerChanges(msg.peers);
|
||||
} else if (msg.type === 'CONTROL_MODE') {
|
||||
const inRoom = elements.sectionActive && elements.sectionActive.style.display === 'block';
|
||||
updateHostControlUI(msg.controlMode, msg.amHost, msg.hostControlSupported, inRoom);
|
||||
updateHostControlUI({ controlMode: msg.controlMode, amHost: msg.amHost, amController: msg.amController, controllers: msg.controllers, hostPeerId: msg.hostPeerId, hostControlSupported: msg.hostControlSupported, coHostSupported: msg.coHostSupported, inRoom });
|
||||
} else if (msg.type === 'CONNECTION_STATUS') {
|
||||
if (msg.status === 'connected' || msg.status === 'disconnected') {
|
||||
if (joinBtnTimeout) { clearTimeout(joinBtnTimeout); joinBtnTimeout = null; }
|
||||
@@ -1761,7 +1806,7 @@ chrome.runtime.onMessage.addListener((msg) => {
|
||||
if (res.peers) updatePeerList(res.peers);
|
||||
if (res.lastActionState) updateLastActionUI(res.lastActionState, res.peers);
|
||||
updatePingDisplay(res.ping);
|
||||
updateHostControlUI(res.controlMode, res.amHost, res.hostControlSupported, true);
|
||||
updateHostControlUI({ controlMode: res.controlMode, amHost: res.amHost, amController: res.amController, controllers: res.controllers, hostPeerId: res.hostPeerId, hostControlSupported: res.hostControlSupported, coHostSupported: res.coHostSupported, inRoom: true });
|
||||
});
|
||||
}
|
||||
if (msg.status === 'disconnected') {
|
||||
|
||||
Reference in New Issue
Block a user