cat / goq
clone

turns a linux box into a private steam streaming appliance: plug in, tap your key, play from another computer

nit: ui changes from UAT

Dami committed c369cdd
commit
c369cddfb8dcee18170deec9f5673050f8a78807
parent
f07125e0907558d4e203fe0a2cf4b426daa6651c
author
Dami <dami.etoile@gmail.com>
9 files changed, +85 -27
Mportal/audio-ui.mjs10+++++-----
Mportal/audio-ui.test.mjs12++++++------
Mportal/index.html2+-
Mportal/input-state.mjs14++++++++++++++
Mportal/input-state.test.mjs20++++++++++++++++++++
Mportal/main.js24+++++++++++++++---------
Mportal/mouse-input-wiring.test.mjs16++++++++++++++++
Mportal/session-controls-layout.test.mjs4+++-
Mportal/style.css10+++++-----
M portal/audio-ui.mjs +5 -5
M portal/audio-ui.test.mjs +6 -6
@@ -54,27 +54,27 @@ test('new audio sessions preserve seeded mute state and isolate mutable state',
5454 assert.notEqual(muted.messageTracker, fresh.messageTracker);5555 });5656 57-test('audio button uses a speaker glyph with a mute action while playing', () => {57+test('audio button uses a text-on label with a mute action while playing', () => {5858 assert.deepEqual(audioButtonPresentation({5959 available: true,6060 muted: false,6161 state: 'playing',6262 detail: 'bounded Opus playback',6363 }), {64- glyph: '๐Ÿ”Š',64+ label: 'vol on',6565 ariaLabel: 'Mute audio. Audio playing. bounded Opus playback',6666 title: 'Audio playing: bounded Opus playback',6767 });6868 });6969 70-test('audio button uses a muted glyph with an unmute action while muted', () => {70+test('audio button uses a text-off label with an unmute action while muted', () => {7171 const presentation = audioButtonPresentation({7272 available: true,7373 muted: true,7474 state: 'playing',7575 detail: 'bounded Opus playback',7676 });77- assert.equal(presentation.glyph, '๐Ÿ”‡');77+ assert.equal(presentation.label, 'vol off');7878 assert.match(presentation.ariaLabel, /^Unmute audio\. Audio muted\./);7979 });8080
@@ -84,13 +84,13 @@ test('audio button distinguishes priming from unavailable', () => {
8484 muted: false,8585 state: 'priming',8686 detail: 'Waiting for bounded Opus prebuffer',87- }).glyph, 'audio ...');87+ }).label, 'vol ...');8888 const unavailable = audioButtonPresentation({8989 available: false,9090 muted: false,9191 state: 'unavailable',9292 detail: 'not connected',9393 });94- assert.equal(unavailable.glyph, '๐Ÿ”‡');94+ assert.equal(unavailable.label, 'vol off');9595 assert.match(unavailable.ariaLabel, /^Audio unavailable\./);9696 });
M portal/index.html +1 -1
M portal/input-state.mjs +14 -0
M portal/input-state.test.mjs +20 -0
M portal/main.js +15 -9
@@ -5,6 +5,7 @@ import {
55 validatePointerSurfaceDimensions,66 validatePointerPositionFeedback,77 browserMouseButtonCode,8+ inputCapabilityLabel,89 } from './input-state.mjs';910 import { createInputRuntime } from './input-runtime.mjs';1011 import { createControlRuntime } from './control-runtime.mjs';
@@ -328,7 +329,7 @@ function updateAudioUI() {
328329 state: session.state,329330 detail: session.stateDetail,330331 });331- toggle.textContent = presentation.glyph;332+ toggle.textContent = presentation.label;332333 toggle.classList.toggle('active', session.available && !session.muted);333334 toggle.classList.toggle('disabled', !session.available);334335 toggle.setAttribute('aria-disabled', session.available ? 'false' : 'true');
@@ -791,14 +792,7 @@ async function toggleControl() {
791792 }792793 793794 function describeInputCapabilities() {794- const accepted = [];795- const capabilities = connectionState.inputCapabilities;796- if (capabilities.relativePointer) accepted.push('relative pointer');797- else if (capabilities.absolutePointer) accepted.push('pointer');798- if (capabilities.keyboard) accepted.push('keyboard');799- if (capabilities.text) accepted.push('text');800- if (capabilities.gamepad) accepted.push('gamepad');801- return accepted.length > 0 ? accepted.join(' + ') : 'view only';795+ return inputCapabilityLabel(connectionState.inputCapabilities);802796 }803797 804798 function pointerInputAvailable() {
@@ -930,6 +924,18 @@ window.addEventListener('mouseup', (e) => {
930924 releaseMouseButton(e);931925 }, { capture: true });932926 927+function suppressLocalClickDuringRelativeControl(e) {928+ if (!controlRuntime.active || !connectionState.inputCapabilities.relativePointer) return;929+ // mousedown/mouseup are forwarded to the host above, but WebKit synthesizes930+ // a separate click at the native cursor's frozen local position. Prevent931+ // that click from reaching the underlying Portal control that acquired the932+ // cursor, where it would immediately toggle controlling mode back off.933+ e.preventDefault();934+ e.stopImmediatePropagation();935+}936+937+window.addEventListener('click', suppressLocalClickDuringRelativeControl, { capture: true });938+933939 window.addEventListener('contextmenu', (e) => {934940 if (!controlRuntime.active || !pointerInputAvailable()) return;935941 if (!connectionState.inputCapabilities.relativePointer && e.target !== canvas) return;
M portal/mouse-input-wiring.test.mjs +16 -0
M portal/session-controls-layout.test.mjs +3 -1
M portal/style.css +5 -5