From 502811f68acaeff18a4407a4329c1edb3a8e0f17 Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Fri, 3 Apr 2020 16:23:03 +0200 Subject: [PATCH] Extract some shite out into an options modal --- .env | 3 +- package.json | 6 +- src/App.css | 4 + src/mixer/MicCalibrationModal.tsx | 83 --------- src/mixer/state.ts | 7 +- src/optionsMenu/AboutTab.tsx | 13 ++ src/optionsMenu/MicTab.tsx | 159 ++++++++++++++++++ .../helpers}/VUMeter.tsx | 0 src/optionsMenu/index.tsx | 46 +++++ src/optionsMenu/state.ts | 59 +++++++ src/rootReducer.ts | 4 +- src/showplanner/index.tsx | 105 +++--------- src/store.ts | 23 ++- yarn.lock | 85 +++++++++- 14 files changed, 416 insertions(+), 181 deletions(-) delete mode 100644 src/mixer/MicCalibrationModal.tsx create mode 100644 src/optionsMenu/AboutTab.tsx create mode 100644 src/optionsMenu/MicTab.tsx rename src/{mixer => optionsMenu/helpers}/VUMeter.tsx (100%) create mode 100644 src/optionsMenu/index.tsx create mode 100644 src/optionsMenu/state.ts diff --git a/.env b/.env index 3ffe9d4..993341b 100644 --- a/.env +++ b/.env @@ -1 +1,2 @@ -HOST=local-development.ury.org.uk \ No newline at end of file +HOST=local-development.ury.org.uk +REACT_APP_VERSION=$npm_package_version \ No newline at end of file diff --git a/package.json b/package.json index 8f7e22d..f35dfee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "showplanner2", - "version": "0.1.0", + "name": "webstudio", + "version": "0.0.9", "private": true, "dependencies": { "@babel/core": "7.6.0", @@ -18,6 +18,7 @@ "@types/react-dom": "16.9.4", "@types/react-modal": "^3.10.5", "@types/react-redux": "^7.1.5", + "@types/reactstrap": "^8.4.2", "@types/wavesurfer.js": "^3.2.0", "@types/webpack-env": "^1.14.1", "@typescript-eslint/eslint-plugin": "^2.2.0", @@ -72,6 +73,7 @@ "react-dom": "^0.0.0-experimental-38dd17ab9", "react-modal": "^3.11.2", "react-redux": "^7.1.3", + "reactstrap": "^8.4.1", "redux": "^4.0.4", "resolve": "1.12.0", "resolve-url-loader": "3.1.0", diff --git a/src/App.css b/src/App.css index 5af2a77..4ba0f17 100644 --- a/src/App.css +++ b/src/App.css @@ -263,3 +263,7 @@ button{ 0% { } 50% { background-color: rgb(199, 255, 199); } } + +.ReactModal__Overlay { + z-index: 10000; +} \ No newline at end of file diff --git a/src/mixer/MicCalibrationModal.tsx b/src/mixer/MicCalibrationModal.tsx deleted file mode 100644 index caf035c..0000000 --- a/src/mixer/MicCalibrationModal.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import React, { useState, useEffect, useRef } from "react"; -import Modal from "react-modal"; -import { useSelector, useDispatch } from "react-redux"; -import { RootState } from "../rootReducer"; - -import * as MixerState from "./state"; -import { VUMeter } from "./VUMeter"; - -export function MicCalibrationModal() { - const state = useSelector((state: RootState) => state.mixer.mic); - const rafRef = useRef(null); - const [peak, setPeak] = useState(-Infinity); - - const animate = () => { - if (state.calibration) { - const result = MixerState.getMicAnalysis(); - setPeak(result); - rafRef.current = requestAnimationFrame(animate); - } else if (rafRef.current !== null) { - cancelAnimationFrame(rafRef.current); - rafRef.current = null; - } - }; - - useEffect(() => { - if (state.calibration) { - rafRef.current = requestAnimationFrame(animate); - } else if (rafRef.current !== null) { - cancelAnimationFrame(rafRef.current); - rafRef.current = null; - } - }, [state.calibration]); - const dispatch = useDispatch(); - return ( - dispatch(MixerState.stopMicCalibration())} - > - {state.calibration && ( - <> - - Speak into the microphone at a normal volume. Adjust the - gain slider until the bar below is green when you're - speaking. - -
- -
-
- - dispatch( - MixerState.setMicBaseGain( - parseFloat(e.target.value) - ) - ) - } - /> - {state.baseGain.toFixed(1)} -
- - - )} -
- ); -} diff --git a/src/mixer/state.ts b/src/mixer/state.ts index 9bd914f..f1f58cf 100644 --- a/src/mixer/state.ts +++ b/src/mixer/state.ts @@ -41,7 +41,7 @@ type PlayerStateEnum = "playing" | "paused" | "stopped"; type PlayerRepeatEnum = "none" | "one" | "all"; type VolumePresetEnum = "off" | "bed" | "full"; type MicVolumePresetEnum = "off" | "full"; -type MicErrorEnum = "NO_PERMISSION" | "NOT_SECURE_CONTEXT" | "UNKNOWN"; +export type MicErrorEnum = "NO_PERMISSION" | "NOT_SECURE_CONTEXT" | "UNKNOWN"; interface PlayerState { loadedItem: PlanItem | Track | AuxItem | null; @@ -660,6 +660,11 @@ export const openMicrophone = (micID: string): AppThunk => async ( .connect(micCompressor) .connect(finalCompressor); dispatch(mixerState.actions.micOpen(micID)); + + const state2 = getState(); + if (state2.optionsMenu.open && state2.optionsMenu.currentTab === "mic") { + dispatch(startMicCalibration()); + } }; export const setMicVolume = ( diff --git a/src/optionsMenu/AboutTab.tsx b/src/optionsMenu/AboutTab.tsx new file mode 100644 index 0000000..6da4e8d --- /dev/null +++ b/src/optionsMenu/AboutTab.tsx @@ -0,0 +1,13 @@ +import React from "react"; + +import logo from "../assets/images/webstudio.svg"; + +export function AboutTab() { + return ( + <> + +
WebStudio v{process.env.REACT_APP_VERSION}
+
Brought to you by URY Computing Team
+ + ); +} \ No newline at end of file diff --git a/src/optionsMenu/MicTab.tsx b/src/optionsMenu/MicTab.tsx new file mode 100644 index 0000000..46c84a0 --- /dev/null +++ b/src/optionsMenu/MicTab.tsx @@ -0,0 +1,159 @@ +import React, { useState, useEffect, useRef } from "react"; +import { useSelector, useDispatch } from "react-redux"; +import { RootState } from "../rootReducer"; + +import * as MixerState from "../mixer/state"; +import { VUMeter } from "./helpers/VUMeter"; + +type MicErrorEnum = + | "NO_PERMISSION" + | "NOT_SECURE_CONTEXT" + | "UNKNOWN" + | "UNKNOWN_ENUM"; + +function reduceToInputs(devices: MediaDeviceInfo[]) { + var temp: MediaDeviceInfo[] = []; + devices.forEach((device) => { + if (device.kind == "audioinput") { + temp.push(device); + } + }); + return temp; +} + +export function MicTab() { + const state = useSelector((state: RootState) => state.mixer.mic); + const [micList, setMicList] = useState(null); + const dispatch = useDispatch(); + const [nextMicSource, setNextMicSource] = useState("default"); + const [openError, setOpenError] = useState(null); + + async function fetchMicNames() { + // Because Chrome, we have to call getUserMedia() before enumerateDevices() + try { + await navigator.mediaDevices.getUserMedia({ audio: true }); + } catch (e) { + if (e instanceof DOMException) { + switch (e.message) { + case "Permission denied": + setOpenError("NO_PERMISSION"); + break; + default: + setOpenError("UNKNOWN"); + } + } else { + setOpenError("UNKNOWN"); + } + return; + } + try { + const devices = await navigator.mediaDevices.enumerateDevices(); + setMicList(reduceToInputs(devices)); + } catch (e) { + setOpenError("UNKNOWN_ENUM"); + } + } + + function setMicSource(sourceId: string) { + setNextMicSource(sourceId); + dispatch(MixerState.openMicrophone(sourceId)); + } + + const rafRef = useRef(null); + const [peak, setPeak] = useState(-Infinity); + + const animate = () => { + if (state.calibration) { + const result = MixerState.getMicAnalysis(); + setPeak(result); + rafRef.current = requestAnimationFrame(animate); + } else if (rafRef.current !== null) { + cancelAnimationFrame(rafRef.current); + rafRef.current = null; + } + }; + + useEffect(() => { + if (state.calibration) { + rafRef.current = requestAnimationFrame(animate); + } else if (rafRef.current !== null) { + cancelAnimationFrame(rafRef.current); + rafRef.current = null; + } + }, [state.calibration]); + + return ( + <> + + + {state.openError !== null && ( +
+ {state.openError === "NO_PERMISSION" || + openError === "NO_PERMISSION" + ? "Please grant this page permission to use your microphone and try again." + : state.openError === "NOT_SECURE_CONTEXT" || + openError === "NOT_SECURE_CONTEXT" + ? "We can't open the microphone. Please make sure the address bar has a https:// at the start and try again." + : openError === "UNKNOWN_ENUM" + ? "An error occurred when enumerating input devices. Please try again." + : "An error occurred when opening the microphone. Please try again."} +
+ )} + +
+

Calibration

+ + Speak into the microphone at a normal volume. Adjust the + gain slider until the bar below is green when you're + speaking. + +
+ +
+
+ + dispatch( + MixerState.setMicBaseGain( + parseFloat(e.target.value) + ) + ) + } + /> + {state.baseGain.toFixed(1)} +
+
+ + ); +} diff --git a/src/mixer/VUMeter.tsx b/src/optionsMenu/helpers/VUMeter.tsx similarity index 100% rename from src/mixer/VUMeter.tsx rename to src/optionsMenu/helpers/VUMeter.tsx diff --git a/src/optionsMenu/index.tsx b/src/optionsMenu/index.tsx new file mode 100644 index 0000000..af4a862 --- /dev/null +++ b/src/optionsMenu/index.tsx @@ -0,0 +1,46 @@ +import React from "react"; +import { Nav, TabContent, TabPane, NavItem, NavLink } from "reactstrap"; +import Modal from "react-modal"; +import { useSelector, useDispatch } from "react-redux"; +import { RootState } from "../rootReducer"; + +import * as OptionsState from "./state"; +import { MicTab } from "./MicTab"; +import { AboutTab } from "./AboutTab"; + +export function OptionsMenu() { + const state = useSelector((state: RootState) => state.optionsMenu); + const dispatch = useDispatch(); + return ( + dispatch(OptionsState.close())} + > + + + + + +
+ +
+
+ ); +} diff --git a/src/optionsMenu/state.ts b/src/optionsMenu/state.ts new file mode 100644 index 0000000..4e5d28c --- /dev/null +++ b/src/optionsMenu/state.ts @@ -0,0 +1,59 @@ +import { + createSlice, + PayloadAction, + Middleware, + Dispatch, +} from "@reduxjs/toolkit"; +import { RootState } from "../rootReducer"; + +import * as MixerState from "../mixer/state"; + +export type OptionsTabIDsEnum = "mic" | "about"; + +const optionsMenuState = createSlice({ + name: "optionsMenu", + initialState: { + open: false, + currentTab: "mic" as OptionsTabIDsEnum, + }, + reducers: { + open(state) { + state.open = true; + }, + openToTab(state, action: PayloadAction) { + state.open = true; + state.currentTab = action.payload; + }, + close(state) { + state.open = false; + }, + changeTab(state, action: PayloadAction) { + state.currentTab = action.payload; + }, + }, +}); + +export default optionsMenuState.reducer; + +export const { open, openToTab, close, changeTab } = optionsMenuState.actions; + +export const tabSyncMiddleware: Middleware<{}, RootState, Dispatch> = ( + store +) => (next) => (action) => { + const oldState = store.getState(); + const result = next(action); + const newState = store.getState(); + if ( + newState.optionsMenu.currentTab === "mic" + ) { + if (oldState.optionsMenu.currentTab !== "mic" && newState.optionsMenu.open) { + store.dispatch(MixerState.startMicCalibration() as any); + } + } else if ( + oldState.optionsMenu.currentTab === "mic" || + oldState.optionsMenu.open !== newState.optionsMenu.open + ) { + store.dispatch(MixerState.stopMicCalibration() as any); + } + return result; +}; diff --git a/src/rootReducer.ts b/src/rootReducer.ts index 34e4acc..d31f13e 100644 --- a/src/rootReducer.ts +++ b/src/rootReducer.ts @@ -5,13 +5,15 @@ import MixerReducer from "./mixer/state"; import BroadcastReducer from "./broadcast/state"; import sessionReducer from "./session/state"; import NavbarReducer from "./navbar/state"; +import OptionsMenuReducer from "./optionsMenu/state"; const rootReducer = combineReducers({ showplan: ShowplanReducer, mixer: MixerReducer, broadcast: BroadcastReducer, session: sessionReducer, - navbar: NavbarReducer + navbar: NavbarReducer, + optionsMenu: OptionsMenuReducer }); export type RootState = ReturnType; diff --git a/src/showplanner/index.tsx b/src/showplanner/index.tsx index c6f8006..18488e4 100644 --- a/src/showplanner/index.tsx +++ b/src/showplanner/index.tsx @@ -1,17 +1,15 @@ import React, { useState, useReducer, useEffect, memo } from "react"; import { ContextMenu, MenuItem } from "react-contextmenu"; import { useBeforeunload } from "react-beforeunload"; -import { MYRADIO_NON_API_BASE } from "../api" +import { MYRADIO_NON_API_BASE } from "../api"; -import { - TimeslotItem, -} from "../api"; +import { TimeslotItem } from "../api"; import { Droppable, DragDropContext, DropResult, - ResponderProvided + ResponderProvided, } from "react-beautiful-dnd"; import { useSelector, useDispatch } from "react-redux"; @@ -22,23 +20,24 @@ import { itemId, moveItem, addItem, - removeItem + removeItem, } from "./state"; import * as MixerState from "../mixer/state"; import * as BroadcastState from "../broadcast/state"; +import * as OptionsMenuState from "../optionsMenu/state"; import { Item, TS_ITEM_MENU_ID } from "./Item"; import { CentralMusicLibrary, CML_CACHE, AuxLibrary, - AUX_CACHE + AUX_CACHE, } from "./libraries"; import { Player, USE_REAL_GAIN_VALUE } from "./Player"; -import { MicCalibrationModal } from "../mixer/MicCalibrationModal"; import { timestampToDateTime } from "../lib/utils"; import { CombinedNavAlertBar } from "../navbar"; +import { OptionsMenu } from "../optionsMenu"; function Column({ id, data }: { id: number; data: PlanItem[] }) { return ( @@ -54,7 +53,7 @@ function Column({ id, data }: { id: number; data: PlanItem[] }) { {typeof data[id] === "undefined" ? null : data - .filter(x => x.channel === id) + .filter((x) => x.channel === id) .sort((a, b) => a.weight - b.weight) .map((x, index) => ( setSauce(e.target.value)} + onChange={(e) => setSauce(e.target.value)} > - {Object.keys(AUX_LIBRARIES).map(libId => ( + {Object.keys(AUX_LIBRARIES).map((libId) => ( @@ -128,74 +127,17 @@ function LibraryColumn() { function MicControl() { const state = useSelector((state: RootState) => state.mixer.mic); - const [micList, setMicList] = useState([]); const dispatch = useDispatch(); - const [nextMicSource, setNextMicSource] = useState("default") // next mic source - const [lock, setLock] = useState(false) - - useEffect(()=>{ - navigator.mediaDevices.enumerateDevices() - .then((devices)=>{ - setMicList(reduceToInputs(devices)) - }) - .catch(() => {console.log("Could not fetch devices");}) - }, []) - - function reduceToInputs(devices:MediaDeviceInfo[]){ - var temp: MediaDeviceInfo[] = [] - devices.forEach((device)=>{ - if (device.kind == "audioinput") { - temp.push(device) - } - }) - return temp - } - - function toggleCheck(){setLock(!lock)} return (

Microphone

- -
- - -
- - - {state.openError !== null && ( -
- {state.openError === "NO_PERMISSION" - ? "Please grant this page permission to use your microphone and try again." - : state.openError === "NOT_SECURE_CONTEXT" - ? "We can't open the microphone. Please make sure the address bar has a https:// at the start and try again." - : "An error occurred when opening the microphone. Please try again."} -
- )}
+
+ +
); } @@ -219,25 +164,25 @@ const Showplanner: React.FC<{ timeslotId: number }> = function({ timeslotId }) { planLoadError, planLoading, planSaveError, - planSaving + planSaving, } = useSelector((state: RootState) => state.showplan); const dispatch = useDispatch(); - useBeforeunload(event => event.preventDefault()); + useBeforeunload((event) => event.preventDefault()); useEffect(() => { dispatch(getShowplan(timeslotId)); }, [dispatch, timeslotId]); - - function toggleSidebar() { var element = document.getElementById("sidebar"); if (element) { element.classList.toggle("active"); } - setTimeout(function () {dispatch(MixerState.redrawWavesurfers())}, 500); + setTimeout(function() { + dispatch(MixerState.redrawWavesurfers()); + }, 500); } const [insertIndex, increment] = useReducer(incrReducer, 0); @@ -259,7 +204,7 @@ const Showplanner: React.FC<{ timeslotId: number }> = function({ timeslotId }) { timeslotitemid: "I" + insertIndex, channel: parseInt(result.destination.droppableId, 10), weight: result.destination.index, - ...data + ...data, }; dispatch(addItem(timeslotId, newItem)); increment(null); @@ -273,7 +218,7 @@ const Showplanner: React.FC<{ timeslotId: number }> = function({ timeslotId }) { channel: parseInt(result.destination.droppableId, 10), weight: result.destination.index, clean: true, - ...data + ...data, }; dispatch(addItem(timeslotId, newItem)); increment(null); @@ -282,7 +227,7 @@ const Showplanner: React.FC<{ timeslotId: number }> = function({ timeslotId }) { dispatch( moveItem(timeslotId, result.draggableId, [ parseInt(result.destination.droppableId, 10), - result.destination.index + result.destination.index, ]) ); } @@ -344,7 +289,7 @@ const Showplanner: React.FC<{ timeslotId: number }> = function({ timeslotId }) { Remove - + ); }; diff --git a/src/store.ts b/src/store.ts index 231a0d5..1ab7278 100644 --- a/src/store.ts +++ b/src/store.ts @@ -1,18 +1,27 @@ import rootReducer, { RootState } from "./rootReducer"; import { configureStore, Action, getDefaultMiddleware } from "@reduxjs/toolkit"; import { ThunkAction } from "redux-thunk"; -import { mixerMiddleware, mixerKeyboardShortcutsMiddleware } from "./mixer/state"; +import { + mixerMiddleware, + mixerKeyboardShortcutsMiddleware, +} from "./mixer/state"; +import { tabSyncMiddleware } from "./optionsMenu/state"; const store = configureStore({ - reducer: rootReducer, - middleware: [...getDefaultMiddleware(), mixerMiddleware, mixerKeyboardShortcutsMiddleware] + reducer: rootReducer, + middleware: [ + mixerMiddleware, + mixerKeyboardShortcutsMiddleware, + tabSyncMiddleware, + ...getDefaultMiddleware(), + ], }); if (process.env.NODE_ENV === "development" && module.hot) { - module.hot.accept("./rootReducer", () => { - const newRootReducer = require("./rootReducer").default; - store.replaceReducer(newRootReducer); - }); + module.hot.accept("./rootReducer", () => { + const newRootReducer = require("./rootReducer").default; + store.replaceReducer(newRootReducer); + }); } export type AppDispatch = typeof store.dispatch; diff --git a/yarn.lock b/yarn.lock index 1260746..c4793bc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -914,7 +914,7 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": +"@babel/runtime@^7.1.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": version "7.9.2" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.2.tgz#d90df0583a3a252f09aaa619665367bae518db06" integrity sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q== @@ -1523,6 +1523,14 @@ "@types/prop-types" "*" csstype "^2.2.0" +"@types/reactstrap@^8.4.2": + version "8.4.2" + resolved "https://registry.yarnpkg.com/@types/reactstrap/-/reactstrap-8.4.2.tgz#e7066d0e67e2924dab0a52c6aedcf922f2be53b6" + integrity sha512-ag4hfFqBZaeoNSSTKjCtedvdcO68QqqlBrFd3obg94JSmhgNTmHz50BvNJkf9NjSzx1yGTW4l/OyP/khLPKqww== + dependencies: + "@types/react" "*" + popper.js "^1.14.1" + "@types/resolve@0.0.8": version "0.0.8" resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-0.0.8.tgz#f26074d238e02659e323ce1a13d041eee280e194" @@ -2809,7 +2817,7 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -classnames@^2.2.5: +classnames@^2.2.3, classnames@^2.2.5: version "2.2.6" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q== @@ -3196,6 +3204,14 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: safe-buffer "^5.0.1" sha.js "^2.4.8" +create-react-context@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/create-react-context/-/create-react-context-0.3.0.tgz#546dede9dc422def0d3fc2fe03afe0bc0f4f7d8c" + integrity sha512-dNldIoSuNSvlTJ7slIKC/ZFGKexBMBrrcc+TTe1NdmROnaASuLPvqpwj9v4XS4uXZ8+YPu0sNmShX2rXI5LNsw== + dependencies: + gud "^1.0.0" + warning "^4.0.3" + cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -3535,7 +3551,7 @@ decode-uri-component@^0.2.0: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= -deep-equal@^1.0.1: +deep-equal@^1.0.1, deep-equal@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g== @@ -3734,6 +3750,13 @@ dom-converter@^0.2: dependencies: utila "~0.4" +dom-helpers@^3.4.0: + 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.2.2" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" @@ -4883,6 +4906,11 @@ growly@^1.3.0: resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= +gud@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0" + integrity sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw== + gzip-size@5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274" @@ -7748,6 +7776,11 @@ pnp-webpack-plugin@1.5.0: dependencies: ts-pnp "^1.1.2" +popper.js@^1.14.1, popper.js@^1.14.4: + version "1.16.1" + resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b" + integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ== + portfinder@^1.0.9: version "1.0.25" resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.25.tgz#254fd337ffba869f4b9d37edc298059cb4d35eca" @@ -8509,7 +8542,7 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.4" -prop-types@^15.5.10, prop-types@^15.6.2, prop-types@^15.7.2: +prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.1, prop-types@^15.6.2, 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== @@ -8787,7 +8820,7 @@ react-is@^16.12.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-i resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -react-lifecycles-compat@^3.0.0: +react-lifecycles-compat@^3.0.0, 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== @@ -8802,6 +8835,19 @@ react-modal@^3.11.2: react-lifecycles-compat "^3.0.0" warning "^4.0.3" +react-popper@^1.3.6: + version "1.3.7" + resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-1.3.7.tgz#f6a3471362ef1f0d10a4963673789de1baca2324" + integrity sha512-nmqYTx7QVjCm3WUZLeuOomna138R1luC4EqkW3hxJUrAe+3eNz3oFCLYdnPwILfn0mX1Ew2c3wctrjlUMYYUww== + dependencies: + "@babel/runtime" "^7.1.2" + create-react-context "^0.3.0" + deep-equal "^1.1.1" + popper.js "^1.14.4" + prop-types "^15.6.1" + typed-styles "^0.0.7" + warning "^4.0.2" + react-redux@^7.1.1, react-redux@^7.1.3: version "7.2.0" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.0.tgz#f970f62192b3981642fec46fd0db18a074fe879d" @@ -8813,6 +8859,16 @@ react-redux@^7.1.1, react-redux@^7.1.3: prop-types "^15.7.2" react-is "^16.9.0" +react-transition-group@^2.3.1: + version "2.9.0" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.9.0.tgz#df9cdb025796211151a436c69a8f3b97b5b07c8d" + integrity sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg== + dependencies: + dom-helpers "^3.4.0" + loose-envify "^1.4.0" + prop-types "^15.6.2" + react-lifecycles-compat "^3.0.4" + react@^0.0.0-experimental-38dd17ab9: version "0.0.0-fec00a869" resolved "https://registry.yarnpkg.com/react/-/react-0.0.0-fec00a869.tgz#1803f4f17cdd5adfdf614de2386c5fb5c84bbe91" @@ -8823,6 +8879,18 @@ react@^0.0.0-experimental-38dd17ab9: prop-types "^15.6.2" scheduler "0.0.0-fec00a869" +reactstrap@^8.4.1: + version "8.4.1" + resolved "https://registry.yarnpkg.com/reactstrap/-/reactstrap-8.4.1.tgz#c7f63b9057f58b52833061711ebe235b9ec4e3e5" + integrity sha512-oAjp9PYYUGKl7SLXwrQ1oRIrYw0MqfO2mUqYgGapFKHG2uwjEtLip5rYxtMujkGx3COjH5FX1WtcfNU4oqpH0Q== + dependencies: + "@babel/runtime" "^7.2.0" + classnames "^2.2.3" + prop-types "^15.5.8" + react-lifecycles-compat "^3.0.4" + react-popper "^1.3.6" + react-transition-group "^2.3.1" + read-pkg-up@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" @@ -10463,6 +10531,11 @@ type@^2.0.0: resolved "https://registry.yarnpkg.com/type/-/type-2.0.0.tgz#5f16ff6ef2eb44f260494dae271033b29c09a9c3" integrity sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow== +typed-styles@^0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/typed-styles/-/typed-styles-0.0.7.tgz#93392a008794c4595119ff62dde6809dbc40a3d9" + integrity sha512-pzP0PWoZUhsECYjABgCGQlRGL1n7tOHsgwYv3oIiEpJwGhFTuty/YNeduxQYzXXa3Ge5BdT6sHYIQYpl4uJ+5Q== + typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" @@ -10737,7 +10810,7 @@ walker@^1.0.7, walker@~1.0.5: dependencies: makeerror "1.0.x" -warning@^4.0.3: +warning@^4.0.2, warning@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==