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
1
.env
1
.env
|
@ -1 +1,2 @@
|
|||
HOST=local-development.ury.org.uk
|
||||
REACT_APP_VERSION=$npm_package_version
|
|
@ -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",
|
||||
|
|
|
@ -263,3 +263,7 @@ button{
|
|||
0% { }
|
||||
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 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 = (
|
||||
|
|
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 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<typeof rootReducer>;
|
||||
|
|
|
@ -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) => (
|
||||
<Item
|
||||
|
@ -88,7 +87,7 @@ const AUX_LIBRARIES: { [key: string]: string } = {
|
|||
"aux-12": "Roses 2018",
|
||||
"aux-10": "Sound Effects",
|
||||
"aux-8": "Speech",
|
||||
"aux-9": "Teasers"
|
||||
"aux-9": "Teasers",
|
||||
};
|
||||
|
||||
function LibraryColumn() {
|
||||
|
@ -99,14 +98,14 @@ function LibraryColumn() {
|
|||
className="form-control"
|
||||
style={{ width: "100%" }}
|
||||
value={sauce}
|
||||
onChange={e => setSauce(e.target.value)}
|
||||
onChange={(e) => setSauce(e.target.value)}
|
||||
>
|
||||
<option value={"None"} disabled>
|
||||
Choose a library
|
||||
</option>
|
||||
<option value={"CentralMusicLibrary"}>Central Music Library</option>
|
||||
<option disabled>Resources</option>
|
||||
{Object.keys(AUX_LIBRARIES).map(libId => (
|
||||
{Object.keys(AUX_LIBRARIES).map((libId) => (
|
||||
<option key={libId} value={libId}>
|
||||
{AUX_LIBRARIES[libId]}
|
||||
</option>
|
||||
|
@ -128,74 +127,17 @@ function LibraryColumn() {
|
|||
|
||||
function MicControl() {
|
||||
const state = useSelector((state: RootState) => state.mixer.mic);
|
||||
const [micList, setMicList] = useState<MediaDeviceInfo[]>([]);
|
||||
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 (
|
||||
<div className="sp-col" style={{ height: "48%", overflowY: "visible" }}>
|
||||
<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-backdrop"
|
||||
style={{
|
||||
width: (USE_REAL_GAIN_VALUE ? state.gain : state.volume) * 100 + "%"
|
||||
width:
|
||||
(USE_REAL_GAIN_VALUE ? state.gain : state.volume) * 100 + "%",
|
||||
}}
|
||||
></div>
|
||||
<button onClick={() => dispatch(MixerState.setMicVolume("off"))}>
|
||||
|
@ -205,6 +147,9 @@ function MicControl() {
|
|||
Full
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<button onClick={() => dispatch(OptionsMenuState.open())}>Options</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -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 }) {
|
|||
<ContextMenu id={TS_ITEM_MENU_ID}>
|
||||
<MenuItem onClick={onCtxRemoveClick}>Remove</MenuItem>
|
||||
</ContextMenu>
|
||||
<MicCalibrationModal />
|
||||
<OptionsMenu />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
13
src/store.ts
13
src/store.ts
|
@ -1,11 +1,20 @@
|
|||
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]
|
||||
middleware: [
|
||||
mixerMiddleware,
|
||||
mixerKeyboardShortcutsMiddleware,
|
||||
tabSyncMiddleware,
|
||||
...getDefaultMiddleware(),
|
||||
],
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV === "development" && module.hot) {
|
||||
|
|
85
yarn.lock
85
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==
|
||||
|
|
Loading…
Reference in a new issue