Use bundled Font Awesome instead of CDN
This commit is contained in:
parent
7c8eff5520
commit
94ffe8dd74
9 changed files with 210 additions and 178 deletions
|
@ -78,6 +78,7 @@
|
|||
"react-dnd": "^9.4.0",
|
||||
"react-dnd-html5-backend": "^9.4.0",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-icons": "^3.9.0",
|
||||
"react-live-clock": "^4.0.5",
|
||||
"react-modal": "^3.11.2",
|
||||
"react-redux": "^7.1.3",
|
||||
|
|
|
@ -41,7 +41,6 @@
|
|||
-->
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
|
||||
integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
|
||||
<script src="https://kit.fontawesome.com/255e917109.js" crossorigin="anonymous"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"
|
||||
integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
|
||||
|
|
13
src/App.css
13
src/App.css
|
@ -95,16 +95,23 @@
|
|||
background-color: #78acf1;
|
||||
}
|
||||
|
||||
.sp-track .circle {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
display: inline-block;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.sp-track .central {
|
||||
color: green;
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
.sp-track .aux {
|
||||
color: #07F;
|
||||
background-color: #07F;
|
||||
}
|
||||
|
||||
.sp-track .ghost {
|
||||
color: gray;
|
||||
background-color: gray;
|
||||
}
|
||||
|
||||
.sp-track-active {
|
||||
|
|
|
@ -2,6 +2,8 @@ import React, { useRef, useEffect } from "react";
|
|||
import { useDispatch, useSelector } from "react-redux";
|
||||
import Clock from "react-live-clock";
|
||||
|
||||
import { FaClock, FaUser } from "react-icons/fa";
|
||||
|
||||
import { RootState } from "../rootReducer";
|
||||
|
||||
import * as BroadcastState from "../broadcast/state";
|
||||
|
@ -112,7 +114,8 @@ export function NavBar() {
|
|||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<span className="fa fa-clock-o"></span>
|
||||
<FaClock />
|
||||
|
||||
{sessionState.currentTimeslot &&
|
||||
sessionState.currentTimeslot.start_time}
|
||||
</a>
|
||||
|
@ -142,7 +145,8 @@ export function NavBar() {
|
|||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<i className="fa fa-user-o"></i>
|
||||
<FaUser />
|
||||
|
||||
{sessionState.currentUser?.fname} {sessionState.currentUser?.sname}
|
||||
</a>
|
||||
<div className="dropdown-menu" aria-labelledby="dropdown07">
|
||||
|
|
|
@ -6,6 +6,7 @@ import { RootState } from "../rootReducer";
|
|||
|
||||
import * as MixerState from "../mixer/state";
|
||||
import { Draggable } from "react-beautiful-dnd";
|
||||
import { FaCircle } from "react-icons/fa";
|
||||
import { ContextMenuTrigger } from "react-contextmenu";
|
||||
|
||||
export const TS_ITEM_MENU_ID = "SongMenu";
|
||||
|
@ -13,7 +14,7 @@ export const TS_ITEM_MENU_ID = "SongMenu";
|
|||
export const Item = memo(function Item({
|
||||
item: x,
|
||||
index,
|
||||
column,
|
||||
column
|
||||
}: {
|
||||
item: PlanItem | Track | AuxItem;
|
||||
index: number;
|
||||
|
@ -24,11 +25,14 @@ export const Item = memo(function Item({
|
|||
const isReal = "timeslotitemid" in x;
|
||||
const isGhost = "ghostid" in x;
|
||||
|
||||
const playerState = useSelector(
|
||||
(state: RootState) => column > -1 ? state.mixer.players[column] : undefined
|
||||
const playerState = useSelector((state: RootState) =>
|
||||
column > -1 ? state.mixer.players[column] : undefined
|
||||
);
|
||||
|
||||
const isLoaded = playerState && playerState.loadedItem !== null && itemId(playerState.loadedItem) === id;
|
||||
const isLoaded =
|
||||
playerState &&
|
||||
playerState.loadedItem !== null &&
|
||||
itemId(playerState.loadedItem) === id;
|
||||
|
||||
const showDebug = useSelector(
|
||||
(state: RootState) => state.settings.showDebugInfo
|
||||
|
@ -41,7 +45,11 @@ export const Item = memo(function Item({
|
|||
}
|
||||
|
||||
return (
|
||||
<Draggable draggableId={id} index={index} isDragDisabled={isGhost || isLoaded}>
|
||||
<Draggable
|
||||
draggableId={id}
|
||||
index={index}
|
||||
isDragDisabled={isGhost || isLoaded}
|
||||
>
|
||||
{(provided, snapshot) => (
|
||||
<div
|
||||
ref={provided.innerRef}
|
||||
|
@ -62,7 +70,8 @@ export const Item = memo(function Item({
|
|||
id={isReal ? TS_ITEM_MENU_ID : ""}
|
||||
collect={() => ({ id })}
|
||||
>
|
||||
<i className={"fa fa-circle " + x.type}></i>
|
||||
<span className={"circle " + x.type} />
|
||||
|
||||
{x.title.toString()}
|
||||
{"artist" in x && " - " + x.artist}
|
||||
<small
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
import React from "react";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import {
|
||||
FaLevelDownAlt,
|
||||
FaPlayCircle,
|
||||
FaRedo,
|
||||
FaPlay,
|
||||
FaPause,
|
||||
FaStop
|
||||
} from "react-icons/fa";
|
||||
import { RootState } from "../rootReducer";
|
||||
import * as MixerState from "../mixer/state";
|
||||
import { secToHHMM } from "../lib/utils";
|
||||
|
@ -28,9 +36,12 @@ export function Player({ id }: { id: number }) {
|
|||
? "btn-primary"
|
||||
: "btn-outline-secondary") + " btn btn-sm col-4 sp-play-on-load"
|
||||
}
|
||||
onClick={() => dispatch(MixerState.toggleAutoAdvance({player: id}))}
|
||||
onClick={() =>
|
||||
dispatch(MixerState.toggleAutoAdvance({ player: id }))
|
||||
}
|
||||
>
|
||||
<i className="fa fa-level-down-alt"></i> Auto Advance
|
||||
<FaLevelDownAlt />
|
||||
Auto Advance
|
||||
</button>
|
||||
<button
|
||||
className={
|
||||
|
@ -38,9 +49,12 @@ export function Player({ id }: { id: number }) {
|
|||
? "btn-primary"
|
||||
: "btn-outline-secondary") + " btn btn-sm col-4 sp-play-on-load"
|
||||
}
|
||||
onClick={() => dispatch(MixerState.togglePlayOnLoad({ player: id }))}
|
||||
onClick={() =>
|
||||
dispatch(MixerState.togglePlayOnLoad({ player: id }))
|
||||
}
|
||||
>
|
||||
<i className="far fa-play-circle"></i> Play on Load
|
||||
<FaPlayCircle />
|
||||
Play on Load
|
||||
</button>
|
||||
<button
|
||||
className={
|
||||
|
@ -50,7 +64,8 @@ export function Player({ id }: { id: number }) {
|
|||
}
|
||||
onClick={() => dispatch(MixerState.toggleRepeat({ player: id }))}
|
||||
>
|
||||
<i className="fa fa-redo"></i> Repeat {playerState.repeat}
|
||||
<FaRedo />
|
||||
Repeat {playerState.repeat}
|
||||
</button>
|
||||
</div>
|
||||
<div className="card-body p-0">
|
||||
|
@ -97,7 +112,7 @@ export function Player({ id }: { id: number }) {
|
|||
: ""
|
||||
}
|
||||
>
|
||||
<i className="fas fa-play"></i>
|
||||
<FaPlay />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => dispatch(MixerState.pause(id))}
|
||||
|
@ -105,7 +120,7 @@ export function Player({ id }: { id: number }) {
|
|||
playerState.state === "paused" ? "sp-state-paused" : ""
|
||||
}
|
||||
>
|
||||
<i className="fas fa-pause"></i>
|
||||
<FaPause />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => dispatch(MixerState.stop(id))}
|
||||
|
@ -113,7 +128,7 @@ export function Player({ id }: { id: number }) {
|
|||
playerState.state === "stopped" ? "sp-state-stopped" : ""
|
||||
}
|
||||
>
|
||||
<i className="fas fa-stop"></i>
|
||||
<FaStop />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -141,11 +156,19 @@ export function Player({ id }: { id: number }) {
|
|||
- in
|
||||
</span>
|
||||
)}
|
||||
<div className={"m-0 graph" + ((playerState.loading !== -1) ? " loading" : "")} id={"waveform-" + id}
|
||||
style={(playerState.loading !== -1) ? {
|
||||
width: (playerState.loading*100) + "%"} : {}}
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
className={
|
||||
"m-0 graph" + (playerState.loading !== -1 ? " loading" : "")
|
||||
}
|
||||
id={"waveform-" + id}
|
||||
style={
|
||||
playerState.loading !== -1
|
||||
? {
|
||||
width: playerState.loading * 100 + "%"
|
||||
}
|
||||
: {}
|
||||
}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,12 +1,7 @@
|
|||
import React, { useState, useReducer, useEffect, memo } from "react";
|
||||
import { ContextMenu, MenuItem } from "react-contextmenu";
|
||||
import { useBeforeunload } from "react-beforeunload";
|
||||
import {
|
||||
MYRADIO_NON_API_BASE,
|
||||
getUserPlaylists,
|
||||
getAuxPlaylists,
|
||||
ManagedPlaylist
|
||||
} from "../api";
|
||||
import { FaCaretSquareDown, FaAlignJustify } from "react-icons/fa";
|
||||
|
||||
import { TimeslotItem } from "../api";
|
||||
|
||||
|
@ -44,7 +39,7 @@ import { Player, USE_REAL_GAIN_VALUE } from "./Player";
|
|||
import { CombinedNavAlertBar } from "../navbar";
|
||||
import { OptionsMenu } from "../optionsMenu";
|
||||
import { WelcomeModal } from "./WelcomeModal";
|
||||
import {PisModal} from "./PISModal";
|
||||
import { PisModal } from "./PISModal";
|
||||
|
||||
function Column({ id, data }: { id: number; data: PlanItem[] }) {
|
||||
return (
|
||||
|
@ -127,7 +122,7 @@ function LibraryColumn() {
|
|||
<span
|
||||
className={sauce === "None" ? "mt-5 text-center text-muted" : "d-none"}
|
||||
>
|
||||
<i className="far fa-2x fa-caret-square-down"></i>
|
||||
<FaCaretSquareDown />
|
||||
<br />
|
||||
Select a library to search.
|
||||
</span>
|
||||
|
@ -303,7 +298,8 @@ const Showplanner: React.FC<{ timeslotId: number }> = function({ timeslotId }) {
|
|||
className="btn btn-outline-dark btn-sm mb-0"
|
||||
onClick={() => toggleSidebar()}
|
||||
>
|
||||
<i className="fas fa-align-justify mb-2"></i>Toggle Sidebar
|
||||
<FaAlignJustify />
|
||||
Toggle Sidebar
|
||||
</span>
|
||||
</div>
|
||||
<div id="sidebar" className="sp-main-col">
|
||||
|
|
|
@ -3,160 +3,146 @@ import useDebounce from "../lib/useDebounce";
|
|||
import { Track, searchForTracks, loadAuxLibrary, AuxItem } from "../api";
|
||||
import { itemId } from "./state";
|
||||
import { Droppable } from "react-beautiful-dnd";
|
||||
import { FaCog, FaSearch, FaTimesCircle } from "react-icons/fa";
|
||||
import { Item } from "./Item";
|
||||
|
||||
export const CML_CACHE: { [recordid_trackid: string]: Track } = {};
|
||||
|
||||
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 [track, setTrack] = useState("");
|
||||
const [artist, setArtist] = useState("");
|
||||
const debouncedTrack = useDebounce(track, 1000);
|
||||
const debouncedArtist = useDebounce(artist, 1000);
|
||||
const [items, setItems] = useState<Track[]>([]);
|
||||
|
||||
type searchingStateEnum =
|
||||
| "searching"
|
||||
| "not-searching"
|
||||
| "results"
|
||||
| "no-results";
|
||||
const [state, setState] = useState<searchingStateEnum>("not-searching");
|
||||
useEffect(() => {
|
||||
if (debouncedTrack === "" && debouncedArtist === "") {
|
||||
setItems([]);
|
||||
setState("not-searching");
|
||||
return;
|
||||
}
|
||||
setItems([]);
|
||||
setState("searching");
|
||||
searchForTracks(artist, track).then(tracks => {
|
||||
if (tracks.length === 0) {
|
||||
setState("no-results");
|
||||
} else {
|
||||
setState("results");
|
||||
}
|
||||
tracks.forEach(track => {
|
||||
const id = itemId(track);
|
||||
if (!(id in CML_CACHE)) {
|
||||
CML_CACHE[id] = track;
|
||||
}
|
||||
});
|
||||
setItems(tracks);
|
||||
});
|
||||
}, [debouncedTrack, debouncedArtist]);
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
className="form-control"
|
||||
type="text"
|
||||
placeholder="Filter by track..."
|
||||
value={track}
|
||||
onChange={e => setTrack(e.target.value)}
|
||||
/>
|
||||
<input
|
||||
className="form-control"
|
||||
type="text"
|
||||
placeholder="Filter by artist..."
|
||||
value={artist}
|
||||
onChange={e => setArtist(e.target.value)}
|
||||
/>
|
||||
<span
|
||||
className={
|
||||
state !== "results"
|
||||
? "mt-5 text-center text-muted"
|
||||
: "d-none"
|
||||
}
|
||||
>
|
||||
<i
|
||||
className={
|
||||
"fa fa-2x " +
|
||||
(state === "not-searching"
|
||||
? "fa-search"
|
||||
: state === "searching"
|
||||
? "fa-cog fa-spin"
|
||||
: state === "no-results"
|
||||
? "fa-times-circle"
|
||||
: "d-none")
|
||||
}
|
||||
></i>
|
||||
<br />
|
||||
{state === "not-searching"
|
||||
? "Enter a search term."
|
||||
: state === "searching"
|
||||
? "Searching..."
|
||||
: state === "no-results"
|
||||
? "No results."
|
||||
: ""}
|
||||
</span>
|
||||
<Droppable droppableId="$CML">
|
||||
{(provided, snapshot) => (
|
||||
<div ref={provided.innerRef} {...provided.droppableProps}>
|
||||
{items.map((item, index) => (
|
||||
<Item
|
||||
key={itemId(item)}
|
||||
item={item}
|
||||
index={index}
|
||||
column={-1}
|
||||
/>
|
||||
))}
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</>
|
||||
);
|
||||
type searchingStateEnum =
|
||||
| "searching"
|
||||
| "not-searching"
|
||||
| "results"
|
||||
| "no-results";
|
||||
const [state, setState] = useState<searchingStateEnum>("not-searching");
|
||||
useEffect(() => {
|
||||
if (debouncedTrack === "" && debouncedArtist === "") {
|
||||
setItems([]);
|
||||
setState("not-searching");
|
||||
return;
|
||||
}
|
||||
setItems([]);
|
||||
setState("searching");
|
||||
searchForTracks(artist, track).then(tracks => {
|
||||
if (tracks.length === 0) {
|
||||
setState("no-results");
|
||||
} else {
|
||||
setState("results");
|
||||
}
|
||||
tracks.forEach(track => {
|
||||
const id = itemId(track);
|
||||
if (!(id in CML_CACHE)) {
|
||||
CML_CACHE[id] = track;
|
||||
}
|
||||
});
|
||||
setItems(tracks);
|
||||
});
|
||||
}, [debouncedTrack, debouncedArtist]);
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
className="form-control"
|
||||
type="text"
|
||||
placeholder="Filter by track..."
|
||||
value={track}
|
||||
onChange={e => setTrack(e.target.value)}
|
||||
/>
|
||||
<input
|
||||
className="form-control"
|
||||
type="text"
|
||||
placeholder="Filter by artist..."
|
||||
value={artist}
|
||||
onChange={e => setArtist(e.target.value)}
|
||||
/>
|
||||
<span
|
||||
className={
|
||||
state !== "results" ? "mt-5 text-center text-muted" : "d-none"
|
||||
}
|
||||
>
|
||||
{state === "not-searching" && <FaSearch />}
|
||||
{state === "searching" && <FaCog className="fa-spin" />}
|
||||
{state === "no-results" && <FaTimesCircle />}
|
||||
<br />
|
||||
{state === "not-searching"
|
||||
? "Enter a search term."
|
||||
: state === "searching"
|
||||
? "Searching..."
|
||||
: state === "no-results"
|
||||
? "No results."
|
||||
: ""}
|
||||
</span>
|
||||
<Droppable droppableId="$CML">
|
||||
{(provided, snapshot) => (
|
||||
<div ref={provided.innerRef} {...provided.droppableProps}>
|
||||
{items.map((item, index) => (
|
||||
<Item key={itemId(item)} item={item} index={index} column={-1} />
|
||||
))}
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const AUX_CACHE: { [auxid: string]: AuxItem } = {};
|
||||
|
||||
export function AuxLibrary({ libraryId }: { libraryId: string }) {
|
||||
const [title, setTitle] = useState("");
|
||||
const debouncedTitle = useDebounce(title, 1000);
|
||||
const [items, setItems] = useState<AuxItem[]>([]);
|
||||
const [title, setTitle] = useState("");
|
||||
const debouncedTitle = useDebounce(title, 1000);
|
||||
const [items, setItems] = useState<AuxItem[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
async function load() {
|
||||
const libItems = await loadAuxLibrary(libraryId);
|
||||
libItems.forEach(item => {
|
||||
const id = itemId(item);
|
||||
if (!(id in AUX_CACHE)) {
|
||||
AUX_CACHE[id] = item;
|
||||
}
|
||||
});
|
||||
setItems(libItems);
|
||||
}
|
||||
load();
|
||||
}, [libraryId]);
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
className="form-control"
|
||||
type="text"
|
||||
placeholder="Filter..."
|
||||
value={title}
|
||||
onChange={e => setTitle(e.target.value)}
|
||||
/>
|
||||
<Droppable droppableId="$AUX">
|
||||
{(provided, snapshot) => (
|
||||
<div ref={provided.innerRef} {...provided.droppableProps}>
|
||||
{items
|
||||
.filter(
|
||||
its =>
|
||||
its.title.toString()
|
||||
.toLowerCase()
|
||||
.indexOf(its.title.toString().toLowerCase()) > -1
|
||||
)
|
||||
.map((item, index) => (
|
||||
<Item
|
||||
key={itemId(item)}
|
||||
item={item}
|
||||
index={index}
|
||||
column={-1}
|
||||
/>
|
||||
))}
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</>
|
||||
);
|
||||
useEffect(() => {
|
||||
async function load() {
|
||||
const libItems = await loadAuxLibrary(libraryId);
|
||||
libItems.forEach(item => {
|
||||
const id = itemId(item);
|
||||
if (!(id in AUX_CACHE)) {
|
||||
AUX_CACHE[id] = item;
|
||||
}
|
||||
});
|
||||
setItems(libItems);
|
||||
}
|
||||
load();
|
||||
}, [libraryId]);
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
className="form-control"
|
||||
type="text"
|
||||
placeholder="Filter..."
|
||||
value={title}
|
||||
onChange={e => setTitle(e.target.value)}
|
||||
/>
|
||||
<Droppable droppableId="$AUX">
|
||||
{(provided, snapshot) => (
|
||||
<div ref={provided.innerRef} {...provided.droppableProps}>
|
||||
{items
|
||||
.filter(
|
||||
its =>
|
||||
its.title
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.indexOf(its.title.toString().toLowerCase()) > -1
|
||||
)
|
||||
.map((item, index) => (
|
||||
<Item
|
||||
key={itemId(item)}
|
||||
item={item}
|
||||
index={index}
|
||||
column={-1}
|
||||
/>
|
||||
))}
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8863,6 +8863,13 @@ react-error-overlay@^6.0.3:
|
|||
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.7.tgz#1dcfb459ab671d53f660a991513cb2f0a0553108"
|
||||
integrity sha512-TAv1KJFh3RhqxNvhzxj6LeT5NWklP6rDr2a0jaTfsZ5wSZWHOGeqQyejUp3xxLfPt2UpyJEcVQB/zyPcmonNFA==
|
||||
|
||||
react-icons@^3.9.0:
|
||||
version "3.9.0"
|
||||
resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-3.9.0.tgz#89a00f20a0e02e6bfd899977eaf46eb4624239d5"
|
||||
integrity sha512-gKbYKR+4QsD3PmIHLAM9TDDpnaTsr3XZeK1NTAb6WQQ+gxEdJ0xuCgLq0pxXdS7Utg2AIpcVhM1ut/jlDhcyNg==
|
||||
dependencies:
|
||||
camelcase "^5.0.0"
|
||||
|
||||
react-is@^16.12.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.9.0:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
|
|
Loading…
Reference in a new issue