diff --git a/src/api.ts b/src/api.ts index de6a65e..b943d1a 100644 --- a/src/api.ts +++ b/src/api.ts @@ -1,36 +1,29 @@ import qs from "qs"; import { convertModelToFormData, urlEncode } from "./lib/utils"; -export const MYRADIO_NON_API_BASE = process.env.REACT_APP_MYRADIO_NONAPI_BASE || "https://ury.org.uk/myradio-staging"; +export const MYRADIO_NON_API_BASE = + process.env.REACT_APP_MYRADIO_NONAPI_BASE || + "https://ury.org.uk/myradio-staging"; export const MYRADIO_BASE_URL = process.env.REACT_APP_MYRADIO_BASE || "https://ury.org.uk/api-staging/v2"; const MYRADIO_API_KEY = process.env.REACT_APP_MYRADIO_KEY!; class ApiException extends Error {} -export async function myradioApiRequest( - endpoint: string, +export async function myradioRequest( + url: string, method: "GET" | "POST" | "PUT", params: any -): Promise { +): Promise { let req = null; if (method === "GET") { - req = fetch( - MYRADIO_BASE_URL + - endpoint + - qs.stringify( - { - ...params, - api_key: MYRADIO_API_KEY - }, - { addQueryPrefix: true } - ), - { credentials: "include" } - ); + req = fetch(url + qs.stringify(params, { addQueryPrefix: true }), { + credentials: "include" + }); } else { const body = JSON.stringify(params); console.log(body); - req = fetch(MYRADIO_BASE_URL + endpoint + "?api_key=" + MYRADIO_API_KEY, { + req = fetch(url, { method, body, headers: { @@ -39,7 +32,16 @@ export async function myradioApiRequest( credentials: "include" }); } - const json = await (await req).json(); + return await req; +} + +export async function myradioApiRequest( + endpoint: string, + method: "GET" | "POST" | "PUT", + params: any +): Promise { + const res = await myradioRequest(MYRADIO_BASE_URL + endpoint, method, params); + const json = await res.json(); if (json.status === "OK") { return json.payload; } else { @@ -77,16 +79,23 @@ interface TimeslotItemCentral { album: Album; } -interface TimeslotItemAux { +export interface AuxItem { type: "aux"; - artist: null; - intro: null; summary: string; + title: string; managedid: number; + length: string; + trackid: number; + expirydate: boolean | string; + expired: boolean; recordid: string; auxid: string; } +interface TimeslotItemAux extends AuxItem { + type: "aux"; +} + export type TimeslotItem = TimeslotItemBase & (TimeslotItemCentral | TimeslotItemAux); @@ -160,6 +169,12 @@ export function searchForTracks( }); } +export function loadAuxLibrary(libraryId: string): Promise { + return myradioRequest(MYRADIO_NON_API_BASE + "/NIPSWeb/load_aux_lib", "GET", { + libraryid: libraryId + }).then(res => res.json()); +} + export type UpdateOp = | { op: "MoveItem"; diff --git a/src/mixer/state.ts b/src/mixer/state.ts index f8c668f..cb169f0 100644 --- a/src/mixer/state.ts +++ b/src/mixer/state.ts @@ -9,7 +9,7 @@ import { import Between from "between.js"; import { PlanItem } from "../showplanner/state"; import Keys from "keymaster"; -import { Track, MYRADIO_NON_API_BASE } from "../api"; +import { Track, MYRADIO_NON_API_BASE, AuxItem } from "../api"; import { AppThunk } from "../store"; import { RootState } from "../rootReducer"; import WaveSurfer from "wavesurfer.js"; @@ -40,7 +40,7 @@ type MicVolumePresetEnum = "off" | "full"; type MicErrorEnum = "NO_PERMISSION" | "NOT_SECURE_CONTEXT" | "UNKNOWN"; interface PlayerState { - loadedItem: PlanItem | Track | null; + loadedItem: PlanItem | Track | AuxItem | null; loading: boolean; state: PlayerStateEnum; volume: number; @@ -123,7 +123,7 @@ const mixerState = createSlice({ reducers: { loadItem( state, - action: PayloadAction<{ player: number; item: PlanItem | Track }> + action: PayloadAction<{ player: number; item: PlanItem | Track | AuxItem }> ) { state.players[action.payload.player].loadedItem = action.payload.item; @@ -241,7 +241,7 @@ export default mixerState.reducer; export const load = ( player: number, - item: PlanItem | Track + item: PlanItem | Track | AuxItem ): AppThunk => async (dispatch, getState) => { if (typeof wavesurfers[player] !== "undefined") { if (wavesurfers[player].isPlaying()) { @@ -261,7 +261,7 @@ export const load = ( item.album.recordid + "&trackid=" + item.trackid; - } else if ("type" in item && item.type == "aux") { + } else if ("managedid" in item) { url = MYRADIO_NON_API_BASE + "/NIPSWeb/managed_play?managedid=" + diff --git a/src/showplanner/Item.tsx b/src/showplanner/Item.tsx index 6fedafb..43b6ff9 100644 --- a/src/showplanner/Item.tsx +++ b/src/showplanner/Item.tsx @@ -1,6 +1,6 @@ import React, { memo } from "react"; import { PlanItem, itemId } from "./state"; -import { Track } from "../api"; +import { Track, AuxItem } from "../api"; import { useDispatch, useSelector } from "react-redux"; import { RootState } from "../rootReducer"; @@ -15,7 +15,7 @@ export const Item = memo(function Item({ index, column }: { - item: PlanItem | Track; + item: PlanItem | Track | AuxItem; index: number; column: number; }) { @@ -60,7 +60,7 @@ export const Item = memo(function Item({ {"artist" in x && " - " + x.artist} + "clean" in x && x.clean === false ? "" : " d-none")}> Explicit diff --git a/src/showplanner/index.tsx b/src/showplanner/index.tsx index bd80cca..9d55632 100644 --- a/src/showplanner/index.tsx +++ b/src/showplanner/index.tsx @@ -37,9 +37,7 @@ import * as MixerState from "../mixer/state"; import appLogo from "../assets/images/webstudio.svg"; import { Item, TS_ITEM_MENU_ID } from "./Item"; -import { CentralMusicLibrary, CML_CACHE } from "./libraries"; - - +import { CentralMusicLibrary, CML_CACHE, AuxLibrary, AUX_CACHE } from "./libraries"; const USE_REAL_GAIN_VALUE = false; @@ -50,86 +48,137 @@ function Player({ id }: { id: number }) { const dispatch = useDispatch(); return ( -
- - -
+
+
- {playerState.loadedItem !== null - && playerState.loading === false - ? playerState.loadedItem.title - : (playerState.loading ? `LOADING` : "No Media Selected")} + {playerState.loadedItem !== null && !playerState.loading + ? playerState.loadedItem.title + : playerState.loading + ? `LOADING` + : "No Media Selected"} - + Explicit -
+ +
- {playerState.loadedItem !== null - && playerState.loading === false - ? playerState.loadedItem.artist - : ""}  + {playerState.loadedItem !== null && !playerState.loading + ? "artist" in playerState.loadedItem && + playerState.loadedItem.artist + : ""} +  
-
- - {secToHHMM(playerState.timeCurrent)} - {secToHHMM(playerState.timeLength)} - {secToHHMM(playerState.timeRemaining)} - {playerState.loadedItem !== null ? secToHHMM(playerState.loadedItem.intro ? playerState.loadedItem.intro : 0) : "00:00:00"} - in - out - 00:00:00 - {(playerState.loadedItem !== null && playerState.timeLength === 0) && LOADING} -
+
+ + {secToHHMM(playerState.timeCurrent)} + + + {secToHHMM(playerState.timeLength)} + + + {secToHHMM(playerState.timeRemaining)} + + {playerState.loadedItem !== null && + "intro" in playerState.loadedItem && ( + + {playerState.loadedItem !== null + ? secToHHMM( + playerState.loadedItem.intro + ? playerState.loadedItem.intro + : 0 + ) + : "00:00:00"}{" "} + - in + + )} + out - 00:00:00 + {playerState.loadedItem !== null && playerState.timeLength === 0 && ( + LOADING + )} +
@@ -191,6 +240,23 @@ function Column({ id, data }: { id: number; data: PlanItem[] }) { ); } +// TODO: this shouldn't have to be hardcoded +const AUX_LIBRARIES: {[key: string]: string} = { + "aux-11": "Ambiences/Soundscapes", + "aux-3": "Artist Drops", + "aux-1": "Beds", + "aux-7": "Daily News Bulletins", + "aux-13": "Event Resources", + "aux-2": "Jingles", + "aux-4": "News", + "aux-5": "Presenter Idents", + "aux-6": "Promos", + "aux-12": "Roses 2018", + "aux-10": "Sound Effects", + "aux-8": "Speech", + "aux-9": "Teasers" +}; + function LibraryColumn() { const [sauce, setSauce] = useState("None"); return ( @@ -205,11 +271,15 @@ function LibraryColumn() { Choose a library + + {Object.keys(AUX_LIBRARIES).map(libId => )}
{sauce === "CentralMusicLibrary" && } - - + {sauce.startsWith("aux-") && } +
Select a library to search. @@ -243,10 +313,7 @@ function MicControl() {
- ); } @@ -323,11 +421,11 @@ const Showplanner: React.FC<{ timeslotId: number }> = function({ timeslotId }) { }, [timeslotId]); function toggleSidebar() { - var element = document.getElementById('sidebar'); + var element = document.getElementById("sidebar"); if (element) { - element.classList.toggle('active') + element.classList.toggle("active"); } - }; + } async function onDragEnd(result: DropResult, provider: ResponderProvided) { if (!result.destination) { @@ -349,6 +447,19 @@ const Showplanner: React.FC<{ timeslotId: number }> = function({ timeslotId }) { ...data }; dispatch(addItem(timeslotId, newItem)); + } else if (result.draggableId[0] === "A") { + // this is an aux resource + // TODO: this is ugly, should be in redux + const data = AUX_CACHE[result.draggableId]; + const newItem: TimeslotItem = { + type: "aux", + timeslotitemid: "CHANGEME" + Math.random(), + channel: parseInt(result.destination.droppableId, 10), + weight: result.destination.index, + clean: true, + ...data + }; + dispatch(addItem(timeslotId, newItem)); } else { // this is a normal move (ghosts aren't draggable) dispatch( @@ -399,8 +510,13 @@ const Showplanner: React.FC<{ timeslotId: number }> = function({ timeslotId }) {
-