Merge remote-tracking branch 'origin/master' into marks/party-mode
This commit is contained in:
commit
840e969113
9 changed files with 139 additions and 77 deletions
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "webstudio",
|
||||
"version": "1.5.3",
|
||||
"version": "1.5.4",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@babel/core": "7.6.0",
|
||||
|
|
|
@ -42,6 +42,10 @@ $number-of-channels: 3;
|
|||
font-size: 0.92em;
|
||||
}
|
||||
|
||||
.navbar.hover-menu .hover-label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hover-menu {
|
||||
.btn {
|
||||
padding-top: 0.1em;
|
||||
|
@ -57,7 +61,6 @@ $number-of-channels: 3;
|
|||
}
|
||||
|
||||
&:not(:hover) {
|
||||
overflow: hidden;
|
||||
height: 15px;
|
||||
|
||||
> *:not(.hover-label) {
|
||||
|
|
12
src/api.ts
12
src/api.ts
|
@ -1,4 +1,6 @@
|
|||
import qs from "qs";
|
||||
import { getUserError, setCurrentUser } from "./session/state";
|
||||
import store from "./store";
|
||||
|
||||
export const MYRADIO_NON_API_BASE = process.env.REACT_APP_MYRADIO_NONAPI_BASE!;
|
||||
export const MYRADIO_BASE_URL = process.env.REACT_APP_MYRADIO_BASE!;
|
||||
|
@ -42,8 +44,14 @@ export async function myradioApiRequest(
|
|||
if (json.status === "OK") {
|
||||
return json.payload;
|
||||
} else {
|
||||
console.error(json.payload);
|
||||
throw new ApiException(json.payload);
|
||||
if (res.status === 401) {
|
||||
// We've logged out! Oh no!
|
||||
store.dispatch(setCurrentUser({ user: null, canBroadcast: false }));
|
||||
store.dispatch(getUserError("User is no longer logged in."));
|
||||
} else {
|
||||
console.error(json.payload);
|
||||
throw new ApiException(json.payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -144,7 +144,16 @@ export const registerForShow = (): AppThunk => async (dispatch, getState) => {
|
|||
})
|
||||
);
|
||||
if (streamer) {
|
||||
await streamer.stop("ApiException " + e.message);
|
||||
// We're connected,
|
||||
var remain_connected = getState().settings.allowStreamingOnReject;
|
||||
if (remain_connected) {
|
||||
console.log(
|
||||
"StateServer refused registration. Due to setting, staying connected.",
|
||||
e
|
||||
);
|
||||
} else {
|
||||
await streamer.stop("ApiException " + e.message);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// let raygun handle it
|
||||
|
|
|
@ -305,6 +305,25 @@ export function AdvancedTab() {
|
|||
/>
|
||||
<label className="form-check-label">Enable recording</label>
|
||||
</div>
|
||||
<div className="form-check">
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
checked={settings.allowStreamingOnReject}
|
||||
onChange={(e) =>
|
||||
dispatch(
|
||||
changeSetting({
|
||||
key: "allowStreamingOnReject",
|
||||
val: e.target.checked,
|
||||
})
|
||||
)
|
||||
}
|
||||
/>
|
||||
<label>
|
||||
Allow connection persistance for troubleshooting. (Don't enable unless
|
||||
requested to.)
|
||||
</label>
|
||||
</div>
|
||||
<div className="form-check">
|
||||
<input
|
||||
className="form-check-input"
|
||||
|
|
|
@ -3,6 +3,7 @@ import { INTERNAL_OUTPUT_ID, PLAYER_COUNT } from "../mixer/audio";
|
|||
|
||||
interface Settings {
|
||||
showDebugInfo: boolean;
|
||||
allowStreamingOnReject: boolean;
|
||||
enableRecording: boolean;
|
||||
tracklist: "always" | "while_live" | "never";
|
||||
doTheNews: "always" | "while_live" | "never";
|
||||
|
@ -19,6 +20,7 @@ const settingsState = createSlice({
|
|||
name: "settings",
|
||||
initialState: {
|
||||
showDebugInfo: false,
|
||||
allowStreamingOnReject: false,
|
||||
enableRecording: false,
|
||||
tracklist: "while_live",
|
||||
doTheNews: "while_live",
|
||||
|
|
|
@ -73,6 +73,8 @@ const sessionState = createSlice({
|
|||
|
||||
export default sessionState.reducer;
|
||||
|
||||
export const { setCurrentUser, getUserError } = sessionState.actions;
|
||||
|
||||
export const getCurrentUser = (): AppThunk => async (dispatch, getState) => {
|
||||
return getState().session.currentUser;
|
||||
};
|
||||
|
@ -91,7 +93,7 @@ export const getUser = (): AppThunk => async (dispatch) => {
|
|||
});
|
||||
dispatch(sessionState.actions.setCurrentUser({ user, canBroadcast }));
|
||||
} catch (e) {
|
||||
console.log("failed to get user. " + e.toString());
|
||||
console.log("Failed to get user. " + e.toString());
|
||||
dispatch(sessionState.actions.getUserError(e.toString()));
|
||||
}
|
||||
};
|
||||
|
@ -102,7 +104,7 @@ export const getTimeslot = (): AppThunk => async (dispatch) => {
|
|||
const timeslot = await getCurrentApiTimeslot();
|
||||
dispatch(sessionState.actions.getTimeslotSuccess(timeslot));
|
||||
} catch (e) {
|
||||
console.log("failed to get selected timeslot. " + e.toString());
|
||||
console.log("Failed to get selected timeslot. " + e.toString());
|
||||
dispatch(sessionState.actions.getTimeslotError(e.toString()));
|
||||
}
|
||||
};
|
||||
|
|
|
@ -347,17 +347,20 @@ export function LoadingDialogue({
|
|||
<p>
|
||||
<strong>{subtitle}</strong>
|
||||
</p>
|
||||
{error !== null && (
|
||||
<>
|
||||
<p>
|
||||
<strong>Failed!</strong> Please tell Computing Team that something
|
||||
broke.
|
||||
</p>
|
||||
<p>
|
||||
<code>{error}</code>
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
{error !== null &&
|
||||
(error === "Error: No valid authentication data provided." ? (
|
||||
<p>Redirecting you to MyRadio, please wait...</p>
|
||||
) : (
|
||||
<>
|
||||
<p>
|
||||
<strong>Failed!</strong> Please tell Computing Team that
|
||||
something broke.
|
||||
</p>
|
||||
<p>
|
||||
<code>{error}</code>
|
||||
</p>
|
||||
</>
|
||||
))}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -173,38 +173,42 @@ export function LibraryColumn() {
|
|||
}
|
||||
|
||||
export function CentralMusicLibrary() {
|
||||
const [track, setTrack] = useState("");
|
||||
const [artist, setArtist] = useState("");
|
||||
const debouncedTrack = useDebounce(track, 1000);
|
||||
const debouncedArtist = useDebounce(artist, 1000);
|
||||
const [items, setItems] = useState<Track[]>([]);
|
||||
const [trackSearchTerm, setTrackSearchTerm] = useState("");
|
||||
const [artistSearchTerm, setArtistSearchTerm] = useState("");
|
||||
const debouncedTrackSearchTerm = useDebounce(trackSearchTerm, 1000);
|
||||
const debouncedArtistSearchTerm = useDebounce(artistSearchTerm, 1000);
|
||||
const [tracks, setTracks] = useState<Track[]>([]);
|
||||
|
||||
const [state, setState] = useState<searchingStateEnum>("not-searching");
|
||||
const [searchingState, setSearchingState] = useState<searchingStateEnum>(
|
||||
"not-searching"
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (debouncedTrack === "" && debouncedArtist === "") {
|
||||
setItems([]);
|
||||
setState("not-searching");
|
||||
if (debouncedTrackSearchTerm === "" && debouncedArtistSearchTerm === "") {
|
||||
setTracks([]);
|
||||
setSearchingState("not-searching");
|
||||
return;
|
||||
}
|
||||
setItems([]);
|
||||
setState("searching");
|
||||
searchForTracks(artist, track).then((tracks) => {
|
||||
tracks.forEach((track) => {
|
||||
const id = itemId(track);
|
||||
if (!(id in CML_CACHE)) {
|
||||
CML_CACHE[id] = track;
|
||||
setTracks([]);
|
||||
setSearchingState("searching");
|
||||
searchForTracks(debouncedArtistSearchTerm, debouncedTrackSearchTerm).then(
|
||||
(tracks) => {
|
||||
tracks.forEach((track) => {
|
||||
const id = itemId(track);
|
||||
if (!(id in CML_CACHE)) {
|
||||
CML_CACHE[id] = track;
|
||||
}
|
||||
});
|
||||
setTracks(tracks);
|
||||
if (tracks.length === 0) {
|
||||
setSearchingState("no-results");
|
||||
} else {
|
||||
setSearchingState("results");
|
||||
ReactTooltip.rebuild(); // Update tooltips so they appear.
|
||||
}
|
||||
});
|
||||
setItems(tracks);
|
||||
if (tracks.length === 0) {
|
||||
setState("no-results");
|
||||
} else {
|
||||
setState("results");
|
||||
ReactTooltip.rebuild(); // Update tooltips so they appear.
|
||||
}
|
||||
});
|
||||
}, [debouncedTrack, debouncedArtist, artist, track]);
|
||||
);
|
||||
}, [debouncedTrackSearchTerm, debouncedArtistSearchTerm]);
|
||||
return (
|
||||
<div className="library library-central">
|
||||
<span className="px-2">
|
||||
|
@ -212,19 +216,19 @@ export function CentralMusicLibrary() {
|
|||
className="form-control form-control-sm"
|
||||
type="text"
|
||||
placeholder="Filter by track..."
|
||||
value={track}
|
||||
onChange={(e) => setTrack(e.target.value)}
|
||||
value={trackSearchTerm}
|
||||
onChange={(e) => setTrackSearchTerm(e.target.value)}
|
||||
/>
|
||||
<input
|
||||
className="form-control form-control-sm mt-2"
|
||||
type="text"
|
||||
placeholder="Filter by artist..."
|
||||
value={artist}
|
||||
onChange={(e) => setArtist(e.target.value)}
|
||||
value={artistSearchTerm}
|
||||
onChange={(e) => setArtistSearchTerm(e.target.value)}
|
||||
/>
|
||||
</span>
|
||||
<div className="border-top mt-2"></div>
|
||||
<ResultsPlaceholder state={state} />
|
||||
<ResultsPlaceholder searchingState={searchingState} />
|
||||
<Droppable droppableId="$CML">
|
||||
{(provided, snapshot) => (
|
||||
<div
|
||||
|
@ -232,7 +236,7 @@ export function CentralMusicLibrary() {
|
|||
ref={provided.innerRef}
|
||||
{...provided.droppableProps}
|
||||
>
|
||||
{items.map((item, index) => (
|
||||
{tracks.map((item, index) => (
|
||||
<Item
|
||||
key={itemId(item)}
|
||||
item={item}
|
||||
|
@ -249,18 +253,20 @@ export function CentralMusicLibrary() {
|
|||
}
|
||||
|
||||
export function ManagedPlaylistLibrary({ libraryId }: { libraryId: string }) {
|
||||
const [track, setTrack] = useState("");
|
||||
const [artist, setArtist] = useState("");
|
||||
const debouncedTrack = useDebounce(track, 1000);
|
||||
const debouncedArtist = useDebounce(artist, 1000);
|
||||
const [trackSearchTerm, setTrackSearchTerm] = useState("");
|
||||
const [artistSearchTerm, setArtistSearchTerm] = useState("");
|
||||
const debouncedTrackSearchTerm = useDebounce(trackSearchTerm, 1000);
|
||||
const debouncedArtistSearchTerm = useDebounce(artistSearchTerm, 1000);
|
||||
const [items, setItems] = useState<Track[]>([]);
|
||||
|
||||
const [state, setState] = useState<searchingStateEnum>("not-searching");
|
||||
const [searchingState, setSearchingState] = useState<searchingStateEnum>(
|
||||
"not-searching"
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
async function load() {
|
||||
setItems([]);
|
||||
setState("searching");
|
||||
setSearchingState("searching");
|
||||
const libItems = await loadPlaylistLibrary(libraryId);
|
||||
libItems.forEach((item) => {
|
||||
const id = itemId(item);
|
||||
|
@ -270,9 +276,9 @@ export function ManagedPlaylistLibrary({ libraryId }: { libraryId: string }) {
|
|||
});
|
||||
setItems(libItems);
|
||||
if (libItems.length === 0) {
|
||||
setState("no-results");
|
||||
setSearchingState("no-results");
|
||||
} else {
|
||||
setState("results");
|
||||
setSearchingState("results");
|
||||
ReactTooltip.rebuild(); // Update tooltips so they appear.
|
||||
}
|
||||
}
|
||||
|
@ -285,19 +291,19 @@ export function ManagedPlaylistLibrary({ libraryId }: { libraryId: string }) {
|
|||
className="form-control form-control-sm"
|
||||
type="text"
|
||||
placeholder="Filter by track..."
|
||||
value={track}
|
||||
onChange={(e) => setTrack(e.target.value)}
|
||||
value={trackSearchTerm}
|
||||
onChange={(e) => setTrackSearchTerm(e.target.value)}
|
||||
/>
|
||||
<input
|
||||
className="form-control form-control-sm mt-2"
|
||||
type="text"
|
||||
placeholder="Filter by artist..."
|
||||
value={artist}
|
||||
onChange={(e) => setArtist(e.target.value)}
|
||||
value={artistSearchTerm}
|
||||
onChange={(e) => setArtistSearchTerm(e.target.value)}
|
||||
/>
|
||||
</span>
|
||||
<div className="border-top mt-2"></div>
|
||||
<ResultsPlaceholder state={state} />
|
||||
<ResultsPlaceholder searchingState={searchingState} />
|
||||
<Droppable droppableId="$CML">
|
||||
{(provided, snapshot) => (
|
||||
<div
|
||||
|
@ -311,14 +317,14 @@ export function ManagedPlaylistLibrary({ libraryId }: { libraryId: string }) {
|
|||
its.title
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.indexOf(debouncedTrack.toLowerCase()) > -1
|
||||
.indexOf(debouncedTrackSearchTerm.toLowerCase()) > -1
|
||||
)
|
||||
.filter(
|
||||
(its) =>
|
||||
its.artist
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.indexOf(debouncedArtist.toLowerCase()) > -1
|
||||
.indexOf(debouncedArtistSearchTerm.toLowerCase()) > -1
|
||||
)
|
||||
.map((item, index) => (
|
||||
<Item
|
||||
|
@ -343,12 +349,14 @@ export function AuxLibrary({ libraryId }: { libraryId: string }) {
|
|||
const debouncedQuery = useDebounce(searchQuery, 500);
|
||||
const [items, setItems] = useState<AuxItem[]>([]);
|
||||
|
||||
const [state, setState] = useState<searchingStateEnum>("not-searching");
|
||||
const [searchingState, setSearchingState] = useState<searchingStateEnum>(
|
||||
"not-searching"
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
async function load() {
|
||||
setItems([]);
|
||||
setState("searching");
|
||||
setSearchingState("searching");
|
||||
const libItems = await loadAuxLibrary(libraryId);
|
||||
libItems.forEach((item) => {
|
||||
const id = itemId(item);
|
||||
|
@ -359,9 +367,9 @@ export function AuxLibrary({ libraryId }: { libraryId: string }) {
|
|||
setItems(libItems);
|
||||
ReactTooltip.rebuild(); // Update tooltips so they appear.
|
||||
if (libItems.length === 0) {
|
||||
setState("no-results");
|
||||
setSearchingState("no-results");
|
||||
} else {
|
||||
setState("results");
|
||||
setSearchingState("results");
|
||||
}
|
||||
}
|
||||
load();
|
||||
|
@ -378,7 +386,7 @@ export function AuxLibrary({ libraryId }: { libraryId: string }) {
|
|||
/>
|
||||
</span>
|
||||
<div className="border-top mt-2"></div>
|
||||
<ResultsPlaceholder state={state} />
|
||||
<ResultsPlaceholder searchingState={searchingState} />
|
||||
<Droppable droppableId="$AUX">
|
||||
{(provided, snapshot) => (
|
||||
<div
|
||||
|
@ -410,20 +418,28 @@ export function AuxLibrary({ libraryId }: { libraryId: string }) {
|
|||
);
|
||||
}
|
||||
|
||||
export function ResultsPlaceholder({ state }: { state: string }) {
|
||||
export function ResultsPlaceholder({
|
||||
searchingState,
|
||||
}: {
|
||||
searchingState: searchingStateEnum;
|
||||
}) {
|
||||
return (
|
||||
<span
|
||||
className={state !== "results" ? "mt-5 text-center text-muted" : "d-none"}
|
||||
className={
|
||||
searchingState !== "results" ? "mt-5 text-center text-muted" : "d-none"
|
||||
}
|
||||
>
|
||||
{state === "not-searching" && <FaSearch size={56} />}
|
||||
{state === "searching" && <FaCog size={56} className="fa-spin" />}
|
||||
{state === "no-results" && <FaTimesCircle size={56} />}
|
||||
{searchingState === "not-searching" && <FaSearch size={56} />}
|
||||
{searchingState === "searching" && (
|
||||
<FaCog size={56} className="fa-spin" />
|
||||
)}
|
||||
{searchingState === "no-results" && <FaTimesCircle size={56} />}
|
||||
<br />
|
||||
{state === "not-searching"
|
||||
{searchingState === "not-searching"
|
||||
? "Enter a search term."
|
||||
: state === "searching"
|
||||
: searchingState === "searching"
|
||||
? "Searching..."
|
||||
: state === "no-results"
|
||||
: searchingState === "no-results"
|
||||
? "No results."
|
||||
: ""}
|
||||
</span>
|
||||
|
|
Loading…
Reference in a new issue