Merge fixes / tidies from BAPS3 to Webstudio
This commit is contained in:
parent
1e53edea2c
commit
fede677bb5
13 changed files with 128 additions and 109 deletions
1
.env
1
.env
|
@ -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_BROADCAST_API_BASE=https://ury.org.uk/webstudio/api/v1
|
||||
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
|
||||
|
|
|
@ -38,6 +38,8 @@
|
|||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
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"
|
||||
integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
|
||||
|
|
|
@ -17,7 +17,7 @@ $number-of-channels: 3;
|
|||
.logo {
|
||||
height: 50px;
|
||||
}
|
||||
.logo-big {
|
||||
.logo-big-bapsicle {
|
||||
height: 30vh;
|
||||
}
|
||||
}
|
||||
|
|
13
src/api.ts
13
src/api.ts
|
@ -205,14 +205,13 @@ export function searchForTracks(
|
|||
artist,
|
||||
title,
|
||||
});
|
||||
} else {
|
||||
return myradioApiRequest("/track/search", "GET", {
|
||||
artist,
|
||||
title,
|
||||
limit: 100,
|
||||
digitised: true,
|
||||
});
|
||||
}
|
||||
return myradioApiRequest("/track/search", "GET", {
|
||||
artist,
|
||||
title,
|
||||
limit: 100,
|
||||
digitised: true,
|
||||
});
|
||||
}
|
||||
|
||||
export function getTimeslots(): Promise<Array<Timeslot>> {
|
||||
|
|
|
@ -12,7 +12,7 @@ export function ConnectionDialogue({ error }: { error: String | null }) {
|
|||
return (
|
||||
<div className="loading-dialogue">
|
||||
<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>
|
||||
|
||||
<span className="inner">
|
||||
|
|
|
@ -4,7 +4,6 @@ import "./index.css";
|
|||
import App from "./App";
|
||||
import * as serviceWorker from "./serviceWorkerLoader";
|
||||
|
||||
|
||||
import * as Sentry from "@sentry/react";
|
||||
import { Integrations } from "@sentry/tracing";
|
||||
|
||||
|
|
|
@ -289,6 +289,8 @@ class Player extends ((PlayerEmitter as unknown) as { new (): EventEmitter }) {
|
|||
instance.emit("pause");
|
||||
});
|
||||
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) {
|
||||
instance.ignore_next_seek = false;
|
||||
} else {
|
||||
|
|
|
@ -379,6 +379,7 @@ export const setLoadedItemOutro = (
|
|||
};
|
||||
|
||||
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);
|
||||
|
||||
if (playerInstance) {
|
||||
|
@ -426,6 +427,7 @@ export const load = (
|
|||
const currentItem = getState().mixer.players[player].loadedItem;
|
||||
if (currentItem !== null && itemId(currentItem) === itemId(item)) {
|
||||
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.
|
||||
if (
|
||||
"cue" in currentItem &&
|
||||
|
@ -567,6 +569,7 @@ export const load = (
|
|||
dispatch(mixerState.actions.itemLoadComplete({ player }));
|
||||
|
||||
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(
|
||||
mixerState.actions.setTimeLength({
|
||||
player,
|
||||
|
@ -585,6 +588,7 @@ export const load = (
|
|||
const state = getState().mixer.players[player];
|
||||
|
||||
if (!process.env.REACT_APP_BAPSICLE_INTERFACE) {
|
||||
// Client doesn't do any audio playing in BAPS.
|
||||
if (state.playOnLoad) {
|
||||
playerInstance.play();
|
||||
}
|
||||
|
@ -603,16 +607,18 @@ export const load = (
|
|||
});
|
||||
|
||||
if (process.env.REACT_APP_BAPSICLE_INTERFACE) {
|
||||
// If user manually seeks on the waveform, just direct that to the webserver.
|
||||
playerInstance.on("timeChangeSeek", (time) => {
|
||||
if (
|
||||
Math.abs(time - getState().mixer.players[player].timeCurrent) > 0.5
|
||||
) {
|
||||
sendBAPSicleChannel({
|
||||
channel: player,
|
||||
command: "SEEK",
|
||||
time: time,
|
||||
});
|
||||
}
|
||||
// Limit
|
||||
//if (
|
||||
// Math.abs(time - getState().mixer.players[player].timeCurrent) > 0.5
|
||||
//) {
|
||||
sendBAPSicleChannel({
|
||||
channel: player,
|
||||
command: "SEEK",
|
||||
time: time,
|
||||
});
|
||||
//}
|
||||
});
|
||||
} else {
|
||||
playerInstance.on("play", () => {
|
||||
|
@ -1261,10 +1267,40 @@ export const mixerKeyboardShortcutsMiddleware: Middleware<
|
|||
store.dispatch(handleKeyboardShortcut(store, 2, "STOP"));
|
||||
});
|
||||
|
||||
Keys("x", () => {
|
||||
const state = store.getState().mixer.mic;
|
||||
store.dispatch(setMicVolume(state.volume === 1 ? "off" : "full"));
|
||||
});
|
||||
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", () => {
|
||||
const state = store.getState().mixer.mic;
|
||||
store.dispatch(setMicVolume(state.volume === 1 ? "off" : "full"));
|
||||
});
|
||||
}
|
||||
|
||||
return (next) => (action) => next(action);
|
||||
};
|
||||
|
|
|
@ -50,8 +50,7 @@ export const Item = memo(function Item({
|
|||
|
||||
function triggerClick() {
|
||||
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) {
|
||||
sendBAPSicleChannel({
|
||||
channel: column,
|
||||
|
|
|
@ -81,6 +81,7 @@ const setTrackIntro = (
|
|||
secs: number,
|
||||
player: number
|
||||
): AppThunk => async (dispatch, getState) => {
|
||||
// TODO Move into MixerState
|
||||
if (process.env.REACT_APP_BAPSICLE_INTERFACE) {
|
||||
let marker = {
|
||||
name: "Intro",
|
||||
|
|
|
@ -50,8 +50,6 @@ import Modal from "react-modal";
|
|||
import { Sidebar } from "./sidebar";
|
||||
import { PLAYER_ID_PREVIEW } from "../mixer/audio";
|
||||
|
||||
import { sendBAPSicleChannel } from "../bapsicle";
|
||||
|
||||
function Channel({ id, data }: { id: number; data: PlanItem[] }) {
|
||||
return (
|
||||
<div className="channel" id={"channel-" + id}>
|
||||
|
@ -112,6 +110,7 @@ const Showplanner: React.FC<{ timeslotId: number }> = function({ timeslotId }) {
|
|||
|
||||
useEffect(() => {
|
||||
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, timeslotId]);
|
||||
|
@ -148,15 +147,7 @@ const Showplanner: React.FC<{ timeslotId: number }> = function({ timeslotId }) {
|
|||
cue: 0,
|
||||
...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);
|
||||
} else if (result.draggableId[0] === "A") {
|
||||
// this is an aux resource
|
||||
|
@ -171,15 +162,7 @@ const Showplanner: React.FC<{ timeslotId: number }> = function({ timeslotId }) {
|
|||
cue: 0,
|
||||
...data,
|
||||
} 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);
|
||||
} else {
|
||||
// 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.
|
||||
// There is a similar listener in showplanner/ImporterModal.tsx to handle closing the iframe.
|
||||
useEffect(() => {
|
||||
|
@ -275,9 +236,9 @@ const Showplanner: React.FC<{ timeslotId: number }> = function({ timeslotId }) {
|
|||
<FaTrash /> Remove
|
||||
</CtxMenuItem>
|
||||
<CtxMenuItem
|
||||
onClick={(args) =>
|
||||
dispatch(setItemPlayed((args.props as any).id, false))
|
||||
}
|
||||
onClick={(args) => {
|
||||
dispatch(setItemPlayed((args.props as any).id, false));
|
||||
}}
|
||||
>
|
||||
<FaCircleNotch /> Mark Unplayed
|
||||
</CtxMenuItem>
|
||||
|
|
|
@ -99,6 +99,8 @@ const showplan = createSlice({
|
|||
state,
|
||||
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(
|
||||
(item) => item.channel !== action.payload.channel
|
||||
);
|
||||
|
@ -263,11 +265,7 @@ export const setItemPlayed = (
|
|||
played: boolean
|
||||
): AppThunk => async (dispatch, getState) => {
|
||||
// The server handles marking things played
|
||||
// TODO: Add support in BAPSicle for marking things played
|
||||
if (process.env.REACT_APP_BAPSICLE_INTERFACE) {
|
||||
if (played) {
|
||||
return;
|
||||
}
|
||||
var weight = -1;
|
||||
var player = null;
|
||||
|
||||
|
@ -282,13 +280,15 @@ export const setItemPlayed = (
|
|||
if (player) {
|
||||
sendBAPSicleChannel({
|
||||
channel: player,
|
||||
command: "RESETPLAYED",
|
||||
command: "SETPLAYED",
|
||||
weight: weight,
|
||||
played: played,
|
||||
});
|
||||
} else {
|
||||
sendBAPSicleChannel({
|
||||
command: "RESETPLAYED",
|
||||
command: "SETPLAYED",
|
||||
weight: weight,
|
||||
played: played,
|
||||
});
|
||||
}
|
||||
return;
|
||||
|
@ -448,6 +448,15 @@ export const addItem = (
|
|||
timeslotId: number,
|
||||
newItemData: TimeslotItem
|
||||
): 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));
|
||||
console.log("New Weight: " + newItemData.weight);
|
||||
const plan = cloneDeep(getState().showplan.plan!);
|
||||
|
@ -527,18 +536,19 @@ export const addItem = (
|
|||
},
|
||||
}
|
||||
);
|
||||
|
||||
const lastResult = result[result.length - 1]; // this is the add op
|
||||
const newItemId = lastResult.timeslotitemid!;
|
||||
|
||||
newItemData.timeslotitemid = newItemId;
|
||||
dispatch(
|
||||
showplan.actions.replaceGhost({
|
||||
ghostId: "G" + ghostId,
|
||||
newItemData,
|
||||
})
|
||||
);
|
||||
dispatch(showplan.actions.planSaveError("Failed to update show plan."));
|
||||
return;
|
||||
}
|
||||
const lastResult = result[result.length - 1]; // this is the add op
|
||||
const newItemId = lastResult.timeslotitemid!;
|
||||
|
||||
newItemData.timeslotitemid = newItemId;
|
||||
dispatch(
|
||||
showplan.actions.replaceGhost({
|
||||
ghostId: "G" + ghostId,
|
||||
newItemData,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
// Just add it straight to the show plan without updating the server.
|
||||
dispatch(showplan.actions.addItem(newItemData));
|
||||
|
@ -551,10 +561,24 @@ export const removeItem = (
|
|||
timeslotId: number,
|
||||
itemid: string
|
||||
): AppThunk => async (dispatch, getState) => {
|
||||
dispatch(showplan.actions.setPlanSaving(true));
|
||||
// This is a simplified version of the second case of moveItem
|
||||
const plan = cloneDeep(getState().showplan.plan!);
|
||||
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
|
||||
.filter((x) => x.channel === item.channel)
|
||||
.sort((a, b) => a.weight - b.weight);
|
||||
|
@ -578,27 +602,25 @@ export const removeItem = (
|
|||
weight: movingItem.weight - 1,
|
||||
});
|
||||
movingItem.weight -= 1;
|
||||
}
|
||||
|
||||
if (!process.env.REACT_APP_BAPSICLE_INTERFACE) {
|
||||
if (getState().settings.saveShowPlanChanges) {
|
||||
const result = await api.updateShowplan(timeslotId, ops);
|
||||
if (!result.every((x) => x.status)) {
|
||||
Sentry.captureException(
|
||||
new Error("Showplan update failure [removeItem]"),
|
||||
{
|
||||
contexts: {
|
||||
updateShowplan: {
|
||||
ops,
|
||||
result,
|
||||
},
|
||||
if (!process.env.REACT_APP_BAPSICLE_INTERFACE) {
|
||||
if (getState().settings.saveShowPlanChanges) {
|
||||
const result = await api.updateShowplan(timeslotId, ops);
|
||||
if (!result.every((x) => x.status)) {
|
||||
Sentry.captureException(
|
||||
new Error("Showplan update failure [removeItem]"),
|
||||
{
|
||||
contexts: {
|
||||
updateShowplan: {
|
||||
ops,
|
||||
result,
|
||||
},
|
||||
}
|
||||
);
|
||||
dispatch(
|
||||
showplan.actions.planSaveError("Failed to update show plan.")
|
||||
);
|
||||
return;
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
dispatch(showplan.actions.planSaveError("Failed to update show plan."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@ import { bapsicleMiddleware } from "./bapsicle";
|
|||
|
||||
import * as Sentry from "@sentry/react";
|
||||
|
||||
|
||||
// const ACTION_HISTORY_MAX_SIZE = 20;
|
||||
|
||||
// const actionHistory: Array<Action> = [];
|
||||
|
|
Loading…
Reference in a new issue