From a58a94aa51da9d897c506813ae9e8dc7a9701d3b Mon Sep 17 00:00:00 2001 From: Max Goodhart Date: Fri, 19 Jun 2020 08:01:59 -0700 Subject: [PATCH] Add reload button to control interface --- src/node/StreamWindow.js | 11 +++++ src/node/index.js | 2 + src/node/viewStateMachine.js | 15 ++++--- src/static/redo-alt-solid.svg | 1 + src/web/control.js | 78 +++++++++++++++++++++++++++-------- 5 files changed, 81 insertions(+), 26 deletions(-) create mode 100644 src/static/redo-alt-solid.svg diff --git a/src/node/StreamWindow.js b/src/node/StreamWindow.js index 0b7374b..b31eae5 100644 --- a/src/node/StreamWindow.js +++ b/src/node/StreamWindow.js @@ -175,6 +175,17 @@ export default class StreamWindow extends EventEmitter { } } + reloadView(viewIdx) { + const view = this.views.find( + (v) => + v.state.matches('displaying') && + v.state.context.pos.spaces.includes(viewIdx), + ) + if (view) { + view.send('RELOAD') + } + } + send(...args) { this.overlayView.webContents.send(...args) } diff --git a/src/node/index.js b/src/node/index.js index 38917ea..360a4c7 100644 --- a/src/node/index.js +++ b/src/node/index.js @@ -60,6 +60,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 === 'reload-view') { + streamWindow.reloadView(msg.viewIdx) } } diff --git a/src/node/viewStateMachine.js b/src/node/viewStateMachine.js index cc36251..2cc7376 100644 --- a/src/node/viewStateMachine.js +++ b/src/node/viewStateMachine.js @@ -16,14 +16,11 @@ const viewStateMachine = Machine( }, states: { empty: { - entry: [ - assign({ - pos: {}, - info: {}, - url: null, - }), - 'hideView', - ], + entry: assign({ + pos: {}, + info: {}, + url: null, + }), invoke: { src: 'clearView', onError: { @@ -45,6 +42,7 @@ const viewStateMachine = Machine( }), cond: 'urlUnchanged', }, + RELOAD: '.loading', }, states: { loading: { @@ -80,6 +78,7 @@ const viewStateMachine = Machine( running: { initial: 'muted', entry: 'positionView', + exit: 'hideView', on: { DISPLAY: { actions: [ diff --git a/src/static/redo-alt-solid.svg b/src/static/redo-alt-solid.svg new file mode 100644 index 0000000..b8e9455 --- /dev/null +++ b/src/static/redo-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 2041b40..57802c9 100644 --- a/src/web/control.js +++ b/src/web/control.js @@ -2,11 +2,13 @@ import range from 'lodash/range' import ReconnectingWebSocket from 'reconnecting-websocket' import { h, render } from 'preact' import { useEffect, useState, useCallback, useRef } from 'preact/hooks' -import styled from 'styled-components' +import { State } from 'xstate' +import styled, { css } from 'styled-components' import '../index.css' import { GRID_COUNT } from '../constants' import SoundIcon from '../static/volume-up-solid.svg' +import ReloadIcon from '../static/redo-alt-solid.svg' function emptyStateIdxMap() { return new Map( @@ -15,7 +17,7 @@ function emptyStateIdxMap() { { streamId: null, url: null, - state: {}, + state: State.from({}), isListening: false, }, ]), @@ -47,13 +49,13 @@ function App({ wsEndpoint }) { continue } const streamId = streams.find((d) => d.Link === url)?._id - const isListening = - viewState.state.displaying?.running === 'listening' + const state = State.from(viewState.state) + const isListening = state.matches('displaying.running.listening') for (const space of pos.spaces) { Object.assign(newStateIdxMap.get(space), { streamId, url, - state: viewState.state, + state, isListening, }) } @@ -102,6 +104,15 @@ function App({ wsEndpoint }) { ) }, []) + const handleReloadView = useCallback((idx) => { + wsRef.current.send( + JSON.stringify({ + type: 'reload-view', + viewIdx: idx, + }), + ) + }, []) + return (

Stream Wall

@@ -120,9 +131,11 @@ function App({ wsEndpoint }) { idx={idx} onChangeSpace={handleSetView} spaceValue={streamId} - isError={state === 'error'} + isError={state.matches('error')} + isDisplaying={state.matches('displaying')} isListening={isListening} onSetListening={handleSetListening} + onReloadView={handleReloadView} /> ) })} @@ -154,9 +167,11 @@ function GridInput({ idx, onChangeSpace, spaceValue, + isDisplaying, isError, isListening, onSetListening, + onReloadView, }) { const [editingValue, setEditingValue] = useState() const handleFocus = useCallback((ev) => { @@ -177,16 +192,29 @@ function GridInput({ () => onSetListening(idx, !isListening), [idx, onSetListening, isListening], ) + const handleReloadClick = useCallback(() => onReloadView(idx), [ + idx, + onReloadView, + ]) const handleClick = useCallback((ev) => { ev.target.select() }) return ( - + {isDisplaying && ( + + + + + + )} + + + (isListening ? 'red' : 'gray')}; - background: ${({ isListening }) => (isListening ? '#c77' : '#ccc')}; + border-color: gray; + background: #ccc; border-radius: 5px; &:focus { @@ -235,13 +263,27 @@ const StyledListeningButton = styled.button` } ` +const StyledListeningButton = styled(StyledButton)` + ${({ isListening }) => + isListening && + ` + border-color: red; + background: #c77; + `}; +` + const StyledGridContainer = styled.div` position: relative; +` - ${StyledListeningButton} { - position: absolute; - bottom: 5px; - right: 5px; +const StyledGridButtons = styled.div` + display: flex; + position: absolute; + bottom: 0; + ${({ side }) => (side === 'left' ? 'left: 0' : 'right: 0')}; + + ${StyledButton} { + margin: 5px; } `