diff --git a/src/browser/overlay.js b/src/browser/overlay.js index 9e48aa0..80e538e 100644 --- a/src/browser/overlay.js +++ b/src/browser/overlay.js @@ -31,10 +31,14 @@ function Overlay({ views, streams, customStreams }) { const data = [...streams, ...customStreams].find( (d) => content.url === d.Link, ) - const isListening = viewState.matches('displaying.running.listening') + const isListening = viewState.matches( + 'displaying.running.audio.listening', + ) + const isBlurred = viewState.matches('displaying.running.video.blurred') const isLoading = viewState.matches('displaying.loading') return ( + {data && ( @@ -186,4 +190,13 @@ const ListeningIndicator = styled(SoundIcon)` } ` +const BlurCover = styled.div` + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + backdrop-filter: ${({ isBlurred }) => (isBlurred ? 'blur(30px)' : 'blur(0)')}; +` + render(, document.body) diff --git a/src/node/StreamWindow.js b/src/node/StreamWindow.js index 12cbfb5..5bbfa6d 100644 --- a/src/node/StreamWindow.js +++ b/src/node/StreamWindow.js @@ -223,16 +223,23 @@ export default class StreamWindow extends EventEmitter { ) } - reloadView(viewIdx) { + sendViewEvent(viewIdx, event) { const view = this.findViewByIdx(viewIdx) if (view) { - view.send('RELOAD') + view.send(event) } } + setViewBlurred(viewIdx, blurred) { + this.sendViewEvent(viewIdx, blurred ? 'BLUR' : 'UNBLUR') + } + + reloadView(viewIdx) { + this.sendViewEvent(viewIdx, 'RELOAD') + } + openDevTools(viewIdx, inWebContents) { - const view = this.findViewByIdx(viewIdx) - view.send({ type: 'DEVTOOLS', inWebContents }) + this.sendViewEvent(viewIdx, { type: 'DEVTOOLS', inWebContents }) } send(...args) { diff --git a/src/node/index.js b/src/node/index.js index 5f84006..6eee89e 100644 --- a/src/node/index.js +++ b/src/node/index.js @@ -83,6 +83,8 @@ async function main() { streamWindow.setViews(new Map(msg.views)) } else if (msg.type === 'set-listening-view') { streamWindow.setListeningView(msg.viewIdx) + } else if (msg.type === 'set-view-blurred') { + streamWindow.setViewBlurred(msg.viewIdx, msg.blurred) } else if (msg.type === 'set-custom-streams') { const customIDGen = new StreamIDGenerator(idGen) clientState.customStreams = customIDGen.process(msg.streams) diff --git a/src/node/viewStateMachine.js b/src/node/viewStateMachine.js index 0e17e67..2d198e6 100644 --- a/src/node/viewStateMachine.js +++ b/src/node/viewStateMachine.js @@ -102,7 +102,7 @@ const viewStateMachine = Machine( }, }, running: { - initial: 'muted', + type: 'parallel', entry: 'positionView', on: { DISPLAY: { @@ -114,15 +114,33 @@ const viewStateMachine = Machine( ], cond: 'contentUnchanged', }, - MUTE: '.muted', - UNMUTE: '.listening', }, states: { - muted: { - entry: 'muteAudio', + audio: { + initial: 'muted', + on: { + MUTE: '.muted', + UNMUTE: '.listening', + }, + states: { + muted: { + entry: 'muteAudio', + }, + listening: { + entry: 'unmuteAudio', + }, + }, }, - listening: { - entry: 'unmuteAudio', + video: { + initial: 'normal', + on: { + BLUR: '.blurred', + UNBLUR: '.normal', + }, + states: { + normal: {}, + blurred: {}, + }, }, }, }, diff --git a/src/static/video-slash-solid.svg b/src/static/video-slash-solid.svg new file mode 100644 index 0000000..0059310 --- /dev/null +++ b/src/static/video-slash-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/web/control.js b/src/web/control.js index 7269eed..7994c56 100644 --- a/src/web/control.js +++ b/src/web/control.js @@ -8,6 +8,7 @@ import styled, { css } from 'styled-components' import '../index.css' import { GRID_COUNT } from '../constants' 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 LifeRingIcon from '../static/life-ring-regular.svg' import WindowIcon from '../static/window-maximize-regular.svg' @@ -42,7 +43,10 @@ function App({ wsEndpoint }) { const stream = allStreams.find((d) => d.Link === content.url) const streamId = stream?._id const state = State.from(viewState.state) - const isListening = state.matches('displaying.running.listening') + const isListening = state.matches( + 'displaying.running.audio.listening', + ) + const isBlurred = state.matches('displaying.running.video.blurred') for (const space of pos.spaces) { if (!newStateIdxMap.has(space)) { newStateIdxMap.set(space, {}) @@ -52,6 +56,7 @@ function App({ wsEndpoint }) { content, state, isListening, + isBlurred, }) } } @@ -102,6 +107,16 @@ function App({ wsEndpoint }) { ) }, []) + const handleSetBlurred = useCallback((idx, blurred) => { + wsRef.current.send( + JSON.stringify({ + type: 'set-view-blurred', + viewIdx: idx, + blurred: blurred, + }), + ) + }, []) + const handleReloadView = useCallback((idx) => { wsRef.current.send( JSON.stringify({ @@ -166,6 +181,7 @@ function App({ wsEndpoint }) { const { streamId = '', isListening = false, + isBlurred = false, content = { url: '' }, state, } = stateIdxMap.get(idx) || {} @@ -177,8 +193,10 @@ function App({ wsEndpoint }) { isError={state && state.matches('displaying.error')} isDisplaying={state && state.matches('displaying')} isListening={isListening} + isBlurred={isBlurred} onChangeSpace={handleSetView} onSetListening={handleSetListening} + onSetBlurred={handleSetBlurred} onReloadView={handleReloadView} onBrowse={handleBrowse} onDevTools={handleDevTools} @@ -255,7 +273,9 @@ function GridInput({ isDisplaying, isError, isListening, + isBlurred, onSetListening, + onSetBlurred, onReloadView, onBrowse, onDevTools, @@ -279,6 +299,11 @@ function GridInput({ () => onSetListening(idx, !isListening), [idx, onSetListening, isListening], ) + const handleBlurClick = useCallback(() => onSetBlurred(idx, !isBlurred), [ + idx, + onSetBlurred, + isBlurred, + ]) const handleReloadClick = useCallback(() => onReloadView(idx), [ idx, onReloadView, @@ -295,23 +320,32 @@ function GridInput({ {isDisplaying && ( - + - - + + - - + + - + )} - + + + + > + + - - - ) -} - const StyledDataContainer = styled.div` opacity: ${({ isConnected }) => (isConnected ? 1 : 0.5)}; ` @@ -400,9 +426,16 @@ const StyledButton = styled.button` } ` -const StyledListeningButton = styled(StyledButton)` - ${({ isListening }) => - isListening && +const StyledSmallButton = styled(StyledButton)` + svg { + width: 14px; + height: 14px; + } +` + +const StyledToggleButton = styled(StyledButton)` + ${({ isActive }) => + isActive && ` border-color: red; background: #c77; @@ -426,7 +459,7 @@ const StyledGridButtons = styled.div` ` const StyledGridInput = styled.input` - width: 150px; + width: 160px; height: 50px; padding: 20px; border: 2px solid ${({ isError }) => (isError ? 'red' : 'black')};