Merge branch 'master' into mstratford/smol-improvements

This commit is contained in:
Matthew Stratford 2021-01-26 22:11:36 +00:00
commit de67bb6f56
9 changed files with 165 additions and 54 deletions

View file

@ -76,7 +76,7 @@
"react-app-polyfill": "^1.0.4",
"react-beautiful-dnd": "^12.1.1",
"react-beforeunload": "^2.1.0",
"react-contextmenu": "^2.13.0",
"react-contexify": "^4.1.1",
"react-dev-utils": "^9.1.0",
"react-dnd": "^9.4.0",
"react-dnd-html5-backend": "^9.4.0",

View file

@ -207,3 +207,7 @@ $number-of-channels: 3;
}
}
}
.react-contexify__item__content svg {
margin: 4px 0.6em 0 0;
}

View file

@ -77,19 +77,19 @@ interface Album {
// TODO
}
interface TimeslotItemBase {
export interface TimeslotItemBase {
timeslotitemid: string;
channel: number;
weight: number;
title: string;
length: string;
trackid: number;
clean: boolean;
cue: number;
}
interface TimeslotItemCentral {
export interface TimeslotItemCentral {
type: "central";
trackid: number;
artist: string;
intro: number;
outro: number;
@ -104,10 +104,8 @@ export interface AuxItem {
title: string | number;
managedid: number;
length: string;
trackid: number;
expirydate: boolean | string;
expired: boolean;
recordid: string;
auxid: string;
}

View file

@ -614,7 +614,7 @@ const attemptTracklist = (player: number): AppThunk => async (
const state = getState().mixer.players[player];
if (
state.loadedItem &&
"album" in state.loadedItem &&
state.loadedItem.type === "central" &&
audioEngine.players[player]?.isPlaying
) {
//track

View file

@ -0,0 +1,37 @@
import React from "react";
import { FaTimes, FaPlayCircle } from "react-icons/fa";
import Modal from "react-modal";
import { Button } from "reactstrap";
interface AutoPlayoutProps {
isOpen: boolean;
close: () => any;
}
export function AutoPlayoutModal(props: AutoPlayoutProps) {
return (
<Modal isOpen={props.isOpen} onRequestClose={props.close}>
<div>
<h1 className="d-inline">
<FaPlayCircle className="mx-2" size={30} />
URY Automatic Playout
</h1>
<Button
onClick={props.close}
className="float-right pt-1"
color="primary"
>
<FaTimes />
</Button>
</div>
<hr />
<iframe
id="uploadIframe"
src={process.env.REACT_APP_MYRADIO_NONAPI_BASE + "/NIPSWeb/playout/"}
frameBorder="0"
title="URY Automatic Playout"
></iframe>
<div></div>
</Modal>
);
}

View file

@ -1,15 +1,16 @@
import React, { memo } from "react";
import { PlanItem, itemId } from "./state";
import { PlanItem, itemId, isTrack, isAux } from "./state";
import { Track, AuxItem } from "../api";
import { useDispatch, useSelector } from "react-redux";
import { RootState } from "../rootReducer";
import * as MixerState from "../mixer/state";
import { Draggable } from "react-beautiful-dnd";
import { ContextMenuTrigger } from "react-contextmenu";
import { contextMenu } from "react-contexify";
import "./item.scss";
export const TS_ITEM_MENU_ID = "SongMenu";
export const TS_ITEM_AUX_ID = "AuxMenu";
export const Item = memo(function Item({
item: x,
@ -22,7 +23,6 @@ export const Item = memo(function Item({
}) {
const dispatch = useDispatch();
const id = itemId(x);
const isReal = "timeslotitemid" in x;
const isGhost = "ghostid" in x;
const loadedItem = useSelector(
@ -45,6 +45,31 @@ export const Item = memo(function Item({
}
}
function openContextMenu(e: React.MouseEvent<HTMLDivElement, MouseEvent>) {
e.preventDefault();
if (isTrack(x)) {
contextMenu.show({
id: TS_ITEM_MENU_ID,
event: e,
props: {
id,
trackid: x.trackid,
title: x.title,
artist: x.artist,
},
});
} else if (isAux(x)) {
contextMenu.show({
id: TS_ITEM_MENU_ID,
event: e,
props: {
id,
title: x.title,
},
});
}
}
function generateTooltipData() {
let data = ["Title: " + x.title.toString()];
@ -78,33 +103,29 @@ export const Item = memo(function Item({
`${column >= 0 && isLoaded ? " active" : ""}`
}
onClick={triggerClick}
onContextMenu={openContextMenu}
{...provided.draggableProps}
{...provided.dragHandleProps}
data-tip={generateTooltipData()}
data-for="track-hover-tooltip"
>
<ContextMenuTrigger
id={isReal ? TS_ITEM_MENU_ID : ""}
collect={() => ({ id })}
<span className={"icon " + x.type} />
&nbsp;
{x.title.toString()}
{"artist" in x && x.artist !== "" && " - " + x.artist}
<small
className={
"border rounded border-danger text-danger p-1 m-1" +
("clean" in x && x.clean === false ? "" : " d-none")
}
>
<span className={"icon " + x.type} />
&nbsp;
{x.title.toString()}
{"artist" in x && x.artist !== "" && " - " + x.artist}
<small
className={
"border rounded border-danger text-danger p-1 m-1" +
("clean" in x && x.clean === false ? "" : " d-none")
}
>
Explicit
</small>
{showDebug && (
<code>
{itemId(x)} {"channel" in x && x.channel + "/" + x.weight}
</code>
)}
</ContextMenuTrigger>
Explicit
</small>
{showDebug && (
<code>
{itemId(x)} {"channel" in x && x.channel + "/" + x.weight}
</code>
)}
</div>
)}
</Draggable>

View file

@ -1,5 +1,6 @@
import React, { useState, useReducer, useEffect } from "react";
import { ContextMenu, MenuItem } from "react-contextmenu";
import { Menu, Item as CtxMenuItem } from "react-contexify";
import "react-contexify/dist/ReactContexify.min.css";
import { useBeforeunload } from "react-beforeunload";
import {
FaBookOpen,
@ -8,12 +9,14 @@ import {
FaMicrophone,
FaTrash,
FaUpload,
FaPlayCircle,
FaCircleNotch,
FaPencilAlt,
} from "react-icons/fa";
import { VUMeter } from "../optionsMenu/helpers/VUMeter";
import Stopwatch from "react-stopwatch";
import { TimeslotItem } from "../api";
import { MYRADIO_NON_API_BASE, TimeslotItem } from "../api";
import appLogo from "../assets/images/webstudio.svg";
import {
@ -54,6 +57,7 @@ import { CombinedNavAlertBar } from "../navbar";
import { OptionsMenu } from "../optionsMenu";
import { WelcomeModal } from "./WelcomeModal";
import { PisModal } from "./PISModal";
import { AutoPlayoutModal } from "./AutoPlayoutModal";
import { LibraryUploadModal } from "./LibraryUploadModal";
import { ImporterModal } from "./ImporterModal";
import "./channel.scss";
@ -92,6 +96,7 @@ function LibraryColumn() {
(state: RootState) => state.showplan
);
const [autoPlayoutModal, setAutoPlayoutModal] = useState(false);
const [showLibraryUploadModal, setShowLibraryModal] = useState(false);
const [showImporterModal, setShowImporterModal] = useState(false);
@ -101,6 +106,10 @@ function LibraryColumn() {
return (
<>
<AutoPlayoutModal
isOpen={autoPlayoutModal}
close={() => setAutoPlayoutModal(false)}
/>
<LibraryUploadModal
isOpen={showLibraryUploadModal}
close={() => setShowLibraryModal(false)}
@ -115,6 +124,16 @@ function LibraryColumn() {
<FaBookOpen className="mx-2" size={28} />
Libraries
</h2>
<Button
className="mr-1"
color="primary"
title="Auto Playout"
size="sm"
outline={true}
onClick={() => setAutoPlayoutModal(true)}
>
<FaPlayCircle /> Auto Playout
</Button>
<Button
className="mr-1"
color="primary"
@ -384,13 +403,6 @@ const Showplanner: React.FC<{ timeslotId: number }> = function({ timeslotId }) {
}
}
async function onCtxRemoveClick(e: any, data: { id: string }) {
dispatch(removeItem(timeslotId, data.id));
}
async function onCtxUnPlayedClick(e: any, data: { id: string }) {
dispatch(setItemPlayed({ itemId: data.id, played: false }));
}
// 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(() => {
@ -434,14 +446,39 @@ const Showplanner: React.FC<{ timeslotId: number }> = function({ timeslotId }) {
</div>
</DragDropContext>
</div>
<ContextMenu id={TS_ITEM_MENU_ID}>
<MenuItem onClick={onCtxRemoveClick}>
<Menu id={TS_ITEM_MENU_ID}>
<CtxMenuItem
onClick={(args) =>
dispatch(removeItem(timeslotId, (args.props as any).id))
}
>
<FaTrash /> Remove
</MenuItem>
<MenuItem onClick={onCtxUnPlayedClick}>
</CtxMenuItem>
<CtxMenuItem
onClick={(args) =>
dispatch(
setItemPlayed({ itemId: (args.props as any).id, played: false })
)
}
>
<FaCircleNotch /> Mark Unplayed
</MenuItem>
</ContextMenu>
</CtxMenuItem>
<CtxMenuItem
onClick={(args) => {
if ("trackid" in (args.props as any)) {
window.open(
MYRADIO_NON_API_BASE +
"/Library/editTrack?trackid=" +
(args.props as any).trackid
);
} else {
alert("Sorry, editing tracks is only possible right now.");
}
}}
>
<FaPencilAlt /> Edit Item
</CtxMenuItem>
</Menu>
<ReactTooltip
id="track-hover-tooltip"
// Sadly dataTip has to be a string, so let's format this the best we can. Split by something unusual to see in the data.

View file

@ -47,6 +47,16 @@ export function itemId(
throw new Error("Can't get id of unknown item.");
}
export function isTrack(
item: PlanItem | Track | AuxItem
): item is (api.TimeslotItemBase & api.TimeslotItemCentral) | Track {
return item.type === "central";
}
export function isAux(item: PlanItem | Track | AuxItem): item is AuxItem {
return "auxid" in item;
}
interface ShowplanState {
planLoading: boolean;
planLoadError: string | null;
@ -418,7 +428,11 @@ export const addItem = (
const idForServer =
newItemData.type === "central"
? `${newItemData.album.recordid}-${newItemData.trackid}`
: `ManagedDB-${newItemData.managedid}`;
: "managedid" in newItemData
? `ManagedDB-${newItemData.managedid}`
: null;
if (!idForServer) return; // Something went very wrong
dispatch(showplan.actions.insertGhost(ghost));
ops.push({

View file

@ -3299,7 +3299,7 @@ class-utils@^0.3.5:
isobject "^3.0.0"
static-extend "^0.1.1"
classnames@^2.2.3, classnames@^2.2.5:
classnames@^2.2.3, classnames@^2.2.6:
version "2.2.6"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
@ -10100,13 +10100,13 @@ react-beforeunload@^2.1.0:
dependencies:
prop-types "^15.7.2"
react-contextmenu@^2.13.0:
version "2.13.0"
resolved "https://registry.yarnpkg.com/react-contextmenu/-/react-contextmenu-2.13.0.tgz#dabaea63124e30c85f1b4245c095b7045d013459"
integrity sha512-hhFuJX4di0zGV7H7pXPn42U70OZbGpQD+PxcdmKStNT5mebSjI+inhOuFESDmDbqVsN/f99hI5/nw95oXTVRXQ==
react-contexify@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/react-contexify/-/react-contexify-4.1.1.tgz#f5eba1ad82a923c033c91d0abcea1da0a71ebaa1"
integrity sha512-WJeRI4ohHEOmNiH0xb62a/eV+5ae168FB7H6pfbeEVJkf0UN7D5H99l6b89poc2LHKN1gOimFjREyY8quGVsXA==
dependencies:
classnames "^2.2.5"
object-assign "^4.1.0"
classnames "^2.2.6"
prop-types "^15.6.2"
react-dev-utils@^10.2.1:
version "10.2.1"