Merge fixes / tidies from BAPS3 to Webstudio

This commit is contained in:
Matthew Stratford 2021-06-13 16:20:51 +01:00
parent 1e53edea2c
commit fede677bb5
13 changed files with 128 additions and 109 deletions

1
.env
View file

@ -4,5 +4,4 @@ REACT_APP_MYRADIO_NONAPI_BASE=https://ury.org.uk/myradio-staging
REACT_APP_MYRADIO_BASE=https://ury.org.uk/api-staging/v2 REACT_APP_MYRADIO_BASE=https://ury.org.uk/api-staging/v2
REACT_APP_BROADCAST_API_BASE=https://ury.org.uk/webstudio/api/v1 REACT_APP_BROADCAST_API_BASE=https://ury.org.uk/webstudio/api/v1
REACT_APP_WS_URL=wss://ury.org.uk/webstudio/api/stream REACT_APP_WS_URL=wss://ury.org.uk/webstudio/api/stream
REACT_APP_BAPSICLE_INTERFACE=true
REACT_APP_SENTRY_PUBLIC_DSN=https://7bdc2a7a1eb24ae080eb1d3af75e6307@o578586.ingest.sentry.io/5734903 REACT_APP_SENTRY_PUBLIC_DSN=https://7bdc2a7a1eb24ae080eb1d3af75e6307@o578586.ingest.sentry.io/5734903

View file

@ -38,6 +38,8 @@
To begin the development, run `npm start` or `yarn start`. To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`. To create a production bundle, use `npm run build` or `yarn build`.
TODO: Ideally we shouldn't rely on CDN's during runtime.
--> -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous"> integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">

View file

@ -17,7 +17,7 @@ $number-of-channels: 3;
.logo { .logo {
height: 50px; height: 50px;
} }
.logo-big { .logo-big-bapsicle {
height: 30vh; height: 30vh;
} }
} }

View file

@ -205,14 +205,13 @@ export function searchForTracks(
artist, artist,
title, title,
}); });
} else { }
return myradioApiRequest("/track/search", "GET", { return myradioApiRequest("/track/search", "GET", {
artist, artist,
title, title,
limit: 100, limit: 100,
digitised: true, digitised: true,
}); });
}
} }
export function getTimeslots(): Promise<Array<Timeslot>> { export function getTimeslots(): Promise<Array<Timeslot>> {

View file

@ -12,7 +12,7 @@ export function ConnectionDialogue({ error }: { error: String | null }) {
return ( return (
<div className="loading-dialogue"> <div className="loading-dialogue">
<div className="logo-container"> <div className="logo-container">
<img className="logo-big mb-2" src={appLogo} alt="BAPS Logo" /> <img className="logo-big-bapsicle mb-2" src={appLogo} alt="BAPS Logo" />
</div> </div>
<span className="inner"> <span className="inner">

View file

@ -4,7 +4,6 @@ import "./index.css";
import App from "./App"; import App from "./App";
import * as serviceWorker from "./serviceWorkerLoader"; import * as serviceWorker from "./serviceWorkerLoader";
import * as Sentry from "@sentry/react"; import * as Sentry from "@sentry/react";
import { Integrations } from "@sentry/tracing"; import { Integrations } from "@sentry/tracing";

View file

@ -289,6 +289,8 @@ class Player extends ((PlayerEmitter as unknown) as { new (): EventEmitter }) {
instance.emit("pause"); instance.emit("pause");
}); });
wavesurfer.on("seek", () => { wavesurfer.on("seek", () => {
// This is used to prevent infinite loops when bapsicle tells wavesurfer to change position,
// since otherwise it would send an change update back to the bapsicle server again (as if the user seeked intentionally).
if (instance.ignore_next_seek) { if (instance.ignore_next_seek) {
instance.ignore_next_seek = false; instance.ignore_next_seek = false;
} else { } else {

View file

@ -379,6 +379,7 @@ export const setLoadedItemOutro = (
}; };
export const seek = (player: number, time_s: number): AppThunk => async () => { export const seek = (player: number, time_s: number): AppThunk => async () => {
// Used only by Bapsicle to update the wavesurfer seek position.
const playerInstance = await audioEngine.getPlayer(player); const playerInstance = await audioEngine.getPlayer(player);
if (playerInstance) { if (playerInstance) {
@ -426,6 +427,7 @@ export const load = (
const currentItem = getState().mixer.players[player].loadedItem; const currentItem = getState().mixer.players[player].loadedItem;
if (currentItem !== null && itemId(currentItem) === itemId(item)) { if (currentItem !== null && itemId(currentItem) === itemId(item)) {
if (process.env.REACT_APP_BAPSICLE_INTERFACE) { if (process.env.REACT_APP_BAPSICLE_INTERFACE) {
// In BAPS, another client could have updated the audio markers etc, so we have to still check and update them.
// The cue/intro/outro point(s) have changed. // The cue/intro/outro point(s) have changed.
if ( if (
"cue" in currentItem && "cue" in currentItem &&
@ -567,6 +569,7 @@ export const load = (
dispatch(mixerState.actions.itemLoadComplete({ player })); dispatch(mixerState.actions.itemLoadComplete({ player }));
if (!process.env.REACT_APP_BAPSICLE_INTERFACE) { if (!process.env.REACT_APP_BAPSICLE_INTERFACE) {
// BAPS server will give these values already on load based on it's own calculation, not wavesurfer's.
dispatch( dispatch(
mixerState.actions.setTimeLength({ mixerState.actions.setTimeLength({
player, player,
@ -585,6 +588,7 @@ export const load = (
const state = getState().mixer.players[player]; const state = getState().mixer.players[player];
if (!process.env.REACT_APP_BAPSICLE_INTERFACE) { if (!process.env.REACT_APP_BAPSICLE_INTERFACE) {
// Client doesn't do any audio playing in BAPS.
if (state.playOnLoad) { if (state.playOnLoad) {
playerInstance.play(); playerInstance.play();
} }
@ -603,16 +607,18 @@ export const load = (
}); });
if (process.env.REACT_APP_BAPSICLE_INTERFACE) { if (process.env.REACT_APP_BAPSICLE_INTERFACE) {
// If user manually seeks on the waveform, just direct that to the webserver.
playerInstance.on("timeChangeSeek", (time) => { playerInstance.on("timeChangeSeek", (time) => {
if ( // Limit
Math.abs(time - getState().mixer.players[player].timeCurrent) > 0.5 //if (
) { // Math.abs(time - getState().mixer.players[player].timeCurrent) > 0.5
//) {
sendBAPSicleChannel({ sendBAPSicleChannel({
channel: player, channel: player,
command: "SEEK", command: "SEEK",
time: time, time: time,
}); });
} //}
}); });
} else { } else {
playerInstance.on("play", () => { playerInstance.on("play", () => {
@ -1261,10 +1267,40 @@ export const mixerKeyboardShortcutsMiddleware: Middleware<
store.dispatch(handleKeyboardShortcut(store, 2, "STOP")); store.dispatch(handleKeyboardShortcut(store, 2, "STOP"));
}); });
if (!process.env.REACT_APP_BAPSICLE_INTERFACE) {
Keys("a", () => {
store.dispatch(setVolume(0, "off"));
});
Keys("s", () => {
store.dispatch(setVolume(0, "bed"));
});
Keys("d", () => {
store.dispatch(setVolume(0, "full"));
});
Keys("f", () => {
store.dispatch(setVolume(1, "off"));
});
Keys("g", () => {
store.dispatch(setVolume(1, "bed"));
});
Keys("h", () => {
store.dispatch(setVolume(1, "full"));
});
Keys("j", () => {
store.dispatch(setVolume(2, "off"));
});
Keys("k", () => {
store.dispatch(setVolume(2, "bed"));
});
Keys("l", () => {
store.dispatch(setVolume(2, "full"));
});
Keys("x", () => { Keys("x", () => {
const state = store.getState().mixer.mic; const state = store.getState().mixer.mic;
store.dispatch(setMicVolume(state.volume === 1 ? "off" : "full")); store.dispatch(setMicVolume(state.volume === 1 ? "off" : "full"));
}); });
}
return (next) => (action) => next(action); return (next) => (action) => next(action);
}; };

View file

@ -50,8 +50,7 @@ export const Item = memo(function Item({
function triggerClick() { function triggerClick() {
if (column > -1) { if (column > -1) {
console.log("Clicking to load:", x); // TODO: move this into mixer state if we can.
if (process.env.REACT_APP_BAPSICLE_INTERFACE) { if (process.env.REACT_APP_BAPSICLE_INTERFACE) {
sendBAPSicleChannel({ sendBAPSicleChannel({
channel: column, channel: column,

View file

@ -81,6 +81,7 @@ const setTrackIntro = (
secs: number, secs: number,
player: number player: number
): AppThunk => async (dispatch, getState) => { ): AppThunk => async (dispatch, getState) => {
// TODO Move into MixerState
if (process.env.REACT_APP_BAPSICLE_INTERFACE) { if (process.env.REACT_APP_BAPSICLE_INTERFACE) {
let marker = { let marker = {
name: "Intro", name: "Intro",

View file

@ -50,8 +50,6 @@ import Modal from "react-modal";
import { Sidebar } from "./sidebar"; import { Sidebar } from "./sidebar";
import { PLAYER_ID_PREVIEW } from "../mixer/audio"; import { PLAYER_ID_PREVIEW } from "../mixer/audio";
import { sendBAPSicleChannel } from "../bapsicle";
function Channel({ id, data }: { id: number; data: PlanItem[] }) { function Channel({ id, data }: { id: number; data: PlanItem[] }) {
return ( return (
<div className="channel" id={"channel-" + id}> <div className="channel" id={"channel-" + id}>
@ -112,6 +110,7 @@ const Showplanner: React.FC<{ timeslotId: number }> = function({ timeslotId }) {
useEffect(() => { useEffect(() => {
if (!process.env.REACT_APP_BAPSICLE_INTERFACE) { if (!process.env.REACT_APP_BAPSICLE_INTERFACE) {
// In BAPS, we'll load in the show plan from a async message from server.
dispatch(getShowplan(timeslotId)); dispatch(getShowplan(timeslotId));
} }
}, [dispatch, timeslotId]); }, [dispatch, timeslotId]);
@ -148,15 +147,7 @@ const Showplanner: React.FC<{ timeslotId: number }> = function({ timeslotId }) {
cue: 0, cue: 0,
...data, ...data,
}; };
if (process.env.REACT_APP_BAPSICLE_INTERFACE) {
sendBAPSicleChannel({
channel: newItem.channel,
command: "ADD",
newItem: newItem,
});
} else {
dispatch(addItem(timeslotId, newItem)); dispatch(addItem(timeslotId, newItem));
}
increment(null); increment(null);
} else if (result.draggableId[0] === "A") { } else if (result.draggableId[0] === "A") {
// this is an aux resource // this is an aux resource
@ -171,15 +162,7 @@ const Showplanner: React.FC<{ timeslotId: number }> = function({ timeslotId }) {
cue: 0, cue: 0,
...data, ...data,
} as any; } as any;
if (process.env.REACT_APP_BAPSICLE_INTERFACE) {
sendBAPSicleChannel({
channel: newItem.channel,
command: "ADD",
newItem: newItem,
});
} else {
dispatch(addItem(timeslotId, newItem)); dispatch(addItem(timeslotId, newItem));
}
increment(null); increment(null);
} else { } else {
// this is a normal move (ghosts aren't draggable) // this is a normal move (ghosts aren't draggable)
@ -204,28 +187,6 @@ const Showplanner: React.FC<{ timeslotId: number }> = function({ timeslotId }) {
} }
} }
async function onCtxRemoveClick(
e: any,
data: { id: string; column: number; index: number }
) {
if (process.env.REACT_APP_BAPSICLE_INTERFACE) {
sendBAPSicleChannel({
channel: data.column,
command: "REMOVE",
weight: data.index,
});
} else {
dispatch(removeItem(timeslotId, data.id));
}
}
async function onCtxUnPlayedClick(
e: any,
data: { id: string; column: number; index: number }
) {
dispatch(setItemPlayed(data.id.toString(), false, data.column));
}
// Add support for reloading the show plan from the iFrames. // Add support for reloading the show plan from the iFrames.
// There is a similar listener in showplanner/ImporterModal.tsx to handle closing the iframe. // There is a similar listener in showplanner/ImporterModal.tsx to handle closing the iframe.
useEffect(() => { useEffect(() => {
@ -275,9 +236,9 @@ const Showplanner: React.FC<{ timeslotId: number }> = function({ timeslotId }) {
<FaTrash /> Remove <FaTrash /> Remove
</CtxMenuItem> </CtxMenuItem>
<CtxMenuItem <CtxMenuItem
onClick={(args) => onClick={(args) => {
dispatch(setItemPlayed((args.props as any).id, false)) dispatch(setItemPlayed((args.props as any).id, false));
} }}
> >
<FaCircleNotch /> Mark Unplayed <FaCircleNotch /> Mark Unplayed
</CtxMenuItem> </CtxMenuItem>

View file

@ -99,6 +99,8 @@ const showplan = createSlice({
state, state,
action: PayloadAction<{ channel: Number; planItems: PlanItem[] }> action: PayloadAction<{ channel: Number; planItems: PlanItem[] }>
) { ) {
// This is used for BAPSicle only to read in individual channels of show plan into the show state from the server.
// TODO: Does this need to be this complicated?
var newItems = state.plan?.filter( var newItems = state.plan?.filter(
(item) => item.channel !== action.payload.channel (item) => item.channel !== action.payload.channel
); );
@ -263,11 +265,7 @@ export const setItemPlayed = (
played: boolean played: boolean
): AppThunk => async (dispatch, getState) => { ): AppThunk => async (dispatch, getState) => {
// The server handles marking things played // The server handles marking things played
// TODO: Add support in BAPSicle for marking things played
if (process.env.REACT_APP_BAPSICLE_INTERFACE) { if (process.env.REACT_APP_BAPSICLE_INTERFACE) {
if (played) {
return;
}
var weight = -1; var weight = -1;
var player = null; var player = null;
@ -282,13 +280,15 @@ export const setItemPlayed = (
if (player) { if (player) {
sendBAPSicleChannel({ sendBAPSicleChannel({
channel: player, channel: player,
command: "RESETPLAYED", command: "SETPLAYED",
weight: weight, weight: weight,
played: played,
}); });
} else { } else {
sendBAPSicleChannel({ sendBAPSicleChannel({
command: "RESETPLAYED", command: "SETPLAYED",
weight: weight, weight: weight,
played: played,
}); });
} }
return; return;
@ -448,6 +448,15 @@ export const addItem = (
timeslotId: number, timeslotId: number,
newItemData: TimeslotItem newItemData: TimeslotItem
): AppThunk => async (dispatch, getState) => { ): AppThunk => async (dispatch, getState) => {
if (process.env.REACT_APP_BAPSICLE_INTERFACE) {
sendBAPSicleChannel({
channel: newItemData.channel,
command: "ADD",
newItem: newItemData,
});
return;
}
dispatch(showplan.actions.setPlanSaving(true)); dispatch(showplan.actions.setPlanSaving(true));
console.log("New Weight: " + newItemData.weight); console.log("New Weight: " + newItemData.weight);
const plan = cloneDeep(getState().showplan.plan!); const plan = cloneDeep(getState().showplan.plan!);
@ -527,7 +536,9 @@ export const addItem = (
}, },
} }
); );
dispatch(showplan.actions.planSaveError("Failed to update show plan."));
return;
}
const lastResult = result[result.length - 1]; // this is the add op const lastResult = result[result.length - 1]; // this is the add op
const newItemId = lastResult.timeslotitemid!; const newItemId = lastResult.timeslotitemid!;
@ -538,7 +549,6 @@ export const addItem = (
newItemData, newItemData,
}) })
); );
}
} else { } else {
// Just add it straight to the show plan without updating the server. // Just add it straight to the show plan without updating the server.
dispatch(showplan.actions.addItem(newItemData)); dispatch(showplan.actions.addItem(newItemData));
@ -551,10 +561,24 @@ export const removeItem = (
timeslotId: number, timeslotId: number,
itemid: string itemid: string
): AppThunk => async (dispatch, getState) => { ): AppThunk => async (dispatch, getState) => {
dispatch(showplan.actions.setPlanSaving(true));
// This is a simplified version of the second case of moveItem // This is a simplified version of the second case of moveItem
const plan = cloneDeep(getState().showplan.plan!); const plan = cloneDeep(getState().showplan.plan!);
const item = plan.find((x) => itemId(x) === itemid)!; const item = plan.find((x) => itemId(x) === itemid)!;
if (process.env.REACT_APP_BAPSICLE_INTERFACE) {
// Server handles deletion, short circuit.
if (item) {
sendBAPSicleChannel({
channel: item.channel,
command: "REMOVE",
weight: item.weight,
});
}
return;
}
dispatch(showplan.actions.setPlanSaving(true));
const planColumn = plan const planColumn = plan
.filter((x) => x.channel === item.channel) .filter((x) => x.channel === item.channel)
.sort((a, b) => a.weight - b.weight); .sort((a, b) => a.weight - b.weight);
@ -578,6 +602,7 @@ export const removeItem = (
weight: movingItem.weight - 1, weight: movingItem.weight - 1,
}); });
movingItem.weight -= 1; movingItem.weight -= 1;
}
if (!process.env.REACT_APP_BAPSICLE_INTERFACE) { if (!process.env.REACT_APP_BAPSICLE_INTERFACE) {
if (getState().settings.saveShowPlanChanges) { if (getState().settings.saveShowPlanChanges) {
@ -594,14 +619,11 @@ export const removeItem = (
}, },
} }
); );
dispatch( dispatch(showplan.actions.planSaveError("Failed to update show plan."));
showplan.actions.planSaveError("Failed to update show plan.")
);
return; return;
} }
} }
} }
}
dispatch(showplan.actions.applyOps(ops)); dispatch(showplan.actions.applyOps(ops));
dispatch(showplan.actions.setPlanSaving(false)); dispatch(showplan.actions.setPlanSaving(false));

View file

@ -20,7 +20,6 @@ import { bapsicleMiddleware } from "./bapsicle";
import * as Sentry from "@sentry/react"; import * as Sentry from "@sentry/react";
// const ACTION_HISTORY_MAX_SIZE = 20; // const ACTION_HISTORY_MAX_SIZE = 20;
// const actionHistory: Array<Action> = []; // const actionHistory: Array<Action> = [];