diff --git a/src/App.css b/src/App.css
index a36a337..68e0cfa 100644
--- a/src/App.css
+++ b/src/App.css
@@ -99,20 +99,42 @@ button {
flex: 1;
font-size: 0.8em;
}
+.timing-buttons .delete {
+ max-width: 3em;
+ color: gray;
+ padding-bottom: 2px;
+}
.timing-buttons .intro {
- border-color: rgba(125, 0, 255, 0.8);
- color: rgba(125, 0, 255, 0.8);
+ border-color: rgb(125, 0, 255);
+ color: rgb(125, 0, 255);
}
.timing-buttons .cue {
- border-color: rgba(0, 100, 0, 0.9);
- color: rgba(0, 100, 0, 0.9);
+ border-color: rgb(0, 100, 0);
+ color: rgb(0, 100, 0);
}
.timing-buttons .outro {
- border-color: rgba(255, 0, 0, 0.7);
- color: rgba(255, 0, 0, 0.7);
+ border-color: rgb(255, 0, 0);
+ color: rgb(255, 0, 0);
+}
+
+.timing-buttons.not-central .intro,
+.timing-buttons.not-central .outro {
+ opacity: 0.2;
+}
+
+.timing-buttons.text-light > * {
+ color: white;
+}
+
+.timing-buttons.text-light .intro {
+ border-color: rgb(179, 115, 248);
+}
+
+.timing-buttons.text-light .cue {
+ border-color: rgb(0, 255, 0);
}
.waveform span {
diff --git a/src/App.scss b/src/App.scss
index d1adc63..20c33c6 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -76,6 +76,8 @@ $number-of-channels: 3;
background: black;
canvas {
max-width: 100%;
+ border-left: 1px solid gray;
+ border-right: 1px solid gray;
}
}
}
@@ -99,7 +101,13 @@ $number-of-channels: 3;
}
.mic-control {
background: var(--sidebar-background);
- padding: 0 0 0.4rem 0;
+ position: relative;
+ .toggle {
+ cursor: pointer;
+ position: absolute;
+ right: 1em;
+ top: 0.2em;
+ }
}
}
#sidebar-toggle {
@@ -127,6 +135,12 @@ $number-of-channels: 3;
box-sizing: content-box;
}
+#importerIframe {
+ width: 100%;
+ height: 100%;
+ box-sizing: content-box;
+}
+
.sp-mic-live {
position: fixed;
top: 0;
diff --git a/src/mixer/audio.ts b/src/mixer/audio.ts
index 1f6fcd6..37bbbe2 100644
--- a/src/mixer/audio.ts
+++ b/src/mixer/audio.ts
@@ -102,7 +102,8 @@ class Player extends ((PlayerEmitter as unknown) as { new (): EventEmitter }) {
if ("outro" in this.wavesurfer.regions.list) {
// If the outro is set to 0, we assume that's no outro.
if (startTime === 0) {
- delete this.wavesurfer.regions.list.outro;
+ // Can't just delete the outro, so set it to the end of the track to hide it.
+ this.wavesurfer.regions.list.outro.start = this.wavesurfer.regions.list.outro.end;
} else {
this.wavesurfer.regions.list.outro.start = startTime;
}
diff --git a/src/navbar/index.tsx b/src/navbar/index.tsx
index 8dca731..8c71239 100644
--- a/src/navbar/index.tsx
+++ b/src/navbar/index.tsx
@@ -10,6 +10,7 @@ import {
FaBroadcastTower,
FaSpinner,
FaExclamationTriangle,
+ FaCog,
} from "react-icons/fa";
import { RootState } from "../rootReducer";
@@ -24,6 +25,8 @@ import { ConnectionStateEnum } from "../broadcast/streamer";
import { VUMeter } from "../optionsMenu/helpers/VUMeter";
import { getShowplan } from "../showplanner/state";
+import * as OptionsMenuState from "../optionsMenu/state";
+
function nicifyConnectionState(state: ConnectionStateEnum): string {
switch (state) {
case "CONNECTED":
@@ -238,7 +241,7 @@ export function NavBarMain() {
"btn rounded-0 pt-2 pb-1 nav-item nav-link " +
(broadcastState.recordingState === "CONNECTED"
? "btn-outline-danger active"
- : "btn-outline-warning")
+ : "btn-outline-light")
}
onClick={() =>
dispatch(
@@ -270,6 +273,13 @@ export function NavBarMain() {
)}
)}
+
dispatch(OptionsMenuState.open())}
+ >
+ Options
+
+
{
+ window.addEventListener(
+ "message",
+ (event) => {
+ if (!event.origin.includes("ury.org.uk")) {
+ return;
+ }
+ if (event.data === "reload_showplan") {
+ props.close();
+ }
+ },
+ false
+ );
+ });
return (
@@ -28,8 +44,8 @@ export function ImporterModal(props: ImporterProps) {
diff --git a/src/showplanner/LibraryUploadModal.tsx b/src/showplanner/LibraryUploadModal.tsx
index 9f43faf..e989195 100644
--- a/src/showplanner/LibraryUploadModal.tsx
+++ b/src/showplanner/LibraryUploadModal.tsx
@@ -27,7 +27,9 @@ export function LibraryUploadModal(props: LibraryUploadProps) {
diff --git a/src/showplanner/Player.tsx b/src/showplanner/Player.tsx
index c70a4f7..d539d60 100644
--- a/src/showplanner/Player.tsx
+++ b/src/showplanner/Player.tsx
@@ -7,6 +7,7 @@ import {
FaPlay,
FaPause,
FaStop,
+ FaTrash,
} from "react-icons/fa";
import { omit } from "lodash";
import { RootState } from "../rootReducer";
@@ -64,10 +65,12 @@ const setTrackIntro = (
track: api.Track,
secs: number,
player: number
-): AppThunk => async (dispatch) => {
+): AppThunk => async (dispatch, getState) => {
try {
dispatch(MixerState.setLoadedItemIntro(player, secs));
- await api.setTrackIntro(track.trackid, secs);
+ if (getState().settings.saveShowPlanChanges) {
+ await api.setTrackIntro(track.trackid, secs);
+ }
dispatch(ShowPlanState.setItemTimings({ item: track, intro: secs }));
} catch (e) {
dispatch(ShowPlanState.planSaveError("Failed saving track intro."));
@@ -79,10 +82,12 @@ const setTrackOutro = (
track: api.Track,
secs: number,
player: number
-): AppThunk => async (dispatch) => {
+): AppThunk => async (dispatch, getState) => {
try {
dispatch(MixerState.setLoadedItemOutro(player, secs));
- await api.setTrackOutro(track.trackid, secs);
+ if (getState().settings.saveShowPlanChanges) {
+ await api.setTrackOutro(track.trackid, secs);
+ }
dispatch(ShowPlanState.setItemTimings({ item: track, outro: secs }));
} catch (e) {
dispatch(ShowPlanState.planSaveError("Failed saving track outro."));
@@ -94,10 +99,12 @@ const setTrackCue = (
item: api.TimeslotItem,
secs: number,
player: number
-): AppThunk => async (dispatch) => {
+): AppThunk => async (dispatch, getState) => {
try {
dispatch(MixerState.setLoadedItemCue(player, secs));
- await api.setTimeslotItemCue(item.timeslotitemid, secs);
+ if (getState().settings.saveShowPlanChanges) {
+ await api.setTimeslotItemCue(item.timeslotitemid, secs);
+ }
dispatch(ShowPlanState.setItemTimings({ item, cue: secs }));
} catch (e) {
dispatch(ShowPlanState.planSaveError("Failed saving track cue."));
@@ -108,14 +115,30 @@ const setTrackCue = (
function TimingButtons({ id }: { id: number }) {
const dispatch = useDispatch();
const state = useSelector((state: RootState) => state.mixer.players[id]);
+ const [showDeleteMenu, setShowDeleteMenu] = useState(false);
+
return (
-
-
Set Marker:
+
+
{showDeleteMenu ? "Delete:" : "Set"} Marker:
{
if (state.loadedItem?.type === "central") {
- dispatch(setTrackIntro(state.loadedItem, state.timeCurrent, id));
+ dispatch(
+ setTrackIntro(
+ state.loadedItem,
+ showDeleteMenu ? 0 : state.timeCurrent,
+ id
+ )
+ );
}
}}
>
@@ -125,7 +148,13 @@ function TimingButtons({ id }: { id: number }) {
className="cue btn btn-sm btn-outline-secondary rounded-0"
onClick={() => {
if (state.loadedItem && "timeslotitemid" in state.loadedItem) {
- dispatch(setTrackCue(state.loadedItem, state.timeCurrent, id));
+ dispatch(
+ setTrackCue(
+ state.loadedItem,
+ showDeleteMenu ? 0 : state.timeCurrent,
+ id
+ )
+ );
}
}}
>
@@ -135,12 +164,29 @@ function TimingButtons({ id }: { id: number }) {
className="outro btn btn-sm btn-outline-secondary rounded-0"
onClick={() => {
if (state.loadedItem?.type === "central") {
- dispatch(setTrackOutro(state.loadedItem, state.timeCurrent, id));
+ dispatch(
+ setTrackOutro(
+ state.loadedItem,
+ showDeleteMenu ? 0 : state.timeCurrent,
+ id
+ )
+ );
}
}}
>
Outro
+
{
+ setShowDeleteMenu(!showDeleteMenu);
+ }}
+ >
+
+
);
}
diff --git a/src/showplanner/WelcomeModal.tsx b/src/showplanner/WelcomeModal.tsx
index e25227c..96c7b91 100644
--- a/src/showplanner/WelcomeModal.tsx
+++ b/src/showplanner/WelcomeModal.tsx
@@ -1,5 +1,7 @@
import React from "react";
+import { FaTimes } from "react-icons/fa";
import Modal from "react-modal";
+import { Button } from "reactstrap";
interface WelcomeModalProps {
isOpen: boolean;
@@ -9,7 +11,15 @@ interface WelcomeModalProps {
export function WelcomeModal(props: WelcomeModalProps) {
return (
- Welcome to WebStudio!
+ Welcome to WebStudio!
+
+
+
+
As you are not WebStudio Trained, you will be able to access all
WebStudio features except going live. If you want to go live, ask in
@@ -20,11 +30,6 @@ export function WelcomeModal(props: WelcomeModalProps) {
Computing in #remote-broadcasting.
Thank you, and have fun!
-
-
- Close
-
-
);
}
diff --git a/src/showplanner/index.tsx b/src/showplanner/index.tsx
index e4022bf..426c541 100644
--- a/src/showplanner/index.tsx
+++ b/src/showplanner/index.tsx
@@ -2,9 +2,9 @@ import React, { useState, useReducer, useEffect } from "react";
import { ContextMenu, MenuItem } from "react-contextmenu";
import { useBeforeunload } from "react-beforeunload";
import {
- FaAlignJustify,
FaBookOpen,
FaFileImport,
+ FaBars,
FaMicrophone,
FaTrash,
FaUpload,
@@ -134,7 +134,7 @@ function LibraryColumn() {
setSauce(e.target.value)}
@@ -198,63 +198,76 @@ function MicControl() {
return (
-
-
- Microphone
-
- {!state.open && (
-
- The microphone has not been setup. Go to options.
-
- )}
- {proMode && (
-
0 ? "live" : ""}
- >
- Mic Live:
- {state.open && state.volume > 0 ? (
- {
- return {formatted} ;
- }}
- />
- ) : (
- "00:00:00"
- )}
-
- )}
-
-
+
+
+ Microphone
+
+
-
-
-
dispatch(MixerState.setMicVolume("off"))}>
- Off
-
-
dispatch(MixerState.setMicVolume("full"))}>
- Full
-
-
-
- dispatch(OptionsMenuState.open())}>
- Options
-
+
);
@@ -358,6 +371,23 @@ const Showplanner: React.FC<{ timeslotId: number }> = function({ timeslotId }) {
dispatch(removeItem(timeslotId, data.id));
}
+ // Add support for reloading the show plan from the iFrames.
+ // There is a similar listener in showplanner/ImporterModal.tsx to handle closing the iframe.
+ useEffect(() => {
+ window.addEventListener(
+ "message",
+ (event) => {
+ if (!event.origin.includes("ury.org.uk")) {
+ return;
+ }
+ if (event.data === "reload_showplan") {
+ session.currentTimeslot !== null &&
+ dispatch(getShowplan(session.currentTimeslot.timeslot_id));
+ }
+ },
+ false
+ );
+ });
if (showplan === null) {
return (
= function({ timeslotId }) {
className="btn btn-outline-dark btn-sm mb-0"
onClick={() => toggleSidebar()}
>
-
+
Toggle Sidebar