From 0beded859321c5db8730635652fc5b0c3715e609 Mon Sep 17 00:00:00 2001 From: Max Goodhart Date: Tue, 11 Aug 2020 01:00:02 -0700 Subject: [PATCH] Add view swap functionality --- README.md | 1 + src/static/exchange-alt-solid.svg | 1 + src/web/control.js | 111 ++++++++++++++++++++++++------ 3 files changed, 93 insertions(+), 20 deletions(-) create mode 100644 src/static/exchange-alt-solid.svg diff --git a/README.md b/README.md index 3d01413..8a22ae6 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ The following hotkeys are available with the "control" webpage focused: - **alt+[1...9]**: Listen to the numbered stream - **alt+shift+[1...9]**: Toggle blur on the numbered stream +- **alt+s**: Select the currently focused stream box to be swapped - **alt+c**: Activate [Streamdelay](https://github.com/chromakode/streamdelay) censor mode - **alt+shift+c**: Deactivate [Streamdelay](https://github.com/chromakode/streamdelay) censor mode diff --git a/src/static/exchange-alt-solid.svg b/src/static/exchange-alt-solid.svg new file mode 100644 index 0000000..03af661 --- /dev/null +++ b/src/static/exchange-alt-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/web/control.js b/src/web/control.js index a7a0d85..256fffa 100644 --- a/src/web/control.js +++ b/src/web/control.js @@ -21,6 +21,7 @@ import { idxInBox } from '../geometry' import SoundIcon from '../static/volume-up-solid.svg' import NoVideoIcon from '../static/video-slash-solid.svg' import ReloadIcon from '../static/redo-alt-solid.svg' +import SwapIcon from '../static/exchange-alt-solid.svg' import LifeRingIcon from '../static/life-ring-regular.svg' import WindowIcon from '../static/window-maximize-regular.svg' import { idColor } from './colors' @@ -137,6 +138,7 @@ function useStreamwallConnection(wsEndpoint) { state, isListening, isBlurred, + spaces: pos.spaces, }) } } @@ -214,26 +216,72 @@ function App({ wsEndpoint }) { setShowDebug(ev.target.checked) }) + const [swapStartIdx, setSwapStartIdx] = useState() + const handleSwapView = useCallback( + (idx) => { + if (!stateIdxMap.has(idx)) { + return + } + // Deselect the input so the contents aren't persisted by GridInput's `editingValue` + document.activeElement.blur() + setSwapStartIdx(idx) + }, + [stateIdxMap], + ) + const handleSwap = useCallback( + (toIdx) => { + if (swapStartIdx === undefined) { + return + } + stateDoc.transact(() => { + const viewsState = stateDoc.getMap('views') + const startStreamId = viewsState + .get(String(swapStartIdx)) + .get('streamId') + const toStreamId = viewsState.get(String(toIdx)).get('streamId') + const startSpaces = stateIdxMap.get(swapStartIdx).spaces + const toSpaces = stateIdxMap.get(toIdx).spaces + for (const startSpaceIdx of startSpaces) { + viewsState.get(String(startSpaceIdx)).set('streamId', toStreamId) + } + for (const toSpaceIdx of toSpaces) { + viewsState.get(String(toSpaceIdx)).set('streamId', startStreamId) + } + }) + setSwapStartIdx() + }, + [stateDoc, stateIdxMap, swapStartIdx], + ) + const [dragStart, setDragStart] = useState() - const handleDragStart = useCallback((idx, ev) => { - setDragStart(idx) - ev.preventDefault() - }, []) + const handleDragStart = useCallback( + (idx, ev) => { + ev.preventDefault() + if (swapStartIdx !== undefined) { + handleSwap(idx) + } else { + setDragStart(idx) + ev.target.select() + } + }, + [handleSwap], + ) const [dragEnd, setDragEnd] = useState() useLayoutEffect(() => { function endDrag() { - if (dragStart !== undefined) { - stateDoc.transact(() => { - const viewsState = stateDoc.getMap('views') - const streamId = viewsState.get(String(dragStart)).get('streamId') - for (let idx = 0; idx < gridCount ** 2; idx++) { - if (idxInBox(gridCount, dragStart, dragEnd, idx)) { - viewsState.get(String(idx)).set('streamId', streamId) - } - } - }) - setDragStart() + if (dragStart === undefined) { + return } + stateDoc.transact(() => { + const viewsState = stateDoc.getMap('views') + const streamId = viewsState.get(String(dragStart)).get('streamId') + for (let idx = 0; idx < gridCount ** 2; idx++) { + if (idxInBox(gridCount, dragStart, dragEnd, idx)) { + viewsState.get(String(idx)).set('streamId', streamId) + } + } + }) + setDragStart() } window.addEventListener('mouseup', endDrag) return () => window.removeEventListener('mouseup', endDrag) @@ -361,6 +409,8 @@ function App({ wsEndpoint }) { const isListening = stateIdxMap.get(idx)?.isListening ?? false handleSetListening(idx, !isListening) }, + // This enables hotkeys when input elements are focused, and affects all hotkeys, not just this one. + { filter: () => true }, [stateIdxMap], ) useHotkeys( @@ -387,6 +437,13 @@ function App({ wsEndpoint }) { }, [setStreamCensored], ) + useHotkeys( + `alt+s`, + () => { + handleSwapView(focusedInputIdx) + }, + [handleSwapView, focusedInputIdx], + ) const normalStreams = streams.filter( (s) => !s.kind || s.kind === 'video' || s.kind === 'web', @@ -426,6 +483,7 @@ function App({ wsEndpoint }) { isListening={isListening} isBlurred={isBlurred} isHighlighted={isDragHighlighted} + isSwapping={idx === swapStartIdx} showDebug={showDebug} onMouseDown={handleDragStart} onMouseEnter={setDragEnd} @@ -435,6 +493,7 @@ function App({ wsEndpoint }) { onSetListening={handleSetListening} onSetBlurred={handleSetBlurred} onReloadView={handleReloadView} + onSwapView={handleSwapView} onBrowse={handleBrowse} onDevTools={handleDevTools} /> @@ -579,6 +638,7 @@ function GridInput({ isListening, isBlurred, isHighlighted, + isSwapping, showDebug, onMouseDown, onMouseEnter, @@ -587,6 +647,7 @@ function GridInput({ onSetListening, onSetBlurred, onReloadView, + onSwapView, onBrowse, onDevTools, }) { @@ -626,6 +687,7 @@ function GridInput({ idx, onReloadView, ]) + const handleSwapClick = useCallback(() => onSwapView(idx), [idx, onSwapView]) const handleBrowseClick = useCallback(() => onBrowse(spaceValue), [ spaceValue, onBrowse, @@ -636,7 +698,6 @@ function GridInput({ ]) const handleMouseDown = useCallback( (ev) => { - ev.target.select() onMouseDown(idx, ev) }, [onMouseDown], @@ -646,10 +707,7 @@ function GridInput({ {isDisplaying && ( - - - - {showDebug && ( + {showDebug ? ( <> @@ -658,6 +716,19 @@ function GridInput({ + ) : ( + <> + + + + + + + )} )}