Merge branch 'master' into mstratford/smol-improvements
This commit is contained in:
commit
de67bb6f56
9 changed files with 165 additions and 54 deletions
|
@ -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",
|
||||
|
|
|
@ -207,3 +207,7 @@ $number-of-channels: 3;
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.react-contexify__item__content svg {
|
||||
margin: 4px 0.6em 0 0;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
37
src/showplanner/AutoPlayoutModal.tsx
Normal file
37
src/showplanner/AutoPlayoutModal.tsx
Normal 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>
|
||||
);
|
||||
}
|
|
@ -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,14 +103,11 @@ 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} />
|
||||
|
||||
|
@ -104,7 +126,6 @@ export const Item = memo(function Item({
|
|||
{itemId(x)} {"channel" in x && x.channel + "/" + x.weight}
|
||||
</code>
|
||||
)}
|
||||
</ContextMenuTrigger>
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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({
|
||||
|
|
14
yarn.lock
14
yarn.lock
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue