diff --git a/package.json b/package.json index 9bc045f..827d458 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,8 @@ "@fortawesome/fontawesome-svg-core": "^1.2.15", "@fortawesome/free-solid-svg-icons": "^5.7.2", "@fortawesome/react-fontawesome": "^0.1.4", + "@material-ui/core": "^3.9.2", + "@material-ui/icons": "^3.0.2", "approximate-number": "^2.0.0", "array-buffer-to-hex": "^1.0.0", "blessed": "^0.1.81", @@ -43,6 +45,7 @@ "qs": "^6.6.0", "react": "^16.4.2", "react-app-rewired": "^2.1.0", + "react-compound-slider": "^1.2.1", "react-dom": "^16.4.2", "react-force-graph-3d": "^1.6.1", "react-router-dom": "^4.3.1", @@ -55,6 +58,7 @@ "tinyqueue": "^2.0.0", "urlsafe-base64": "^1.0.0", "use-events": "^1.2.0", + "use-fullscreen": "^0.0.5", "uuid": "^3.3.2", "worker-loader": "^2.0.0", "wrtc": "^0.3.5", diff --git a/src/App.js b/src/App.js deleted file mode 100644 index 4554388..0000000 --- a/src/App.js +++ /dev/null @@ -1,235 +0,0 @@ -import React, {useEffect, useState, useMemo, useRef} from 'react'; -import URI from './ppspp/uri'; -import DiagnosticMenu from './DiagnosticMenu'; -import SwarmPlayer from './SwarmPlayer'; -import {ChunkedReadStream} from './chunkedStream'; -import {Client} from './client'; -import {ConnManager} from './wrtc'; -import {PubSubConsumer} from './pubsub'; -import PlayButton from './PlayButton'; -import qs from 'qs'; -import {useTimeout, useAsync} from 'react-use'; -import hexToUint8Array from './hexToUint8Array'; -import moment from 'moment'; - -import './App.scss'; - -const getDefaultBootstrapAddress = () => { - const proto = window.location.protocol === 'https:' ? 'wss' : 'ws'; - const host = process.env.NODE_ENV === 'development' - ? window.location.hostname + ':8080' - : window.location.host; - return `${proto}://${host}`; -}; - -const useSwarm = ({ppsppClient} = {}) => { - const [swarm, setSwarm] = useState(null); - const join = uri => setSwarm(ppsppClient.joinSwarm(URI.parse(uri))); - return [swarm, join]; -}; - -const usePubSubSwarm = (client, name) => { - const [swarm, setSwarm] = useState(null); - const [consumer, setConsumer] = useState(null); - - useEffect(() => { - if (client) { - setImmediate(() => { - const {uri} = client.bootstrap.swarms.find(desc => desc.name === name); - const swarm = client.ppsppClient.joinSwarm(URI.parse(uri)); - const consumer = new PubSubConsumer(swarm); - - setSwarm(swarm); - setConsumer(consumer); - - return () => client.ppsppClient.leaveSwarm(URI.parse(uri)); - }); - } - }, [client]); - - return [consumer, swarm]; -}; - -const useIndexSwarm = client => usePubSubSwarm(client, 'index'); - -const useQuery = queryString => useMemo(() => { - return qs.parse(queryString, {ignoreQueryPrefix: true}) || {}; -}, [queryString]); - -const NoiseLogger = ({swarm}) => { - useEffect(() => { - if (swarm) { - const stream = new ChunkedReadStream(swarm); - stream.on('data', ({length}) => console.log(`received ${length} bytes`)); - } - }, [swarm]); - - return ; -}; - -const PubSubLogger = ({indexSwarm, swarm}) => { - useEffect(() => { - if (swarm) { - const consumer = new PubSubConsumer(swarm); - consumer.on('message', message => console.log(message)); - } - }, [swarm]); - - return ( - <> - - - - ); -}; - -const useChatSwarm = client => { - const [consumer] = usePubSubSwarm(client, 'chat'); - const [messages, setMessages] = useState([]); - - useEffect(() => { - if (consumer == null) { - return; - } - - const handleMessage = message => setMessages(prev => ([ - ...prev.slice(prev.length > 100 ? 1 : 0), - message, - ])); - - consumer.on('message', e => console.log(e)); - consumer.on('message', handleMessage); - return () => consumer.removeListener('message', handleMessage); - }, [consumer]); - - const sendMessage = message => { - if (client.dhtClient) { - client.dhtClient.send( - hexToUint8Array(client.bootstrap.bootstrapId), - 'chat.message', - {message}, - ); - } - }; - - return [messages, sendMessage]; -}; - -const ChatMessages = ({messages}) => { - const items = messages.map(({time, message, id}) => ( -
  • - {moment(time).format('HH:mm:ss')} - {message} -
  • - )).reverse(); - - return ( -
      - {items} -
    - ); -}; - -const Chat = ({client}) => { - const [messages, sendMessage] = useChatSwarm(client); - const [message, setMessage] = useState(''); - const input = useRef(); - - const handleSubmit = e => { - e.preventDefault(); - - sendMessage(message); - setMessage(''); - }; - - const handleChange = e => { - setMessage(e.target.value); - }; - - return ( -
    -
    - - -
    - -
    - ); -}; - -const App = ({ - location, - match: {params}, - clientTimeoutMs = 5000, -}) => { - const query = useQuery(location.search); - const autoPlay = 'autoplay' in query; - const bootstrapAddress = query.bootstrap || getDefaultBootstrapAddress(); - const swarmName = params.name; - - const clientTimeout = useTimeout(clientTimeoutMs); - const { - loading: clientLoading, - error: clientError, - value: client, - } = useAsync(() => Client.create(new ConnManager(bootstrapAddress)), []); - - const index = null; - const indexSwarm = null; - // const [index, indexSwarm] = useIndexSwarm(client); - const [swarm, joinSwarm] = useSwarm(client); - - const swarmDesc = client?.bootstrap.swarms.find(desc => desc.name === swarmName); - const error = clientError || (autoPlay && clientTimeout) || !(clientLoading || swarmDesc); - - useEffect(() => { - if (autoPlay && swarmDesc) { - setImmediate(() => joinSwarm(swarmDesc.uri)); - } - }, [autoPlay, swarmDesc]); - - if (swarm) { - const Component = { - 'application/octet-stream': NoiseLogger, - 'application/json': PubSubLogger, - 'video/mpeg-ts': SwarmPlayer, - }[swarmDesc.contentType]; - - return ( - - ); - } - - const indexSwarmDiagnosticMenu = indexSwarm && ; - const chat = 'chat' in query && ; - - return ( - <> - {chat} - {indexSwarmDiagnosticMenu} -
    -
    -
    - joinSwarm(swarmDesc.uri)} - pulse={!clientLoading && !autoPlay} - flicker={clientLoading || autoPlay} - error={error} - blur - /> - - ); -}; - -export default App; diff --git a/src/Router.js b/src/Router.js deleted file mode 100644 index 4efd537..0000000 --- a/src/Router.js +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import {HashRouter, Redirect, Route, Switch} from 'react-router-dom'; -import App from './App'; -import Test from './Test'; -import DhtGraph from './DhtGraph'; - -const Router = () => ( - - - - - - - - -); - -export default Router; diff --git a/src/SwarmPlayer.scss b/src/SwarmPlayer.scss deleted file mode 100644 index 12ae92c..0000000 --- a/src/SwarmPlayer.scss +++ /dev/null @@ -1,26 +0,0 @@ -.swarm_player__video { - height: 100vh; - width: 100vw; -} - -.swarm_player__waiting_spinner { - position: absolute; - top: 50%; - left: 50%; - margin: -8px 0 0 -8px; - animation: spin 2s linear infinite; - opacity: 0.8; - - svg { - transform: scale(6); - } -} - -@keyframes spin { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } -} diff --git a/src/components/App.js b/src/components/App.js new file mode 100644 index 0000000..a60694c --- /dev/null +++ b/src/components/App.js @@ -0,0 +1,86 @@ +import React, {useEffect, useState} from 'react'; +import URI from '../ppspp/uri'; +import {Client} from '../client'; +import {ConnManager} from '../wrtc'; +import PlayButton from './PlayButton'; +import {useTimeout, useAsync} from 'react-use'; +import useQuery from '../hooks/useQuery'; +import VideoPlayer from './VideoPlayer'; + +import './App.scss'; + +const NoiseLogger = React.lazy(() => import('./NoiseLogger')); +const PubSubLogger = React.lazy(() => import('./PubSubLogger')); + +const getDefaultBootstrapAddress = () => { + const proto = window.location.protocol === 'https:' ? 'wss' : 'ws'; + const host = process.env.NODE_ENV === 'development' + ? window.location.hostname + ':8080' + : window.location.host; + return `${proto}://${host}`; +}; + +const useSwarm = ({ppsppClient} = {}) => { + const [swarm, setSwarm] = useState(null); + const join = uri => setSwarm(ppsppClient.joinSwarm(URI.parse(uri))); + return [swarm, join]; +}; + +const App = ({ + location, + match: {params}, + clientTimeoutMs = 5000, +}) => { + const query = useQuery(location.search); + const autoPlay = 'autoplay' in query; + const bootstrapAddress = query.bootstrap || getDefaultBootstrapAddress(); + const swarmName = params.name; + + const clientTimeout = useTimeout(clientTimeoutMs); + const { + loading: clientLoading, + error: clientError, + value: client, + } = useAsync(() => Client.create(new ConnManager(bootstrapAddress)), []); + + const [swarm, joinSwarm] = useSwarm(client); + + const swarmDesc = client?.bootstrap.swarms.find(desc => desc.name === swarmName); + const error = clientError || (autoPlay && clientTimeout) || !(clientLoading || swarmDesc); + + useEffect(() => { + if (autoPlay && swarmDesc) { + setImmediate(() => joinSwarm(swarmDesc.uri)); + } + }, [autoPlay, swarmDesc]); + + if (swarm) { + const Component = { + 'application/octet-stream': NoiseLogger, + 'application/json': PubSubLogger, + 'video/mpeg-ts': VideoPlayer, + }[swarmDesc.contentType]; + + return ( + + ); + } + + return ( + <> +
    +
    +
    + joinSwarm(swarmDesc.uri)} + pulse={!clientLoading && !autoPlay} + flicker={clientLoading || autoPlay} + error={error} + blur + /> + + ); +}; + +export default App; diff --git a/src/App.scss b/src/components/App.scss similarity index 98% rename from src/App.scss rename to src/components/App.scss index 35dbfdd..55d1df2 100644 --- a/src/App.scss +++ b/src/components/App.scss @@ -82,7 +82,7 @@ body { margin-left: -350px; height: calc(100vh + 700px); width: calc(100vw + 700px); - background-image: url("/noise.png"); + background-image: url("./noise.png"); animation: idle-jitter 500ms infinite } diff --git a/src/components/Chat.js b/src/components/Chat.js new file mode 100644 index 0000000..57b0f9b --- /dev/null +++ b/src/components/Chat.js @@ -0,0 +1,38 @@ +import React, {useState, useRef} from 'react'; +import ChatMessages from './ChatMessages'; +import useChatSwarm from '../hooks/useChatSwarm'; + +const Chat = ({client}) => { + const [messages, sendMessage] = useChatSwarm(client); + const [message, setMessage] = useState(''); + const input = useRef(); + + const handleSubmit = e => { + e.preventDefault(); + + sendMessage(message); + setMessage(''); + }; + + const handleChange = e => { + setMessage(e.target.value); + }; + + return ( +
    +
    + + +
    + +
    + ); +}; + +export default Chat; diff --git a/src/components/ChatMessages.js b/src/components/ChatMessages.js new file mode 100644 index 0000000..a17fdca --- /dev/null +++ b/src/components/ChatMessages.js @@ -0,0 +1,19 @@ +import React from 'react'; +import moment from 'moment'; + +const Messages = ({messages}) => { + const items = messages.map(({time, message, id}) => ( +
  • + {moment(time).format('HH:mm:ss')} + {message} +
  • + )).reverse(); + + return ( +
      + {items} +
    + ); +}; + +export default Messages; diff --git a/src/DhtGraph.js b/src/components/DhtGraph.js similarity index 97% rename from src/DhtGraph.js rename to src/components/DhtGraph.js index 81e9c7e..ad871cd 100644 --- a/src/DhtGraph.js +++ b/src/components/DhtGraph.js @@ -1,6 +1,6 @@ import React, {useEffect, useReducer, useState} from 'react'; -import {Server, ConnManager} from './loopback'; -import {Client} from './client'; +import {Server, ConnManager} from '../loopback'; +import {Client} from '../client'; import arrayBufferToHex from 'array-buffer-to-hex'; import ForceGraph3D from 'react-force-graph-3d'; import {schemeCategory10} from 'd3-scale-chromatic'; @@ -74,7 +74,7 @@ const reduceGraph = (graph, {type, ...data}) => { }), }; case 'REMOVE_LINK': - console.log(data); + // console.log(data); return { nodes: graph.nodes, links: graph.links.filter(({source, target}) => { @@ -123,7 +123,7 @@ const useGraph = () => { // } const {id, allChannels} = dhtClient; - console.log(allChannels); + // console.log(allChannels); const source = arrayBufferToHex(id); dispatchGraphAction({ @@ -241,7 +241,7 @@ const App = () => { const [graph, {addNodes, deleteNodes}] = useGraph(); const handleNodeClick = useNodePinger(); - console.log(graph); + // console.log(graph); // useEffect(() => { // let n = 1; @@ -259,7 +259,6 @@ const App = () => {
    -
    diff --git a/src/DiagnosticMenu/index.css b/src/components/DiagnosticMenu/index.css similarity index 100% rename from src/DiagnosticMenu/index.css rename to src/components/DiagnosticMenu/index.css diff --git a/src/DiagnosticMenu/index.js b/src/components/DiagnosticMenu/index.js similarity index 100% rename from src/DiagnosticMenu/index.js rename to src/components/DiagnosticMenu/index.js diff --git a/src/components/NoiseLogger.js b/src/components/NoiseLogger.js new file mode 100644 index 0000000..ceef569 --- /dev/null +++ b/src/components/NoiseLogger.js @@ -0,0 +1,16 @@ +import React, {useEffect} from 'react'; +import {ChunkedReadStream} from '../chunkedStream'; +import DiagnosticMenu from './DiagnosticMenu'; + +const NoiseLogger = ({swarm}) => { + useEffect(() => { + if (swarm) { + const stream = new ChunkedReadStream(swarm); + stream.on('data', ({length}) => console.log(`received ${length} bytes`)); + } + }, [swarm]); + + return ; +}; + +export default NoiseLogger; diff --git a/src/PlayButton.js b/src/components/PlayButton.js similarity index 100% rename from src/PlayButton.js rename to src/components/PlayButton.js diff --git a/src/PlayButton.scss b/src/components/PlayButton.scss similarity index 100% rename from src/PlayButton.scss rename to src/components/PlayButton.scss diff --git a/src/components/PubSubLogger.js b/src/components/PubSubLogger.js new file mode 100644 index 0000000..d307483 --- /dev/null +++ b/src/components/PubSubLogger.js @@ -0,0 +1,21 @@ +import React, {useEffect} from 'react'; +import {PubSubConsumer} from '../pubsub'; +import DiagnosticMenu from './DiagnosticMenu'; + +const PubSubLogger = ({indexSwarm, swarm}) => { + useEffect(() => { + if (swarm) { + const consumer = new PubSubConsumer(swarm); + consumer.on('message', message => console.log(message)); + } + }, [swarm]); + + return ( + <> + + + + ); +}; + +export default PubSubLogger; diff --git a/src/components/Router.js b/src/components/Router.js new file mode 100644 index 0000000..0af29bb --- /dev/null +++ b/src/components/Router.js @@ -0,0 +1,24 @@ +import React, {Suspense, lazy} from 'react'; +import {HashRouter, Redirect, Route, Switch} from 'react-router-dom'; +import App from './App'; +import PlayButton from './PlayButton'; + +const Test = lazy(() => import('./Test')); +const DhtGraph = lazy(() => import('./DhtGraph')); + +console.log({App, Test, DhtGraph}); + +const Router = () => ( + + }> + + } /> + } /> + + + + + +); + +export default Router; diff --git a/src/Test.js b/src/components/Test.js similarity index 89% rename from src/Test.js rename to src/components/Test.js index d06b27a..2b8fdef 100644 --- a/src/Test.js +++ b/src/components/Test.js @@ -1,7 +1,7 @@ import React, {useEffect, useState} from 'react'; -import {Server, ConnManager} from './loopback'; -import {Client} from './client'; -import {ChunkedReadStream, ChunkedWriteStreamInjector} from './chunkedStream'; +import {Server, ConnManager} from '../loopback'; +import {Client} from '../client'; +import {ChunkedReadStream, ChunkedWriteStreamInjector} from '../chunkedStream'; import DiagnosticMenu from './DiagnosticMenu'; import './App.scss'; diff --git a/src/components/VideoControls.js b/src/components/VideoControls.js new file mode 100644 index 0000000..5f96ba1 --- /dev/null +++ b/src/components/VideoControls.js @@ -0,0 +1,75 @@ +import React from 'react'; +import VideoVolume from './VideoVolume'; +import classNames from 'classnames'; +import { + Pause, + PlayArrow, + VolumeOff, + VolumeMute, + VolumeDown, + VolumeUp, + Fullscreen, + FullscreenExit, +} from '@material-ui/icons'; + +import './VideoPlayer.scss'; + +const VideoControls = ({ + playing, + pause, + play, + volume, + unmute, + mute, + fullscreen, + toggleFullscreen, + visible, + setVolume, +}) => { + const playButton = playing + ? + : ; + + const volumeIcons = [ + VolumeOff, + VolumeMute, + VolumeDown, + VolumeUp, + ]; + const VolumeIcon = volumeIcons[Math.ceil(volume * (volumeIcons.length - 1))]; + const handleVolumeClick = () => volume === 0 ? unmute() : mute(); + + let fullscreenButton; + if (document.fullscreenEnabled) { + const Icon = fullscreen ? FullscreenExit : Fullscreen; + fullscreenButton = ( +
    + +
    + ); + } + + const controlsClassName = classNames({ + swarm_player__controls: true, + visible: visible, + }); + + return ( +
    +
    +
    + {playButton} +
    +
    + + +
    +
    +
    + {fullscreenButton} +
    +
    + ); +}; + +export default VideoControls; diff --git a/src/components/VideoPlayer.js b/src/components/VideoPlayer.js new file mode 100644 index 0000000..7081410 --- /dev/null +++ b/src/components/VideoPlayer.js @@ -0,0 +1,79 @@ +import React, {useEffect, useRef, useState} from 'react'; +import DiagnosticMenu from './DiagnosticMenu'; +import PlayButton from './PlayButton'; +import useSwarmMediaSource from '../hooks/useSwarmMediaSource'; +import useVideo from '../hooks/useVideo'; +import {useDebounce} from 'react-use'; +import useFullscreen from 'use-fullscreen'; +import {Loop} from '@material-ui/icons'; +import VideoControls from './VideoControls'; + +import './VideoPlayer.scss'; + +const SwarmPlayer = ({swarm, indexSwarm}) => { + const [videoState, videoProps, videoControls] = useVideo(); + const mediaSource = useSwarmMediaSource(swarm); + + useEffect(() => { + if (videoProps.ref.current != null && mediaSource != null) { + videoProps.ref.current.src = URL.createObjectURL(mediaSource); + videoControls.play(); + } + }, [videoProps.ref, mediaSource]); + + const [controlsVisible, setControlsVisible] = useState(false); + const [lastActive, setLastActive] = useState(false); + + useDebounce(() => setControlsVisible(false), 5000, [lastActive]); + + const handleMouseMove = () => { + setControlsVisible(true); + setLastActive(Date.now()); + }; + + const handleMouseOut = () => setControlsVisible(false); + + const ref = useRef(); + const [isFullscreen, toggleFullscreen] = useFullscreen(); + + const playButton = (videoState.waiting && videoState.loaded) ? ( +
    + +
    + ) : ( + + ); + + return ( +
    + {/* */} + +
    + ); +}; + +export default SwarmPlayer; diff --git a/src/components/VideoPlayer.scss b/src/components/VideoPlayer.scss new file mode 100644 index 0000000..f4955d1 --- /dev/null +++ b/src/components/VideoPlayer.scss @@ -0,0 +1,121 @@ +.swarm_player__video { + height: 100vh; + width: 100vw; +} + +.swarm_player__waiting_spinner { + position: absolute; + top: 50%; + left: 50%; + margin: -16px 0 0 -16px; + animation: spin 2s linear infinite; + opacity: 0.8; + height: 24px; + width: 24px; + + svg { + transform: scale(6); + height: 24px; + width: 24px; + } +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(-360deg); + } +} + +.swarm_player__controls { + position: absolute; + bottom: 0; + height: 40px; + width: 100%; + display: flex; + justify-content: space-between; + opacity: 0; + background: rgba(0, 0, 0, 0.8); + transition: 400ms opacity ease-in-out; + + &.visible { + opacity: 1; + } + + .swarm_player__controls__group { + display: flex; + } + + .play, + .settings, + .fullscreen { + width: 30px; + flex-grow: 0; + flex-shrink: 0; + } + + .volume { + flex-grow: 0; + width: 200px; + display: flex; + justify-content: space-between; + } + + .play, + .settings, + .fullscreen, + .volume { + padding: 10px; + + svg { + cursor: pointer; + fill: #fff; + } + } + + .video_volume__slider { + width: 150px; + position: relative; + margin: 9px 10px; + opacity: 0; + transition: 400ms opacity ease-in-out; + } + + .volume:hover .video_volume__slider, + .video_volume__slider.dragging { + opacity: 1; + } + + .video_volume__rail { + position: absolute; + width: 100%; + height: 8px; + border-radius: 8px; + cursor: pointer; + background-color: #bbb; + } + + .video_volume__track { + position: absolute; + height: 8px; + z-index: 1px; + background-color: #eee; + border-radius: 8px; + cursor: pointer; + } + + .video_volume__handle { + position: absolute; + margin-left: -11px; + margin-top: -6px; + z-index: 2; + width: 18px; + height: 18px; + cursor: pointer; + border-radius: 50%; + box-shadow: 1px 1px 1px 1px rgba(100, 100, 100, 0.2); + background-color: #fff; + } +} diff --git a/src/components/VideoVolume.js b/src/components/VideoVolume.js new file mode 100644 index 0000000..55b3d16 --- /dev/null +++ b/src/components/VideoVolume.js @@ -0,0 +1,89 @@ +import React, {useState} from 'react'; +import {Slider, Rail, Handles, Tracks} from 'react-compound-slider'; +import classNames from 'classnames'; + +export const Handle = ({ + domain: [min, max], + handle: {id, value, percent}, + getHandleProps, +}) => ( +
    +); + +export const Track = ({source, target, getTrackProps}) => ( +
    +); + +const VideoVolume = ({ + value, + onUpdate, +}) => { + const [dragging, setDragging] = useState(false); + + const sliderClassNames = classNames({ + video_volume__slider: true, + dragging, + }); + + return ( + setDragging(true)} + onSlideEnd={() => setDragging(false)} + values={[value]} + > + + {({getRailProps}) =>
    } + + + {({handles, getHandleProps}) => ( +
    + {handles.map(handle => ( + + ))} +
    + )} +
    + + {({tracks, getTrackProps}) => ( +
    + {tracks.map(({id, source, target}) => ( + + ))} +
    + )} +
    + + ); +}; + +export default VideoVolume; diff --git a/public/noise.png b/src/components/noise.png similarity index 100% rename from public/noise.png rename to src/components/noise.png diff --git a/src/hooks/useChatSwarm.js b/src/hooks/useChatSwarm.js new file mode 100644 index 0000000..e62e195 --- /dev/null +++ b/src/hooks/useChatSwarm.js @@ -0,0 +1,37 @@ +import {useEffect, useState} from 'react'; +import usePubSubSwarm from './usePubSubSwarm'; +import hexToUint8Array from '../hexToUint8Array'; + +const useChatSwarm = client => { + const [consumer] = usePubSubSwarm(client, 'chat'); + const [messages, setMessages] = useState([]); + + useEffect(() => { + if (consumer == null) { + return; + } + + const handleMessage = message => setMessages(prev => ([ + ...prev.slice(prev.length > 100 ? 1 : 0), + message, + ])); + + consumer.on('message', e => console.log(e)); + consumer.on('message', handleMessage); + return () => consumer.removeListener('message', handleMessage); + }, [consumer]); + + const sendMessage = message => { + if (client.dhtClient) { + client.dhtClient.send( + hexToUint8Array(client.bootstrap.bootstrapId), + 'chat.message', + {message}, + ); + } + }; + + return [messages, sendMessage]; +}; + +export default useChatSwarm; diff --git a/src/hooks/useIndexSwarm.js b/src/hooks/useIndexSwarm.js new file mode 100644 index 0000000..77644c0 --- /dev/null +++ b/src/hooks/useIndexSwarm.js @@ -0,0 +1,3 @@ +import usePubSubSwarm from './usePubSubSwarm'; + +const useIndexSwarm = client => usePubSubSwarm(client, 'index'); diff --git a/src/hooks/usePubSubSwarm b/src/hooks/usePubSubSwarm new file mode 100644 index 0000000..aa3aefc --- /dev/null +++ b/src/hooks/usePubSubSwarm @@ -0,0 +1,37 @@ +import React, {useEffect, useState, useMemo, useRef} from 'react'; +import URI from '../ppspp/uri'; +import DiagnosticMenu from './DiagnosticMenu'; +import SwarmPlayer from './SwarmPlayer'; +import {ChunkedReadStream} from '../chunkedStream'; +import {Client} from '../client'; +import {ConnManager} from '../wrtc'; +import {PubSubConsumer} from '../pubsub'; +import PlayButton from './PlayButton'; +import qs from 'qs'; +import {useTimeout, useAsync} from 'react-use'; +import hexToUint8Array from '../hexToUint8Array'; +import moment from 'moment'; + +const usePubSubSwarm = (client, name) => { + const [swarm, setSwarm] = useState(null); + const [consumer, setConsumer] = useState(null); + + useEffect(() => { + if (client) { + setImmediate(() => { + const {uri} = client.bootstrap.swarms.find(desc => desc.name === name); + const swarm = client.ppsppClient.joinSwarm(URI.parse(uri)); + const consumer = new PubSubConsumer(swarm); + + setSwarm(swarm); + setConsumer(consumer); + + return () => client.ppsppClient.leaveSwarm(URI.parse(uri)); + }); + } + }, [client]); + + return [consumer, swarm]; +}; + +const useIndexSwarm = client => usePubSubSwarm(client, 'index'); diff --git a/src/hooks/usePubSubSwarm.js b/src/hooks/usePubSubSwarm.js new file mode 100644 index 0000000..95a73c6 --- /dev/null +++ b/src/hooks/usePubSubSwarm.js @@ -0,0 +1,28 @@ + +import {useEffect, useState} from 'react'; +import URI from '../ppspp/uri'; +import {PubSubConsumer} from '../pubsub'; + +const usePubSubSwarm = (client, name) => { + const [swarm, setSwarm] = useState(null); + const [consumer, setConsumer] = useState(null); + + useEffect(() => { + if (client) { + setImmediate(() => { + const {uri} = client.bootstrap.swarms.find(desc => desc.name === name); + const swarm = client.ppsppClient.joinSwarm(URI.parse(uri)); + const consumer = new PubSubConsumer(swarm); + + setSwarm(swarm); + setConsumer(consumer); + + return () => client.ppsppClient.leaveSwarm(URI.parse(uri)); + }); + } + }, [client]); + + return [consumer, swarm]; +}; + +export default usePubSubSwarm; diff --git a/src/hooks/useQuery.js b/src/hooks/useQuery.js new file mode 100644 index 0000000..208f645 --- /dev/null +++ b/src/hooks/useQuery.js @@ -0,0 +1,10 @@ + + +import {useMemo} from 'react'; +import qs from 'qs'; + +const useQuery = queryString => useMemo(() => { + return qs.parse(queryString, {ignoreQueryPrefix: true}) || {}; +}, [queryString]); + +export default useQuery; diff --git a/src/hooks/useSwarmMediaSource.js b/src/hooks/useSwarmMediaSource.js new file mode 100644 index 0000000..cfa65ac --- /dev/null +++ b/src/hooks/useSwarmMediaSource.js @@ -0,0 +1,72 @@ +import {useState} from 'react'; +import muxjs from 'mux.js'; +import {ChunkedFragmentedReadStream} from '../chunkedStream'; +import {Buffer} from 'buffer'; + +const useSwarmMediaSource = swarm => { + const [mediaSource] = useState(() => { + const mediaSource = new MediaSource(); + mediaSource.addEventListener('sourceopen', handleSourceOpen); + return mediaSource; + }, []); + + function handleSourceOpen() { + const sourceBuffer = mediaSource.addSourceBuffer('video/mp4; codecs="mp4a.40.5,avc1.64001F"'); + // sourceBuffer.addEventListener('updatestart', e => console.log(e)); + // sourceBuffer.addEventListener('updateend', e => console.log(e)); + sourceBuffer.addEventListener('error', e => console.log(e)); + + const videoSegments = []; + const appendBuffer = newSegment => { + if (newSegment !== undefined && (videoSegments.length !== 0 || sourceBuffer.updating)) { + videoSegments.push(newSegment); + return; + } + + if (sourceBuffer.updating) { + return; + } + + const segment = newSegment || videoSegments.shift(); + if (segment === undefined) { + return; + } + + try { + sourceBuffer.appendBuffer(segment); + } catch (e) { + videoSegments.unshift(segment); + setImmediate(appendBuffer); + } + }; + + sourceBuffer.addEventListener('updateend', () => appendBuffer()); + + const transmuxer = new muxjs.mp4.Transmuxer(); + let initSet = false; + transmuxer.on('data', event => { + if (event.type === 'combined') { + const buf = initSet + ? event.data + : Buffer.concat([Buffer.from(event.initSegment), Buffer.from(event.data)]); + initSet = true; + + appendBuffer(buf); + } else { + console.log('unhandled event', event.type); + } + }); + + const stream = new ChunkedFragmentedReadStream(swarm); + stream.on('start', data => transmuxer.push(data)); + stream.on('data', data => transmuxer.push(data)); + stream.on('end', data => { + transmuxer.push(data); + transmuxer.flush(); + }); + } + + return mediaSource; +}; + +export default useSwarmMediaSource; diff --git a/src/SwarmPlayer.js b/src/hooks/useVideo.js similarity index 60% rename from src/SwarmPlayer.js rename to src/hooks/useVideo.js index 9d4ada7..40173eb 100644 --- a/src/SwarmPlayer.js +++ b/src/hooks/useVideo.js @@ -1,79 +1,4 @@ -import React, {useRef, useEffect, useState} from 'react'; -import muxjs from 'mux.js'; -import {ChunkedFragmentedReadStream} from './chunkedStream'; -import DiagnosticMenu from './DiagnosticMenu'; -import {Buffer} from 'buffer'; -import PlayButton from './PlayButton'; -import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; -import {faSyncAlt} from '@fortawesome/free-solid-svg-icons'; - -import './SwarmPlayer.scss'; - -const useSwarmMediaSource = swarm => { - const [mediaSource] = useState(() => { - const mediaSource = new MediaSource(); - mediaSource.addEventListener('sourceopen', handleSourceOpen); - return mediaSource; - }, []); - - function handleSourceOpen() { - const sourceBuffer = mediaSource.addSourceBuffer('video/mp4; codecs="mp4a.40.5,avc1.64001F"'); - // sourceBuffer.addEventListener('updatestart', e => console.log(e)); - // sourceBuffer.addEventListener('updateend', e => console.log(e)); - sourceBuffer.addEventListener('error', e => console.log(e)); - - const videoSegments = []; - const appendBuffer = newSegment => { - if (newSegment !== undefined && (videoSegments.length !== 0 || sourceBuffer.updating)) { - videoSegments.push(newSegment); - return; - } - - if (sourceBuffer.updating) { - return; - } - - const segment = newSegment || videoSegments.shift(); - if (segment === undefined) { - return; - } - - try { - sourceBuffer.appendBuffer(segment); - } catch (e) { - videoSegments.unshift(segment); - setImmediate(appendBuffer); - } - }; - - sourceBuffer.addEventListener('updateend', () => appendBuffer()); - - const transmuxer = new muxjs.mp4.Transmuxer(); - let initSet = false; - transmuxer.on('data', event => { - if (event.type === 'combined') { - const buf = initSet - ? event.data - : Buffer.concat([Buffer.from(event.initSegment), Buffer.from(event.data)]); - initSet = true; - - appendBuffer(buf); - } else { - console.log('unhandled event', event.type); - } - }); - - const stream = new ChunkedFragmentedReadStream(swarm); - stream.on('start', data => transmuxer.push(data)); - stream.on('data', data => transmuxer.push(data)); - stream.on('end', data => { - transmuxer.push(data); - transmuxer.flush(); - }); - } - - return mediaSource; -}; +import {useEffect, useRef, useState} from 'react'; export const VideoReadyState = { // No information is available about the media resource. @@ -101,6 +26,7 @@ const useVideo = () => { const [waiting, setWaiting] = useState(true); const [muted, setMuted] = useState(null); const [volume, setVolume] = useState(null); + const [savedVolume, setSavedVolume] = useState(null); const [readyState, setReadyState] = useState(0); useEffect(() => { @@ -113,6 +39,8 @@ const useVideo = () => { setPaused(ref.current.paused); setReadyState(ref.current.readyState); + console.log(ref); + ref.current.addEventListener('audioprocess', e => console.log(new Date().toUTCString(), 'audioprocess', e)); ref.current.addEventListener('canplay', e => console.log(new Date().toUTCString(), 'canplay', e)); ref.current.addEventListener('canplaythrough', e => console.log(new Date().toUTCString(), 'canplaythrough', e)); @@ -207,6 +135,15 @@ const useVideo = () => { } }; + const mute = () => { + setSavedVolume(ref.current.volume); + ref.current.volume = 0; + }; + + const unmute = () => { + ref.current.volume = savedVolume || 0.5; + }; + return [ { readyState, @@ -234,50 +171,12 @@ const useVideo = () => { }, { play, + pause: () => ref.current && ref.current.pause(), + setVolume: volume => ref.current && (ref.current.volume = volume), + mute, + unmute, }, ]; }; -const SwarmPlayer = ({swarm, indexSwarm}) =>{ - const [videoState, videoProps, videoControls] = useVideo(); - const mediaSource = useSwarmMediaSource(swarm); - - useEffect(() => { - if (videoProps.ref.current != null && mediaSource != null) { - videoProps.ref.current.src = URL.createObjectURL(mediaSource); - videoControls.play(); - } - }, [videoProps.ref, mediaSource]); - - console.log(videoState); - - const playButton = (videoState.waiting && videoState.loaded) ? ( -
    - -
    - ) : ( - - ); - - return ( - - {/* */} - - - ); -}; - -export default SwarmPlayer; +export default useVideo; diff --git a/src/index.js b/src/index.js index c91281a..776701f 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,6 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import Router from './Router'; +import Router from './components/Router'; import './index.css'; diff --git a/src/ppspp/scheduler.js b/src/ppspp/scheduler.js index f1595ac..59570f9 100644 --- a/src/ppspp/scheduler.js +++ b/src/ppspp/scheduler.js @@ -709,7 +709,7 @@ export class Scheduler { // TODO: how to pick this... maybe remote discard window size? // const startBin = this.loadedChunks.max(); // const startBin = this.loadedChunks.max() - 32; - const startBin = this.lastCompletedBin; + const startBin = this.lastCompletedBin - 32; // bail if no chunks have been loaded yet if (!isFinite(startBin)) { diff --git a/yarn.lock b/yarn.lock index 71b7f63..579d012 100644 --- a/yarn.lock +++ b/yarn.lock @@ -890,7 +890,7 @@ dependencies: regenerator-runtime "^0.12.0" -"@babel/runtime@^7.1.2", "@babel/runtime@^7.2.0": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.4": version "7.3.4" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.3.4.tgz#73d12ba819e365fcf7fd152aed56d6df97d21c83" integrity sha512-IvfvnMdSaLBateu0jfsYIpZTxAc2cKEXEMiezGGN75QcBcecDUKd3PgLAncT0oOgxKy8dd8hrJKj9MfzgfZd6g== @@ -962,6 +962,66 @@ humps "^2.0.1" prop-types "^15.5.10" +"@material-ui/core@^3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@material-ui/core/-/core-3.9.2.tgz#41ed1a470e981d199829eb5d9317a671c66a6f7d" + integrity sha512-aukR3mSH3g115St2OnqoeMRtmxzxxx+Mch7pFKRV3Tz3URExBlZwOolimjxKZpG4LGec8HlhREawafLsDzjVWQ== + dependencies: + "@babel/runtime" "^7.2.0" + "@material-ui/system" "^3.0.0-alpha.0" + "@material-ui/utils" "^3.0.0-alpha.2" + "@types/jss" "^9.5.6" + "@types/react-transition-group" "^2.0.8" + brcast "^3.0.1" + classnames "^2.2.5" + csstype "^2.5.2" + debounce "^1.1.0" + deepmerge "^3.0.0" + dom-helpers "^3.2.1" + hoist-non-react-statics "^3.2.1" + is-plain-object "^2.0.4" + jss "^9.8.7" + jss-camel-case "^6.0.0" + jss-default-unit "^8.0.2" + jss-global "^3.0.0" + jss-nested "^6.0.1" + jss-props-sort "^6.0.0" + jss-vendor-prefixer "^7.0.0" + normalize-scroll-left "^0.1.2" + popper.js "^1.14.1" + prop-types "^15.6.0" + react-event-listener "^0.6.2" + react-transition-group "^2.2.1" + recompose "0.28.0 - 0.30.0" + warning "^4.0.1" + +"@material-ui/icons@^3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@material-ui/icons/-/icons-3.0.2.tgz#d67a6dd1ec8312d3a88ec97944a63daeef24fe10" + integrity sha512-QY/3gJnObZQ3O/e6WjH+0ah2M3MOgLOzCy8HTUoUx9B6dDrS18vP7Ycw3qrDEKlB6q1KNxy6CZHm5FCauWGy2g== + dependencies: + "@babel/runtime" "^7.2.0" + recompose "0.28.0 - 0.30.0" + +"@material-ui/system@^3.0.0-alpha.0": + version "3.0.0-alpha.2" + resolved "https://registry.yarnpkg.com/@material-ui/system/-/system-3.0.0-alpha.2.tgz#096e80c8bb0f70aea435b9e38ea7749ee77b4e46" + integrity sha512-odmxQ0peKpP7RQBQ8koly06YhsPzcoVib1vByVPBH4QhwqBXuYoqlCjt02846fYspAqkrWzjxnWUD311EBbxOA== + dependencies: + "@babel/runtime" "^7.2.0" + deepmerge "^3.0.0" + prop-types "^15.6.0" + warning "^4.0.1" + +"@material-ui/utils@^3.0.0-alpha.2": + version "3.0.0-alpha.3" + resolved "https://registry.yarnpkg.com/@material-ui/utils/-/utils-3.0.0-alpha.3.tgz#836c62ea46f5ffc6f0b5ea05ab814704a86908b1" + integrity sha512-rwMdMZptX0DivkqBuC+Jdq7BYTXwqKai5G5ejPpuEDKpWzi1Oxp+LygGw329FrKpuKeiqpcymlqJTjmy+quWng== + dependencies: + "@babel/runtime" "^7.2.0" + prop-types "^15.6.0" + react-is "^16.6.3" + "@mrmlnc/readdir-enhanced@^2.2.1": version "2.2.1" resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" @@ -1085,6 +1145,14 @@ resolved "https://registry.yarnpkg.com/@tweenjs/tween.js/-/tween.js-17.3.0.tgz#1cbea14b4b5baa292274c96766fd1b2a56b7ca9f" integrity sha512-SPkhNj9/wGfbdX2C3B3KhttLQ4iesd+Ny8Dv1RnqF1MFUIqsZz/OJVLzJEHSEl7zheNx70dvqrwfbCFDQ0sWBw== +"@types/jss@^9.5.6": + version "9.5.8" + resolved "https://registry.yarnpkg.com/@types/jss/-/jss-9.5.8.tgz#258391f42211c042fc965508d505cbdc579baa5b" + integrity sha512-bBbHvjhm42UKki+wZpR89j73ykSXg99/bhuKuYYePtpma3ZAnmeGnl0WxXiZhPGsIfzKwCUkpPC0jlrVMBfRxA== + dependencies: + csstype "^2.0.0" + indefinite-observable "^1.0.1" + "@types/node@*": version "11.10.4" resolved "https://registry.yarnpkg.com/@types/node/-/node-11.10.4.tgz#3f5fc4f0f322805f009e00ab35a2ff3d6b778e42" @@ -1100,6 +1168,21 @@ resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.1.tgz#48fd98c1561fe718b61733daed46ff115b496e18" integrity sha512-eqz8c/0kwNi/OEHQfvIuJVLTst3in0e7uTKeuY+WL/zfKn0xVujOTp42bS/vUUokhK5P2BppLd9JXMOMHcgbjA== +"@types/react-transition-group@^2.0.8": + version "2.0.16" + resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-2.0.16.tgz#2dcb9e396ab385ee19c4af1c9caa469a14cd042f" + integrity sha512-FUJEx2BGJPU1qVQoWd9v7wpOwnCPTWhcE4iTaU5prry9SvwiI11lCXOci8Nz9cM/Fuf650l7Skg6nlVeCYjPFA== + dependencies: + "@types/react" "*" + +"@types/react@*": + version "16.8.8" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.8.8.tgz#4b60a469fd2469f7aa6eaa0f8cfbc51f6d76e662" + integrity sha512-xwEvyet96u7WnB96kqY0yY7qxx/pEpU51QeACkKFtrgjjXITQn0oO1iwPEraXVgh10ZFPix7gs1R4OJXF7P5sg== + dependencies: + "@types/prop-types" "*" + csstype "^2.2.0" + "@types/react@^16.8.2": version "16.8.7" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.8.7.tgz#7b1c0223dd5494f9b4501ad2a69aa6acb350a29b" @@ -1592,7 +1675,7 @@ arrify@^1.0.1: resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= -asap@~2.0.6: +asap@~2.0.3, asap@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= @@ -2173,6 +2256,11 @@ braces@^2.3.1, braces@^2.3.2: split-string "^3.0.2" to-regex "^3.0.1" +brcast@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/brcast/-/brcast-3.0.1.tgz#6256a8349b20de9eed44257a9b24d71493cd48dd" + integrity sha512-eI3yqf9YEqyGl9PCNTR46MGvDylGtaHjalcz6Q3fAPnP/PhpKkkve52vFdfGpwp4VUvK6LUr4TQN+2stCrEwTg== + bresenham@0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/bresenham/-/bresenham-0.0.3.tgz#abdab9e5b194e27c757cd314d8444314f299877a" @@ -2516,6 +2604,11 @@ chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" +change-emitter@^0.1.2: + version "0.1.6" + resolved "https://registry.yarnpkg.com/change-emitter/-/change-emitter-0.1.6.tgz#e8b2fe3d7f1ab7d69a32199aff91ea6931409515" + integrity sha1-6LL+PX8at9aaMhma/5HqaTFAlRU= + chardet@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" @@ -2590,7 +2683,7 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -classnames@^2.2.6: +classnames@^2.2.5, classnames@^2.2.6: version "2.2.6" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q== @@ -2897,6 +2990,11 @@ core-js@2.6.4: resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.4.tgz#b8897c062c4d769dd30a0ac5c73976c47f92ea0d" integrity sha512-05qQ5hXShcqGkPZpXEFLIpxayZscVD2kuMBZewxiIPPEagukO4mqgPA9CWhUvFBJfy3ODdK2p9xyHh7FTU9/7A== +core-js@^1.0.0: + version "1.2.7" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" + integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY= + core-js@^2.4.0, core-js@^2.5.0, core-js@^2.5.7: version "2.6.5" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.5.tgz#44bc8d249e7fb2ff5d00e0341a7ffb94fbf67895" @@ -3137,6 +3235,13 @@ css-url-regex@^1.1.0: resolved "https://registry.yarnpkg.com/css-url-regex/-/css-url-regex-1.1.0.tgz#83834230cc9f74c457de59eebd1543feeb83b7ec" integrity sha1-g4NCMMyfdMRX3lnuvRVD/uuDt+w= +css-vendor@^0.3.8: + version "0.3.8" + resolved "https://registry.yarnpkg.com/css-vendor/-/css-vendor-0.3.8.tgz#6421cfd3034ce664fe7673972fd0119fc28941fa" + integrity sha1-ZCHP0wNM5mT+dnOXL9ARn8KJQfo= + dependencies: + is-in-browser "^1.0.2" + css-what@2.1, css-what@^2.1.2: version "2.1.3" resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" @@ -3244,7 +3349,7 @@ cssstyle@^1.0.0: dependencies: cssom "0.3.x" -csstype@^2.2.0, csstype@^2.5.5: +csstype@^2.0.0, csstype@^2.2.0, csstype@^2.5.2, csstype@^2.5.5: version "2.6.3" resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.3.tgz#b701e5968245bf9b08d54ac83d00b624e622a9fa" integrity sha512-rINUZXOkcBmoHWEyu7JdHu5JMzkGRoMX4ov9830WNgxf5UYxcBUO0QTKAqeJ5EZfSdlrcJYkC8WwfVW7JYi4yg== @@ -3268,7 +3373,7 @@ cyclist@~0.2.2: resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640" integrity sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA= -d3-array@^1.2.0: +d3-array@^1.2.0, d3-array@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-1.2.4.tgz#635ce4d5eea759f6f605863dbcfc30edc737f71f" integrity sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw== @@ -3447,6 +3552,11 @@ deep-is@~0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= +deepmerge@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-3.2.0.tgz#58ef463a57c08d376547f8869fdc5bcee957f44e" + integrity sha512-6+LuZGU7QCNUnAJyX8cIrlzoEgggTM6B7mm+znKOX4t5ltluT9KLjN6g61ECMS0LTsLW7yDpNoxhix5FZcrIow== + default-gateway@^2.6.0: version "2.7.2" resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-2.7.2.tgz#b7ef339e5e024b045467af403d50348db4642d0f" @@ -3625,6 +3735,13 @@ dom-converter@^0.2: dependencies: utila "~0.4" +dom-helpers@^3.2.1, dom-helpers@^3.3.1: + version "3.4.0" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.4.0.tgz#e9b369700f959f62ecde5a6babde4bccd9169af8" + integrity sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA== + dependencies: + "@babel/runtime" "^7.1.2" + dom-serializer@0: version "0.1.1" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0" @@ -3787,6 +3904,13 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= +encoding@^0.1.11: + version "0.1.12" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" + integrity sha1-U4tm8+5izRq1HsMjgp0flIDHS+s= + dependencies: + iconv-lite "~0.4.13" + end-of-stream@^1.0.0, end-of-stream@^1.1.0: version "1.4.1" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" @@ -4361,6 +4485,19 @@ fb-watchman@^2.0.0: dependencies: bser "^2.0.0" +fbjs@^0.8.1: + version "0.8.17" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd" + integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90= + dependencies: + core-js "^1.0.0" + isomorphic-fetch "^2.1.1" + loose-envify "^1.0.0" + object-assign "^4.1.0" + promise "^7.1.1" + setimmediate "^1.0.5" + ua-parser-js "^0.7.18" + fenwick-tree@^0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/fenwick-tree/-/fenwick-tree-0.0.0.tgz#4693b8a46c55d3cb5bcfc763e37a42718424e0a1" @@ -5116,11 +5253,18 @@ hoek@4.x.x: resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb" integrity sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA== -hoist-non-react-statics@^2.5.0: +hoist-non-react-statics@^2.3.1, hoist-non-react-statics@^2.5.0: version "2.5.5" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47" integrity sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw== +hoist-non-react-statics@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz#b09178f0122184fb95acf525daaecb4d8f45958b" + integrity sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA== + dependencies: + react-is "^16.7.0" + home-or-tmp@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" @@ -5304,7 +5448,7 @@ iconv-lite@0.4.23: dependencies: safer-buffer ">= 2.1.2 < 3" -iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4: +iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -5433,6 +5577,13 @@ in-publish@^2.0.0: resolved "https://registry.yarnpkg.com/in-publish/-/in-publish-2.0.0.tgz#e20ff5e3a2afc2690320b6dc552682a9c7fadf51" integrity sha1-4g/146KvwmkDILbcVSaCqcf631E= +indefinite-observable@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/indefinite-observable/-/indefinite-observable-1.0.2.tgz#0a328793ab2385d4b9dca23eaab4afe6936a73f8" + integrity sha512-Mps0898zEduHyPhb7UCgNmfzlqNZknVmaFz5qzr0mm04YQ5FGLhAyK/dJ+NaRxGyR6juQXIxh5Ev0xx+qq0nYA== + dependencies: + symbol-observable "1.2.0" + indent-string@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" @@ -5762,6 +5913,11 @@ is-glob@^4.0.0: dependencies: is-extglob "^2.1.1" +is-in-browser@^1.0.2, is-in-browser@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835" + integrity sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU= + is-installed-globally@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80" @@ -5877,7 +6033,7 @@ is-root@2.0.0: resolved "https://registry.yarnpkg.com/is-root/-/is-root-2.0.0.tgz#838d1e82318144e5a6f77819d90207645acc7019" integrity sha512-F/pJIk8QD6OX5DNhRB7hWamLsUilmkDGho48KbgZ6xg/lmAZXHxzXQ91jzB3yRSw5kdQGGGc4yz8HYhTYIMWPg== -is-stream@^1.0.0, is-stream@^1.1.0: +is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= @@ -5950,6 +6106,14 @@ isobject@^3.0.0, isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= +isomorphic-fetch@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" + integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk= + dependencies: + node-fetch "^1.0.1" + whatwg-fetch ">=0.10.0" + isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -6534,6 +6698,51 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" +jss-camel-case@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jss-camel-case/-/jss-camel-case-6.1.0.tgz#ccb1ff8d6c701c02a1fed6fb6fb6b7896e11ce44" + integrity sha512-HPF2Q7wmNW1t79mCqSeU2vdd/vFFGpkazwvfHMOhPlMgXrJDzdj9viA2SaHk9ZbD5pfL63a8ylp4++irYbbzMQ== + dependencies: + hyphenate-style-name "^1.0.2" + +jss-default-unit@^8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/jss-default-unit/-/jss-default-unit-8.0.2.tgz#cc1e889bae4c0b9419327b314ab1c8e2826890e6" + integrity sha512-WxNHrF/18CdoAGw2H0FqOEvJdREXVXLazn7PQYU7V6/BWkCV0GkmWsppNiExdw8dP4TU1ma1dT9zBNJ95feLmg== + +jss-global@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/jss-global/-/jss-global-3.0.0.tgz#e19e5c91ab2b96353c227e30aa2cbd938cdaafa2" + integrity sha512-wxYn7vL+TImyQYGAfdplg7yaxnPQ9RaXY/cIA8hawaVnmmWxDHzBK32u1y+RAvWboa3lW83ya3nVZ/C+jyjZ5Q== + +jss-nested@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/jss-nested/-/jss-nested-6.0.1.tgz#ef992b79d6e8f63d939c4397b9d99b5cbbe824ca" + integrity sha512-rn964TralHOZxoyEgeq3hXY8hyuCElnvQoVrQwKHVmu55VRDd6IqExAx9be5HgK0yN/+hQdgAXQl/GUrBbbSTA== + dependencies: + warning "^3.0.0" + +jss-props-sort@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/jss-props-sort/-/jss-props-sort-6.0.0.tgz#9105101a3b5071fab61e2d85ea74cc22e9b16323" + integrity sha512-E89UDcrphmI0LzmvYk25Hp4aE5ZBsXqMWlkFXS0EtPkunJkRr+WXdCNYbXbksIPnKlBenGB9OxzQY+mVc70S+g== + +jss-vendor-prefixer@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/jss-vendor-prefixer/-/jss-vendor-prefixer-7.0.0.tgz#0166729650015ef19d9f02437c73667231605c71" + integrity sha512-Agd+FKmvsI0HLcYXkvy8GYOw3AAASBUpsmIRvVQheps+JWaN892uFOInTr0DRydwaD91vSSUCU4NssschvF7MA== + dependencies: + css-vendor "^0.3.8" + +jss@^9.8.7: + version "9.8.7" + resolved "https://registry.yarnpkg.com/jss/-/jss-9.8.7.tgz#ed9763fc0f2f0260fc8260dac657af61e622ce05" + integrity sha512-awj3XRZYxbrmmrx9LUSj5pXSUfm12m8xzi/VKeqI1ZwWBtQ0kVPTs3vYs32t4rFw83CgFDukA8wKzOE9sMQnoQ== + dependencies: + is-in-browser "^1.1.3" + symbol-observable "^1.1.0" + warning "^3.0.0" + jsx-ast-utils@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.0.1.tgz#e801b1b39985e20fffc87b40e3748080e2dcac7f" @@ -7448,6 +7657,14 @@ node-emoji@^1.4.1: dependencies: lodash.toarray "^4.4.0" +node-fetch@^1.0.1: + version "1.7.3" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" + integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ== + dependencies: + encoding "^0.1.11" + is-stream "^1.0.1" + node-forge@0.7.5: version "0.7.5" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.5.tgz#6c152c345ce11c52f465c2abd957e8639cd674df" @@ -7672,6 +7889,11 @@ normalize-range@^0.1.2: resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= +normalize-scroll-left@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-scroll-left/-/normalize-scroll-left-0.1.2.tgz#6b79691ba79eb5fb107fa5edfbdc06b55caee2aa" + integrity sha512-F9YMRls0zCF6BFIE2YnXDRpHPpfd91nOIaNdDgrx5YMoPLo8Wqj+6jNXHQsYBavJeXP4ww8HCt0xQAKc5qk2Fg== + normalize-url@^1.0.0: version "1.9.1" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c" @@ -8321,6 +8543,11 @@ polished@^2.3.3: dependencies: "@babel/runtime" "^7.2.0" +popper.js@^1.14.1: + version "1.14.7" + resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.7.tgz#e31ec06cfac6a97a53280c3e55e4e0c860e7738e" + integrity sha512-4q1hNvoUre/8srWsH7hnoSJ5xVmIL4qgz+s4qf2TnJIMyZFUFMGH+9vE7mXynAlHSZ/NdTmmow86muD0myUkVQ== + portfinder@^1.0.9: version "1.0.20" resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.20.tgz#bea68632e54b2e13ab7b0c4775e9b41bf270e44a" @@ -9031,6 +9258,13 @@ promise@8.0.2: dependencies: asap "~2.0.6" +promise@^7.1.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" + integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== + dependencies: + asap "~2.0.3" + prompts@^0.1.9: version "0.1.14" resolved "https://registry.yarnpkg.com/prompts/-/prompts-0.1.14.tgz#a8e15c612c5c9ec8f8111847df3337c9cbd443b2" @@ -9039,7 +9273,7 @@ prompts@^0.1.9: kleur "^2.0.1" sisteransi "^0.1.1" -prop-types@^15.5.10, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.0: +prop-types@^15.5.10, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.0, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -9249,6 +9483,16 @@ react-app-rewired@^2.1.0: dotenv "^6.2.0" semver "^5.6.0" +react-compound-slider@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/react-compound-slider/-/react-compound-slider-1.2.1.tgz#0f127a9944cf860f2041b70035e96a2f1d39d2c6" + integrity sha512-N1cwnSkjuiXvVWo8kNDcQ/jiQK2KrybEhnf72kQYL0OcLc36WrlAu9xVo8Yz6c3xWCJI+WKFcFjmitI3FQKn9g== + dependencies: + "@babel/runtime" "^7.3.4" + d3-array "^1.2.4" + prop-types "^15.7.2" + warning "^3.0.0" + react-dev-utils@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-7.0.3.tgz#f1316cfffd792fd41b0c28ad5db86c1d74484d6f" @@ -9294,6 +9538,15 @@ react-error-overlay@^5.1.3: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-5.1.3.tgz#16fcbde75ed4dc6161dc6dc959b48e92c6ffa9ad" integrity sha512-GoqeM3Xadie7XUApXOjkY3Qhs8RkwB/Za4WMedBGrOKH1eTuKGyoAECff7jiVonJchOx6KZ9i8ILO5XIoHB+Tg== +react-event-listener@^0.6.2: + version "0.6.6" + resolved "https://registry.yarnpkg.com/react-event-listener/-/react-event-listener-0.6.6.tgz#758f7b991cad9086dd39fd29fad72127e1d8962a" + integrity sha512-+hCNqfy7o9wvO6UgjqFmBzARJS7qrNoda0VqzvOuioEpoEXKutiKuv92dSz6kP7rYLmyHPyYNLesi5t/aH1gfw== + dependencies: + "@babel/runtime" "^7.2.0" + prop-types "^15.6.0" + warning "^4.0.1" + react-force-graph-3d@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/react-force-graph-3d/-/react-force-graph-3d-1.6.1.tgz#d197027f10ff5389093ab5ffc4670e64d79d092f" @@ -9303,6 +9556,11 @@ react-force-graph-3d@^1.6.1: prop-types "^15.7.0" react-kapsule "^1.4.1" +react-is@^16.6.3, react-is@^16.7.0: + version "16.8.4" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.4.tgz#90f336a68c3a29a096a3d648ab80e87ec61482a2" + integrity sha512-PVadd+WaUDOAciICm/J1waJaSvgq+4rHE/K70j0PFqKhkTBsPv/82UGQJNXAngz1fOQLLxI6z1sEDmJDQhCTAA== + react-is@^16.8.1: version "16.8.3" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.3.tgz#4ad8b029c2a718fc0cfc746c8d4e1b7221e5387d" @@ -9313,6 +9571,11 @@ react-kapsule@^1.4.1: resolved "https://registry.yarnpkg.com/react-kapsule/-/react-kapsule-1.4.1.tgz#4dfef562d0757f43933e9959f4b57e418881bd8a" integrity sha512-YZRaxMWaotkWlHtgNgOxucDG+ulkJPAgBmw+RZLPNNm6WOofeW5fdXiR3ubKzhQZ8JdT0pNB6+fR3NK0T4Kong== +react-lifecycles-compat@^3.0.2, react-lifecycles-compat@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" + integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== + react-router-dom@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.3.1.tgz#4c2619fc24c4fa87c9fd18f4fb4a43fe63fbd5c6" @@ -9393,6 +9656,16 @@ react-scripts@2.1.5: optionalDependencies: fsevents "1.2.4" +react-transition-group@^2.2.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.6.1.tgz#abf4a95e2f13fb9ba83a970a896fedbc5c4856a2" + integrity sha512-9DHwCy0aOYEe35frlEN68N9ut/THDQBLnVoQuKTvzF4/s3tk7lqkefCqxK2Nv96fOh6JXk6tQtliygk6tl3bQA== + dependencies: + dom-helpers "^3.3.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + react-lifecycles-compat "^3.0.4" + react-use@^5.5.6: version "5.5.6" resolved "https://registry.yarnpkg.com/react-use/-/react-use-5.5.6.tgz#cf7f07bc323ab23a66aef0cb083d164ff08093cc" @@ -9519,6 +9792,18 @@ rebound@^0.1.0: resolved "https://registry.yarnpkg.com/rebound/-/rebound-0.1.0.tgz#0638c61a93666bb515a58a03e1cfb34021e88b72" integrity sha1-BjjGGpNma7UVpYoD4c+zQCHoi3I= +"recompose@0.28.0 - 0.30.0": + version "0.30.0" + resolved "https://registry.yarnpkg.com/recompose/-/recompose-0.30.0.tgz#82773641b3927e8c7d24a0d87d65aeeba18aabd0" + integrity sha512-ZTrzzUDa9AqUIhRk4KmVFihH0rapdCSMFXjhHbNrjAWxBuUD/guYlyysMnuHjlZC/KRiOKRtB4jf96yYSkKE8w== + dependencies: + "@babel/runtime" "^7.0.0" + change-emitter "^0.1.2" + fbjs "^0.8.1" + hoist-non-react-statics "^2.3.1" + react-lifecycles-compat "^3.0.2" + symbol-observable "^1.0.4" + reconnecting-websocket@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/reconnecting-websocket/-/reconnecting-websocket-3.2.2.tgz#8097514e926e9855e03c39e76efa2e3d1f371bee" @@ -10088,7 +10373,7 @@ set-value@^2.0.0: is-plain-object "^2.0.3" split-string "^3.0.1" -setimmediate@^1.0.4: +setimmediate@^1.0.4, setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= @@ -10712,6 +10997,11 @@ svgo@^1.0.0, svgo@^1.1.1: unquote "~1.1.1" util.promisify "~1.0.0" +symbol-observable@1.2.0, symbol-observable@^1.0.4, symbol-observable@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" + integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== + symbol-tree@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6" @@ -11116,6 +11406,11 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= +ua-parser-js@^0.7.18: + version "0.7.19" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.19.tgz#94151be4c0a7fb1d001af7022fdaca4642659e4b" + integrity sha512-T3PVJ6uz8i0HzPxOF9SWzWAlfN/DavlpQqepn22xgve/5QecC+XMCAtmUNnY7C9StehaV6exjUCI801lOI7QlQ== + uglify-js@3.4.x, uglify-js@^3.1.4: version "3.4.9" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3" @@ -11331,6 +11626,11 @@ use-events@^1.2.0: dependencies: resize-observer-polyfill "1.5.1" +use-fullscreen@^0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/use-fullscreen/-/use-fullscreen-0.0.5.tgz#ad18151cf5f91550ffabeb6740a6a469be9eeee2" + integrity sha512-t1UfX7jJOR5Jso3NiLn6j1J33e//Jmcp03It0IxrCI5QJ8E48TNIu6q1GaUciCrmiZmyb3EhnDlSPi5kVbgcwA== + use-onclickoutside@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/use-onclickoutside/-/use-onclickoutside-0.3.0.tgz#a7c6bb68c8643c9a3437e34840d0cb2b96ae32b1" @@ -11638,7 +11938,7 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3: dependencies: iconv-lite "0.4.24" -whatwg-fetch@3.0.0: +whatwg-fetch@3.0.0, whatwg-fetch@>=0.10.0: version "3.0.0" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==