Merge remote-tracking branch 'origin/master' into marks/party-mode

This commit is contained in:
Marks Polakovs 2021-03-13 10:43:55 +00:00
commit 840e969113
9 changed files with 139 additions and 77 deletions

View file

@ -1,6 +1,6 @@
{
"name": "webstudio",
"version": "1.5.3",
"version": "1.5.4",
"private": true,
"dependencies": {
"@babel/core": "7.6.0",

View file

@ -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) {

View file

@ -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);
}
}
}

View file

@ -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

View file

@ -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"

View file

@ -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",

View file

@ -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()));
}
};

View file

@ -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>
);

View file

@ -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>