Extract some shite out into an options modal
This commit is contained in:
parent
5763295b76
commit
502811f68a
14 changed files with 416 additions and 181 deletions
3
.env
3
.env
|
@ -1 +1,2 @@
|
||||||
HOST=local-development.ury.org.uk
|
HOST=local-development.ury.org.uk
|
||||||
|
REACT_APP_VERSION=$npm_package_version
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "showplanner2",
|
"name": "webstudio",
|
||||||
"version": "0.1.0",
|
"version": "0.0.9",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "7.6.0",
|
"@babel/core": "7.6.0",
|
||||||
|
@ -18,6 +18,7 @@
|
||||||
"@types/react-dom": "16.9.4",
|
"@types/react-dom": "16.9.4",
|
||||||
"@types/react-modal": "^3.10.5",
|
"@types/react-modal": "^3.10.5",
|
||||||
"@types/react-redux": "^7.1.5",
|
"@types/react-redux": "^7.1.5",
|
||||||
|
"@types/reactstrap": "^8.4.2",
|
||||||
"@types/wavesurfer.js": "^3.2.0",
|
"@types/wavesurfer.js": "^3.2.0",
|
||||||
"@types/webpack-env": "^1.14.1",
|
"@types/webpack-env": "^1.14.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^2.2.0",
|
"@typescript-eslint/eslint-plugin": "^2.2.0",
|
||||||
|
@ -72,6 +73,7 @@
|
||||||
"react-dom": "^0.0.0-experimental-38dd17ab9",
|
"react-dom": "^0.0.0-experimental-38dd17ab9",
|
||||||
"react-modal": "^3.11.2",
|
"react-modal": "^3.11.2",
|
||||||
"react-redux": "^7.1.3",
|
"react-redux": "^7.1.3",
|
||||||
|
"reactstrap": "^8.4.1",
|
||||||
"redux": "^4.0.4",
|
"redux": "^4.0.4",
|
||||||
"resolve": "1.12.0",
|
"resolve": "1.12.0",
|
||||||
"resolve-url-loader": "3.1.0",
|
"resolve-url-loader": "3.1.0",
|
||||||
|
|
|
@ -263,3 +263,7 @@ button{
|
||||||
0% { }
|
0% { }
|
||||||
50% { background-color: rgb(199, 255, 199); }
|
50% { background-color: rgb(199, 255, 199); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ReactModal__Overlay {
|
||||||
|
z-index: 10000;
|
||||||
|
}
|
|
@ -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<number | null>(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 (
|
|
||||||
<Modal
|
|
||||||
isOpen={state.calibration}
|
|
||||||
onRequestClose={() => dispatch(MixerState.stopMicCalibration())}
|
|
||||||
>
|
|
||||||
{state.calibration && (
|
|
||||||
<>
|
|
||||||
<b>
|
|
||||||
Speak into the microphone at a normal volume. Adjust the
|
|
||||||
gain slider until the bar below is green when you're
|
|
||||||
speaking.
|
|
||||||
</b>
|
|
||||||
<div>
|
|
||||||
<VUMeter
|
|
||||||
width={400}
|
|
||||||
height={40}
|
|
||||||
value={peak}
|
|
||||||
range={[-70, 0]}
|
|
||||||
greenRange={[-20, -7]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<input
|
|
||||||
type="range"
|
|
||||||
min={1 / 10}
|
|
||||||
max={3}
|
|
||||||
step={0.05}
|
|
||||||
value={state.gain}
|
|
||||||
onChange={e =>
|
|
||||||
dispatch(
|
|
||||||
MixerState.setMicBaseGain(
|
|
||||||
parseFloat(e.target.value)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<b>{state.baseGain.toFixed(1)}</b>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
onClick={() =>
|
|
||||||
dispatch(MixerState.stopMicCalibration())
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Stop
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -41,7 +41,7 @@ type PlayerStateEnum = "playing" | "paused" | "stopped";
|
||||||
type PlayerRepeatEnum = "none" | "one" | "all";
|
type PlayerRepeatEnum = "none" | "one" | "all";
|
||||||
type VolumePresetEnum = "off" | "bed" | "full";
|
type VolumePresetEnum = "off" | "bed" | "full";
|
||||||
type MicVolumePresetEnum = "off" | "full";
|
type MicVolumePresetEnum = "off" | "full";
|
||||||
type MicErrorEnum = "NO_PERMISSION" | "NOT_SECURE_CONTEXT" | "UNKNOWN";
|
export type MicErrorEnum = "NO_PERMISSION" | "NOT_SECURE_CONTEXT" | "UNKNOWN";
|
||||||
|
|
||||||
interface PlayerState {
|
interface PlayerState {
|
||||||
loadedItem: PlanItem | Track | AuxItem | null;
|
loadedItem: PlanItem | Track | AuxItem | null;
|
||||||
|
@ -660,6 +660,11 @@ export const openMicrophone = (micID: string): AppThunk => async (
|
||||||
.connect(micCompressor)
|
.connect(micCompressor)
|
||||||
.connect(finalCompressor);
|
.connect(finalCompressor);
|
||||||
dispatch(mixerState.actions.micOpen(micID));
|
dispatch(mixerState.actions.micOpen(micID));
|
||||||
|
|
||||||
|
const state2 = getState();
|
||||||
|
if (state2.optionsMenu.open && state2.optionsMenu.currentTab === "mic") {
|
||||||
|
dispatch(startMicCalibration());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setMicVolume = (
|
export const setMicVolume = (
|
||||||
|
|
13
src/optionsMenu/AboutTab.tsx
Normal file
13
src/optionsMenu/AboutTab.tsx
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import logo from "../assets/images/webstudio.svg";
|
||||||
|
|
||||||
|
export function AboutTab() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<img src={logo} style={{ filter: "invert(1)" }} />
|
||||||
|
<div><b>WebStudio v{process.env.REACT_APP_VERSION}</b></div>
|
||||||
|
<div>Brought to you by URY Computing Team</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
159
src/optionsMenu/MicTab.tsx
Normal file
159
src/optionsMenu/MicTab.tsx
Normal file
|
@ -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 | MediaDeviceInfo[]>(null);
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const [nextMicSource, setNextMicSource] = useState("default");
|
||||||
|
const [openError, setOpenError] = useState<null | MicErrorEnum>(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<number | null>(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 (
|
||||||
|
<>
|
||||||
|
<button onClick={fetchMicNames} disabled={micList !== null}>
|
||||||
|
Open
|
||||||
|
</button>
|
||||||
|
<select
|
||||||
|
className="form-control"
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
value={nextMicSource}
|
||||||
|
onChange={(e) => setMicSource(e.target.value)}
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
value={"None"}
|
||||||
|
disabled
|
||||||
|
label="Choose a microphone"
|
||||||
|
></option>
|
||||||
|
{(micList || []).map(function(e, i) {
|
||||||
|
return (
|
||||||
|
<option value={e.deviceId} key={i}>
|
||||||
|
{e.label !== "" ? e.label : e.deviceId}
|
||||||
|
</option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</select>
|
||||||
|
{state.openError !== null && (
|
||||||
|
<div className="sp-alert">
|
||||||
|
{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."}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div style={{ opacity: state.open ? 1 : 0.5 }}>
|
||||||
|
<h3>Calibration</h3>
|
||||||
|
<b>
|
||||||
|
Speak into the microphone at a normal volume. Adjust the
|
||||||
|
gain slider until the bar below is green when you're
|
||||||
|
speaking.
|
||||||
|
</b>
|
||||||
|
<div>
|
||||||
|
<VUMeter
|
||||||
|
width={400}
|
||||||
|
height={40}
|
||||||
|
value={peak}
|
||||||
|
range={[-70, 0]}
|
||||||
|
greenRange={[-20, -7]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min={1.0 / 10}
|
||||||
|
max={3}
|
||||||
|
step={0.05}
|
||||||
|
value={state.gain}
|
||||||
|
onChange={(e) =>
|
||||||
|
dispatch(
|
||||||
|
MixerState.setMicBaseGain(
|
||||||
|
parseFloat(e.target.value)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<b>{state.baseGain.toFixed(1)}</b>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
46
src/optionsMenu/index.tsx
Normal file
46
src/optionsMenu/index.tsx
Normal file
|
@ -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 (
|
||||||
|
<Modal
|
||||||
|
isOpen={state.open}
|
||||||
|
onRequestClose={() => dispatch(OptionsState.close())}
|
||||||
|
>
|
||||||
|
<Nav tabs>
|
||||||
|
<NavItem>
|
||||||
|
<NavLink
|
||||||
|
className={state.currentTab === "mic" ? "active" : ""}
|
||||||
|
onClick={() => dispatch(OptionsState.changeTab("mic"))}
|
||||||
|
>
|
||||||
|
Microphone
|
||||||
|
</NavLink>
|
||||||
|
</NavItem>
|
||||||
|
<NavItem>
|
||||||
|
<NavLink
|
||||||
|
className={state.currentTab === "about" ? "active" : ""}
|
||||||
|
onClick={() => dispatch(OptionsState.changeTab("about"))}
|
||||||
|
>
|
||||||
|
About
|
||||||
|
</NavLink>
|
||||||
|
</NavItem>
|
||||||
|
</Nav>
|
||||||
|
<TabContent activeTab={state.currentTab}>
|
||||||
|
<TabPane tabId="mic"><MicTab /></TabPane>
|
||||||
|
<TabPane tabId="about"><AboutTab /></TabPane>
|
||||||
|
</TabContent>
|
||||||
|
<footer>
|
||||||
|
<button onClick={() => dispatch(OptionsState.close())}>Exit</button>
|
||||||
|
</footer>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
59
src/optionsMenu/state.ts
Normal file
59
src/optionsMenu/state.ts
Normal file
|
@ -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<OptionsTabIDsEnum>) {
|
||||||
|
state.open = true;
|
||||||
|
state.currentTab = action.payload;
|
||||||
|
},
|
||||||
|
close(state) {
|
||||||
|
state.open = false;
|
||||||
|
},
|
||||||
|
changeTab(state, action: PayloadAction<OptionsTabIDsEnum>) {
|
||||||
|
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;
|
||||||
|
};
|
|
@ -5,13 +5,15 @@ import MixerReducer from "./mixer/state";
|
||||||
import BroadcastReducer from "./broadcast/state";
|
import BroadcastReducer from "./broadcast/state";
|
||||||
import sessionReducer from "./session/state";
|
import sessionReducer from "./session/state";
|
||||||
import NavbarReducer from "./navbar/state";
|
import NavbarReducer from "./navbar/state";
|
||||||
|
import OptionsMenuReducer from "./optionsMenu/state";
|
||||||
|
|
||||||
const rootReducer = combineReducers({
|
const rootReducer = combineReducers({
|
||||||
showplan: ShowplanReducer,
|
showplan: ShowplanReducer,
|
||||||
mixer: MixerReducer,
|
mixer: MixerReducer,
|
||||||
broadcast: BroadcastReducer,
|
broadcast: BroadcastReducer,
|
||||||
session: sessionReducer,
|
session: sessionReducer,
|
||||||
navbar: NavbarReducer
|
navbar: NavbarReducer,
|
||||||
|
optionsMenu: OptionsMenuReducer
|
||||||
});
|
});
|
||||||
|
|
||||||
export type RootState = ReturnType<typeof rootReducer>;
|
export type RootState = ReturnType<typeof rootReducer>;
|
||||||
|
|
|
@ -1,17 +1,15 @@
|
||||||
import React, { useState, useReducer, useEffect, memo } from "react";
|
import React, { useState, useReducer, useEffect, memo } from "react";
|
||||||
import { ContextMenu, MenuItem } from "react-contextmenu";
|
import { ContextMenu, MenuItem } from "react-contextmenu";
|
||||||
import { useBeforeunload } from "react-beforeunload";
|
import { useBeforeunload } from "react-beforeunload";
|
||||||
import { MYRADIO_NON_API_BASE } from "../api"
|
import { MYRADIO_NON_API_BASE } from "../api";
|
||||||
|
|
||||||
import {
|
import { TimeslotItem } from "../api";
|
||||||
TimeslotItem,
|
|
||||||
} from "../api";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Droppable,
|
Droppable,
|
||||||
DragDropContext,
|
DragDropContext,
|
||||||
DropResult,
|
DropResult,
|
||||||
ResponderProvided
|
ResponderProvided,
|
||||||
} from "react-beautiful-dnd";
|
} from "react-beautiful-dnd";
|
||||||
|
|
||||||
import { useSelector, useDispatch } from "react-redux";
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
|
@ -22,23 +20,24 @@ import {
|
||||||
itemId,
|
itemId,
|
||||||
moveItem,
|
moveItem,
|
||||||
addItem,
|
addItem,
|
||||||
removeItem
|
removeItem,
|
||||||
} from "./state";
|
} from "./state";
|
||||||
|
|
||||||
import * as MixerState from "../mixer/state";
|
import * as MixerState from "../mixer/state";
|
||||||
import * as BroadcastState from "../broadcast/state";
|
import * as BroadcastState from "../broadcast/state";
|
||||||
|
import * as OptionsMenuState from "../optionsMenu/state";
|
||||||
import { Item, TS_ITEM_MENU_ID } from "./Item";
|
import { Item, TS_ITEM_MENU_ID } from "./Item";
|
||||||
import {
|
import {
|
||||||
CentralMusicLibrary,
|
CentralMusicLibrary,
|
||||||
CML_CACHE,
|
CML_CACHE,
|
||||||
AuxLibrary,
|
AuxLibrary,
|
||||||
AUX_CACHE
|
AUX_CACHE,
|
||||||
} from "./libraries";
|
} from "./libraries";
|
||||||
import { Player, USE_REAL_GAIN_VALUE } from "./Player";
|
import { Player, USE_REAL_GAIN_VALUE } from "./Player";
|
||||||
import { MicCalibrationModal } from "../mixer/MicCalibrationModal";
|
|
||||||
|
|
||||||
import { timestampToDateTime } from "../lib/utils";
|
import { timestampToDateTime } from "../lib/utils";
|
||||||
import { CombinedNavAlertBar } from "../navbar";
|
import { CombinedNavAlertBar } from "../navbar";
|
||||||
|
import { OptionsMenu } from "../optionsMenu";
|
||||||
|
|
||||||
function Column({ id, data }: { id: number; data: PlanItem[] }) {
|
function Column({ id, data }: { id: number; data: PlanItem[] }) {
|
||||||
return (
|
return (
|
||||||
|
@ -54,7 +53,7 @@ function Column({ id, data }: { id: number; data: PlanItem[] }) {
|
||||||
{typeof data[id] === "undefined"
|
{typeof data[id] === "undefined"
|
||||||
? null
|
? null
|
||||||
: data
|
: data
|
||||||
.filter(x => x.channel === id)
|
.filter((x) => x.channel === id)
|
||||||
.sort((a, b) => a.weight - b.weight)
|
.sort((a, b) => a.weight - b.weight)
|
||||||
.map((x, index) => (
|
.map((x, index) => (
|
||||||
<Item
|
<Item
|
||||||
|
@ -88,7 +87,7 @@ const AUX_LIBRARIES: { [key: string]: string } = {
|
||||||
"aux-12": "Roses 2018",
|
"aux-12": "Roses 2018",
|
||||||
"aux-10": "Sound Effects",
|
"aux-10": "Sound Effects",
|
||||||
"aux-8": "Speech",
|
"aux-8": "Speech",
|
||||||
"aux-9": "Teasers"
|
"aux-9": "Teasers",
|
||||||
};
|
};
|
||||||
|
|
||||||
function LibraryColumn() {
|
function LibraryColumn() {
|
||||||
|
@ -99,14 +98,14 @@ function LibraryColumn() {
|
||||||
className="form-control"
|
className="form-control"
|
||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
value={sauce}
|
value={sauce}
|
||||||
onChange={e => setSauce(e.target.value)}
|
onChange={(e) => setSauce(e.target.value)}
|
||||||
>
|
>
|
||||||
<option value={"None"} disabled>
|
<option value={"None"} disabled>
|
||||||
Choose a library
|
Choose a library
|
||||||
</option>
|
</option>
|
||||||
<option value={"CentralMusicLibrary"}>Central Music Library</option>
|
<option value={"CentralMusicLibrary"}>Central Music Library</option>
|
||||||
<option disabled>Resources</option>
|
<option disabled>Resources</option>
|
||||||
{Object.keys(AUX_LIBRARIES).map(libId => (
|
{Object.keys(AUX_LIBRARIES).map((libId) => (
|
||||||
<option key={libId} value={libId}>
|
<option key={libId} value={libId}>
|
||||||
{AUX_LIBRARIES[libId]}
|
{AUX_LIBRARIES[libId]}
|
||||||
</option>
|
</option>
|
||||||
|
@ -128,74 +127,17 @@ function LibraryColumn() {
|
||||||
|
|
||||||
function MicControl() {
|
function MicControl() {
|
||||||
const state = useSelector((state: RootState) => state.mixer.mic);
|
const state = useSelector((state: RootState) => state.mixer.mic);
|
||||||
const [micList, setMicList] = useState<MediaDeviceInfo[]>([]);
|
|
||||||
const dispatch = useDispatch();
|
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 (
|
return (
|
||||||
<div className="sp-col" style={{ height: "48%", overflowY: "visible" }}>
|
<div className="sp-col" style={{ height: "48%", overflowY: "visible" }}>
|
||||||
<h2>Microphone</h2>
|
<h2>Microphone</h2>
|
||||||
<button
|
|
||||||
disabled={state.id == nextMicSource || lock}
|
|
||||||
onClick={() => dispatch(MixerState.openMicrophone(nextMicSource))}
|
|
||||||
>
|
|
||||||
Open
|
|
||||||
</button>
|
|
||||||
<div className="custom-control custom-checkbox">
|
|
||||||
<input className="custom-control-input" type="checkbox" id="micLock" onChange={toggleCheck}></input>
|
|
||||||
<label className="custom-control-label" htmlFor="micLock" style={{marginLeft:"8px"}}> Lock Microphone</label>
|
|
||||||
</div>
|
|
||||||
<select
|
|
||||||
className="form-control"
|
|
||||||
style={{ width: "100%" }}
|
|
||||||
value={nextMicSource}
|
|
||||||
onChange={e => setNextMicSource(e.target.value)}
|
|
||||||
>
|
|
||||||
<option value={"None"} disabled label="Choose a microphone"></option>
|
|
||||||
{
|
|
||||||
micList.map(function(e,i) {
|
|
||||||
return <option value={e.deviceId} key={i}>{e.label !== "" ? e.label : e.deviceId}</option>;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
<button disabled={!state.open} onClick={() => dispatch(MixerState.startMicCalibration())}>
|
|
||||||
Calibrate Trim
|
|
||||||
</button>
|
|
||||||
{state.openError !== null && (
|
|
||||||
<div className="sp-alert">
|
|
||||||
{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."}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="sp-mixer-buttons">
|
<div className="sp-mixer-buttons">
|
||||||
<div
|
<div
|
||||||
className="sp-mixer-buttons-backdrop"
|
className="sp-mixer-buttons-backdrop"
|
||||||
style={{
|
style={{
|
||||||
width: (USE_REAL_GAIN_VALUE ? state.gain : state.volume) * 100 + "%"
|
width:
|
||||||
|
(USE_REAL_GAIN_VALUE ? state.gain : state.volume) * 100 + "%",
|
||||||
}}
|
}}
|
||||||
></div>
|
></div>
|
||||||
<button onClick={() => dispatch(MixerState.setMicVolume("off"))}>
|
<button onClick={() => dispatch(MixerState.setMicVolume("off"))}>
|
||||||
|
@ -205,6 +147,9 @@ function MicControl() {
|
||||||
Full
|
Full
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<button onClick={() => dispatch(OptionsMenuState.open())}>Options</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -219,25 +164,25 @@ const Showplanner: React.FC<{ timeslotId: number }> = function({ timeslotId }) {
|
||||||
planLoadError,
|
planLoadError,
|
||||||
planLoading,
|
planLoading,
|
||||||
planSaveError,
|
planSaveError,
|
||||||
planSaving
|
planSaving,
|
||||||
} = useSelector((state: RootState) => state.showplan);
|
} = useSelector((state: RootState) => state.showplan);
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
useBeforeunload(event => event.preventDefault());
|
useBeforeunload((event) => event.preventDefault());
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(getShowplan(timeslotId));
|
dispatch(getShowplan(timeslotId));
|
||||||
}, [dispatch, timeslotId]);
|
}, [dispatch, timeslotId]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function toggleSidebar() {
|
function toggleSidebar() {
|
||||||
var element = document.getElementById("sidebar");
|
var element = document.getElementById("sidebar");
|
||||||
if (element) {
|
if (element) {
|
||||||
element.classList.toggle("active");
|
element.classList.toggle("active");
|
||||||
}
|
}
|
||||||
setTimeout(function () {dispatch(MixerState.redrawWavesurfers())}, 500);
|
setTimeout(function() {
|
||||||
|
dispatch(MixerState.redrawWavesurfers());
|
||||||
|
}, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
const [insertIndex, increment] = useReducer(incrReducer, 0);
|
const [insertIndex, increment] = useReducer(incrReducer, 0);
|
||||||
|
@ -259,7 +204,7 @@ const Showplanner: React.FC<{ timeslotId: number }> = function({ timeslotId }) {
|
||||||
timeslotitemid: "I" + insertIndex,
|
timeslotitemid: "I" + insertIndex,
|
||||||
channel: parseInt(result.destination.droppableId, 10),
|
channel: parseInt(result.destination.droppableId, 10),
|
||||||
weight: result.destination.index,
|
weight: result.destination.index,
|
||||||
...data
|
...data,
|
||||||
};
|
};
|
||||||
dispatch(addItem(timeslotId, newItem));
|
dispatch(addItem(timeslotId, newItem));
|
||||||
increment(null);
|
increment(null);
|
||||||
|
@ -273,7 +218,7 @@ const Showplanner: React.FC<{ timeslotId: number }> = function({ timeslotId }) {
|
||||||
channel: parseInt(result.destination.droppableId, 10),
|
channel: parseInt(result.destination.droppableId, 10),
|
||||||
weight: result.destination.index,
|
weight: result.destination.index,
|
||||||
clean: true,
|
clean: true,
|
||||||
...data
|
...data,
|
||||||
};
|
};
|
||||||
dispatch(addItem(timeslotId, newItem));
|
dispatch(addItem(timeslotId, newItem));
|
||||||
increment(null);
|
increment(null);
|
||||||
|
@ -282,7 +227,7 @@ const Showplanner: React.FC<{ timeslotId: number }> = function({ timeslotId }) {
|
||||||
dispatch(
|
dispatch(
|
||||||
moveItem(timeslotId, result.draggableId, [
|
moveItem(timeslotId, result.draggableId, [
|
||||||
parseInt(result.destination.droppableId, 10),
|
parseInt(result.destination.droppableId, 10),
|
||||||
result.destination.index
|
result.destination.index,
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -344,7 +289,7 @@ const Showplanner: React.FC<{ timeslotId: number }> = function({ timeslotId }) {
|
||||||
<ContextMenu id={TS_ITEM_MENU_ID}>
|
<ContextMenu id={TS_ITEM_MENU_ID}>
|
||||||
<MenuItem onClick={onCtxRemoveClick}>Remove</MenuItem>
|
<MenuItem onClick={onCtxRemoveClick}>Remove</MenuItem>
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
<MicCalibrationModal />
|
<OptionsMenu />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
23
src/store.ts
23
src/store.ts
|
@ -1,18 +1,27 @@
|
||||||
import rootReducer, { RootState } from "./rootReducer";
|
import rootReducer, { RootState } from "./rootReducer";
|
||||||
import { configureStore, Action, getDefaultMiddleware } from "@reduxjs/toolkit";
|
import { configureStore, Action, getDefaultMiddleware } from "@reduxjs/toolkit";
|
||||||
import { ThunkAction } from "redux-thunk";
|
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({
|
const store = configureStore({
|
||||||
reducer: rootReducer,
|
reducer: rootReducer,
|
||||||
middleware: [...getDefaultMiddleware(), mixerMiddleware, mixerKeyboardShortcutsMiddleware]
|
middleware: [
|
||||||
|
mixerMiddleware,
|
||||||
|
mixerKeyboardShortcutsMiddleware,
|
||||||
|
tabSyncMiddleware,
|
||||||
|
...getDefaultMiddleware(),
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (process.env.NODE_ENV === "development" && module.hot) {
|
if (process.env.NODE_ENV === "development" && module.hot) {
|
||||||
module.hot.accept("./rootReducer", () => {
|
module.hot.accept("./rootReducer", () => {
|
||||||
const newRootReducer = require("./rootReducer").default;
|
const newRootReducer = require("./rootReducer").default;
|
||||||
store.replaceReducer(newRootReducer);
|
store.replaceReducer(newRootReducer);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AppDispatch = typeof store.dispatch;
|
export type AppDispatch = typeof store.dispatch;
|
||||||
|
|
85
yarn.lock
85
yarn.lock
|
@ -914,7 +914,7 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime "^0.13.4"
|
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"
|
version "7.9.2"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.2.tgz#d90df0583a3a252f09aaa619665367bae518db06"
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.2.tgz#d90df0583a3a252f09aaa619665367bae518db06"
|
||||||
integrity sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==
|
integrity sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==
|
||||||
|
@ -1523,6 +1523,14 @@
|
||||||
"@types/prop-types" "*"
|
"@types/prop-types" "*"
|
||||||
csstype "^2.2.0"
|
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":
|
"@types/resolve@0.0.8":
|
||||||
version "0.0.8"
|
version "0.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-0.0.8.tgz#f26074d238e02659e323ce1a13d041eee280e194"
|
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"
|
isobject "^3.0.0"
|
||||||
static-extend "^0.1.1"
|
static-extend "^0.1.1"
|
||||||
|
|
||||||
classnames@^2.2.5:
|
classnames@^2.2.3, classnames@^2.2.5:
|
||||||
version "2.2.6"
|
version "2.2.6"
|
||||||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
|
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
|
||||||
integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
|
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"
|
safe-buffer "^5.0.1"
|
||||||
sha.js "^2.4.8"
|
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:
|
cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5:
|
||||||
version "6.0.5"
|
version "6.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
|
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"
|
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
|
||||||
integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=
|
integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=
|
||||||
|
|
||||||
deep-equal@^1.0.1:
|
deep-equal@^1.0.1, deep-equal@^1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a"
|
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a"
|
||||||
integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==
|
integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==
|
||||||
|
@ -3734,6 +3750,13 @@ dom-converter@^0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
utila "~0.4"
|
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:
|
dom-serializer@0:
|
||||||
version "0.2.2"
|
version "0.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51"
|
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"
|
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
|
||||||
integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=
|
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:
|
gzip-size@5.1.1:
|
||||||
version "5.1.1"
|
version "5.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274"
|
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:
|
dependencies:
|
||||||
ts-pnp "^1.1.2"
|
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:
|
portfinder@^1.0.9:
|
||||||
version "1.0.25"
|
version "1.0.25"
|
||||||
resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.25.tgz#254fd337ffba869f4b9d37edc298059cb4d35eca"
|
resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.25.tgz#254fd337ffba869f4b9d37edc298059cb4d35eca"
|
||||||
|
@ -8509,7 +8542,7 @@ prompts@^2.0.1:
|
||||||
kleur "^3.0.3"
|
kleur "^3.0.3"
|
||||||
sisteransi "^1.0.4"
|
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"
|
version "15.7.2"
|
||||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
|
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
|
||||||
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
|
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"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
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"
|
version "3.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
||||||
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
|
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
|
||||||
|
@ -8802,6 +8835,19 @@ react-modal@^3.11.2:
|
||||||
react-lifecycles-compat "^3.0.0"
|
react-lifecycles-compat "^3.0.0"
|
||||||
warning "^4.0.3"
|
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:
|
react-redux@^7.1.1, react-redux@^7.1.3:
|
||||||
version "7.2.0"
|
version "7.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.0.tgz#f970f62192b3981642fec46fd0db18a074fe879d"
|
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"
|
prop-types "^15.7.2"
|
||||||
react-is "^16.9.0"
|
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:
|
react@^0.0.0-experimental-38dd17ab9:
|
||||||
version "0.0.0-fec00a869"
|
version "0.0.0-fec00a869"
|
||||||
resolved "https://registry.yarnpkg.com/react/-/react-0.0.0-fec00a869.tgz#1803f4f17cdd5adfdf614de2386c5fb5c84bbe91"
|
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"
|
prop-types "^15.6.2"
|
||||||
scheduler "0.0.0-fec00a869"
|
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:
|
read-pkg-up@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02"
|
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"
|
resolved "https://registry.yarnpkg.com/type/-/type-2.0.0.tgz#5f16ff6ef2eb44f260494dae271033b29c09a9c3"
|
||||||
integrity sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow==
|
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:
|
typedarray@^0.0.6:
|
||||||
version "0.0.6"
|
version "0.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
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:
|
dependencies:
|
||||||
makeerror "1.0.x"
|
makeerror "1.0.x"
|
||||||
|
|
||||||
warning@^4.0.3:
|
warning@^4.0.2, warning@^4.0.3:
|
||||||
version "4.0.3"
|
version "4.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
|
resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
|
||||||
integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==
|
integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==
|
||||||
|
|
Loading…
Reference in a new issue