Add view swap functionality

This commit is contained in:
Max Goodhart
2020-08-11 01:00:02 -07:00
parent 0d9ebd247c
commit 0beded8593
3 changed files with 93 additions and 20 deletions

View File

@@ -63,6 +63,7 @@ The following hotkeys are available with the "control" webpage focused:
- **alt+[1...9]**: Listen to the numbered stream - **alt+[1...9]**: Listen to the numbered stream
- **alt+shift+[1...9]**: Toggle blur on 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+c**: Activate [Streamdelay](https://github.com/chromakode/streamdelay) censor mode
- **alt+shift+c**: Deactivate [Streamdelay](https://github.com/chromakode/streamdelay) censor mode - **alt+shift+c**: Deactivate [Streamdelay](https://github.com/chromakode/streamdelay) censor mode

View File

@@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="exchange-alt" class="svg-inline--fa fa-exchange-alt fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M0 168v-16c0-13.255 10.745-24 24-24h360V80c0-21.367 25.899-32.042 40.971-16.971l80 80c9.372 9.373 9.372 24.569 0 33.941l-80 80C409.956 271.982 384 261.456 384 240v-48H24c-13.255 0-24-10.745-24-24zm488 152H128v-48c0-21.314-25.862-32.08-40.971-16.971l-80 80c-9.372 9.373-9.372 24.569 0 33.941l80 80C102.057 463.997 128 453.437 128 432v-48h360c13.255 0 24-10.745 24-24v-16c0-13.255-10.745-24-24-24z"></path></svg>

After

Width:  |  Height:  |  Size: 639 B

View File

@@ -21,6 +21,7 @@ import { idxInBox } from '../geometry'
import SoundIcon from '../static/volume-up-solid.svg' import SoundIcon from '../static/volume-up-solid.svg'
import NoVideoIcon from '../static/video-slash-solid.svg' import NoVideoIcon from '../static/video-slash-solid.svg'
import ReloadIcon from '../static/redo-alt-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 LifeRingIcon from '../static/life-ring-regular.svg'
import WindowIcon from '../static/window-maximize-regular.svg' import WindowIcon from '../static/window-maximize-regular.svg'
import { idColor } from './colors' import { idColor } from './colors'
@@ -137,6 +138,7 @@ function useStreamwallConnection(wsEndpoint) {
state, state,
isListening, isListening,
isBlurred, isBlurred,
spaces: pos.spaces,
}) })
} }
} }
@@ -214,15 +216,62 @@ function App({ wsEndpoint }) {
setShowDebug(ev.target.checked) 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 [dragStart, setDragStart] = useState()
const handleDragStart = useCallback((idx, ev) => { const handleDragStart = useCallback(
setDragStart(idx) (idx, ev) => {
ev.preventDefault() ev.preventDefault()
}, []) if (swapStartIdx !== undefined) {
handleSwap(idx)
} else {
setDragStart(idx)
ev.target.select()
}
},
[handleSwap],
)
const [dragEnd, setDragEnd] = useState() const [dragEnd, setDragEnd] = useState()
useLayoutEffect(() => { useLayoutEffect(() => {
function endDrag() { function endDrag() {
if (dragStart !== undefined) { if (dragStart === undefined) {
return
}
stateDoc.transact(() => { stateDoc.transact(() => {
const viewsState = stateDoc.getMap('views') const viewsState = stateDoc.getMap('views')
const streamId = viewsState.get(String(dragStart)).get('streamId') const streamId = viewsState.get(String(dragStart)).get('streamId')
@@ -234,7 +283,6 @@ function App({ wsEndpoint }) {
}) })
setDragStart() setDragStart()
} }
}
window.addEventListener('mouseup', endDrag) window.addEventListener('mouseup', endDrag)
return () => window.removeEventListener('mouseup', endDrag) return () => window.removeEventListener('mouseup', endDrag)
}, [stateDoc, dragStart, dragEnd]) }, [stateDoc, dragStart, dragEnd])
@@ -361,6 +409,8 @@ function App({ wsEndpoint }) {
const isListening = stateIdxMap.get(idx)?.isListening ?? false const isListening = stateIdxMap.get(idx)?.isListening ?? false
handleSetListening(idx, !isListening) handleSetListening(idx, !isListening)
}, },
// This enables hotkeys when input elements are focused, and affects all hotkeys, not just this one.
{ filter: () => true },
[stateIdxMap], [stateIdxMap],
) )
useHotkeys( useHotkeys(
@@ -387,6 +437,13 @@ function App({ wsEndpoint }) {
}, },
[setStreamCensored], [setStreamCensored],
) )
useHotkeys(
`alt+s`,
() => {
handleSwapView(focusedInputIdx)
},
[handleSwapView, focusedInputIdx],
)
const normalStreams = streams.filter( const normalStreams = streams.filter(
(s) => !s.kind || s.kind === 'video' || s.kind === 'web', (s) => !s.kind || s.kind === 'video' || s.kind === 'web',
@@ -426,6 +483,7 @@ function App({ wsEndpoint }) {
isListening={isListening} isListening={isListening}
isBlurred={isBlurred} isBlurred={isBlurred}
isHighlighted={isDragHighlighted} isHighlighted={isDragHighlighted}
isSwapping={idx === swapStartIdx}
showDebug={showDebug} showDebug={showDebug}
onMouseDown={handleDragStart} onMouseDown={handleDragStart}
onMouseEnter={setDragEnd} onMouseEnter={setDragEnd}
@@ -435,6 +493,7 @@ function App({ wsEndpoint }) {
onSetListening={handleSetListening} onSetListening={handleSetListening}
onSetBlurred={handleSetBlurred} onSetBlurred={handleSetBlurred}
onReloadView={handleReloadView} onReloadView={handleReloadView}
onSwapView={handleSwapView}
onBrowse={handleBrowse} onBrowse={handleBrowse}
onDevTools={handleDevTools} onDevTools={handleDevTools}
/> />
@@ -579,6 +638,7 @@ function GridInput({
isListening, isListening,
isBlurred, isBlurred,
isHighlighted, isHighlighted,
isSwapping,
showDebug, showDebug,
onMouseDown, onMouseDown,
onMouseEnter, onMouseEnter,
@@ -587,6 +647,7 @@ function GridInput({
onSetListening, onSetListening,
onSetBlurred, onSetBlurred,
onReloadView, onReloadView,
onSwapView,
onBrowse, onBrowse,
onDevTools, onDevTools,
}) { }) {
@@ -626,6 +687,7 @@ function GridInput({
idx, idx,
onReloadView, onReloadView,
]) ])
const handleSwapClick = useCallback(() => onSwapView(idx), [idx, onSwapView])
const handleBrowseClick = useCallback(() => onBrowse(spaceValue), [ const handleBrowseClick = useCallback(() => onBrowse(spaceValue), [
spaceValue, spaceValue,
onBrowse, onBrowse,
@@ -636,7 +698,6 @@ function GridInput({
]) ])
const handleMouseDown = useCallback( const handleMouseDown = useCallback(
(ev) => { (ev) => {
ev.target.select()
onMouseDown(idx, ev) onMouseDown(idx, ev)
}, },
[onMouseDown], [onMouseDown],
@@ -646,10 +707,7 @@ function GridInput({
<StyledGridContainer> <StyledGridContainer>
{isDisplaying && ( {isDisplaying && (
<StyledGridButtons side="left"> <StyledGridButtons side="left">
<StyledSmallButton onClick={handleReloadClick} tabIndex={1}> {showDebug ? (
<ReloadIcon />
</StyledSmallButton>
{showDebug && (
<> <>
<StyledSmallButton onClick={handleBrowseClick} tabIndex={1}> <StyledSmallButton onClick={handleBrowseClick} tabIndex={1}>
<WindowIcon /> <WindowIcon />
@@ -658,6 +716,19 @@ function GridInput({
<LifeRingIcon /> <LifeRingIcon />
</StyledSmallButton> </StyledSmallButton>
</> </>
) : (
<>
<StyledSmallButton onClick={handleReloadClick} tabIndex={1}>
<ReloadIcon />
</StyledSmallButton>
<StyledSmallButton
isActive={isSwapping}
onClick={handleSwapClick}
tabIndex={1}
>
<SwapIcon />
</StyledSmallButton>
</>
)} )}
</StyledGridButtons> </StyledGridButtons>
)} )}