implement removing

This commit is contained in:
Marks Polakovs 2019-11-26 13:00:15 +00:00
parent ea5663c838
commit f2928ab266
6 changed files with 106 additions and 7 deletions

View file

@ -17,6 +17,7 @@
"qs": "^6.9.1",
"react": "^0.0.0-experimental-38dd17ab9",
"react-beautiful-dnd": "^12.1.1",
"react-contextmenu": "^2.13.0",
"react-dnd": "^9.4.0",
"react-dnd-html5-backend": "^9.4.0",
"react-dom": "^0.0.0-experimental-38dd17ab9",

View file

@ -61,4 +61,19 @@ html, body, #root {
margin: 0;
width: 100%;
height: 100%;
}
.react-contextmenu--visible {
background-color: white;
padding: 0.7em;
border: 1.5px solid grey;
box-shadow: 3px 3px 6px grey;
}
.react-contextmenu-item {
cursor: pointer;
}
.react-contextmenu-item--disabled {
cursor: not-allowed;
}

View file

@ -166,6 +166,12 @@ export type UpdateOp =
channel: number;
weight: number;
id: string;
}
| {
op: "RemoveItem";
timeslotitemid: string;
channel: number;
weight: number;
};
interface OpResult {

View file

@ -1,6 +1,7 @@
import React, { useState, useReducer, useRef, useEffect } from "react";
import { DndProvider, useDrag, useDrop } from "react-dnd";
import HTML5Backend from "react-dnd-html5-backend";
import { ContextMenu, ContextMenuTrigger, MenuItem } from "react-contextmenu";
import {
showPlanResource,
@ -26,15 +27,20 @@ import {
getShowplan,
itemId,
moveItem,
addItem
addItem,
removeItem
} from "./state";
const CML_CACHE: { [recordid_trackid: string]: Track } = {};
const TS_ITEM_MENU_ID = "SongMenu";
function Item({ item: x, index }: { item: PlanItem | Track; index: number }) {
const id = itemId(x);
const isReal = "timeslotitemid" in x;
const isGhost = "ghostid" in x;
return (
<Draggable draggableId={id} index={index} isDragDisabled={"ghostid" in x}>
<Draggable draggableId={id} index={index} isDragDisabled={isGhost}>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
@ -43,11 +49,13 @@ function Item({ item: x, index }: { item: PlanItem | Track; index: number }) {
{...provided.draggableProps}
{...provided.dragHandleProps}
>
{x.title}
{"artist" in x && " - " + x.artist}
<code>
{itemId(x)} {"channel" in x && x.channel + "/" + x.weight}
</code>
<ContextMenuTrigger id={isReal ? TS_ITEM_MENU_ID : ""} collect={() => ({ id })}>
{x.title}
{"artist" in x && " - " + x.artist}
<code>
{itemId(x)} {"channel" in x && x.channel + "/" + x.weight}
</code>
</ContextMenuTrigger>
</div>
)}
</Draggable>
@ -178,6 +186,11 @@ const Showplanner: React.FC<{ timeslotId: number }> = function({ timeslotId }) {
);
}
}
async function onCtxRemoveClick(e: any, data: { id: string }) {
dispatch(removeItem(timeslotId, data.id));
}
if (showplan === null) {
return (
<div className="sp-container">
@ -215,6 +228,10 @@ const Showplanner: React.FC<{ timeslotId: number }> = function({ timeslotId }) {
<LibraryColumn />
</DragDropContext>
</div>
<ContextMenu id={TS_ITEM_MENU_ID}>
<MenuItem onClick={onCtxRemoveClick}>Remove</MenuItem>
</ContextMenu>
</div>
);
};

View file

@ -83,6 +83,13 @@ const showplan = createSlice({
case "AddItem":
// no-op
break;
case "RemoveItem":
const idx = state.plan!.findIndex(x => itemId(x) === op.timeslotitemid);
if (idx < 0) {
throw new Error();
}
state.plan!.splice(idx, 1);
break;
default:
throw new Error();
}
@ -303,6 +310,46 @@ export const addItem = (
);
};
export const removeItem = (
timeslotId: number,
itemid: string
): AppThunk => async (dispatch, getState) => {
// This is a simplified version of the second case of moveItem
const plan = cloneDeep(getState().showplan.plan!);
const item = plan.find(x => itemId(x) === itemid)!;
const planColumn = plan
.filter(x => x.channel === item.channel)
.sort((a, b) => a.weight - b.weight);
const ops: api.UpdateOp[] = [];
ops.push({
op: "RemoveItem",
timeslotitemid: itemid,
channel: item.channel,
weight: item.weight
});
planColumn.splice(planColumn.indexOf(item), 1);
for (let i = item.weight; i < planColumn.length; i++) {
const movingItem = planColumn[i];
ops.push({
op: "MoveItem",
timeslotitemid: itemId(item),
oldchannel: item.channel,
oldweight: item.weight,
channel: item.channel,
weight: item.weight - 1
});
movingItem.weight -= 1;
}
const result = await api.updateShowplan(timeslotId, ops);
if (!result.every(x => x.status)) {
dispatch(showplan.actions.planSaveError("Server says no!"));
return;
}
dispatch(showplan.actions.applyOps(ops));
};
export const getShowplan = (timeslotId: number): AppThunk => async dispatch => {
dispatch(showplan.actions.getShowplanStarting());
try {

View file

@ -2545,6 +2545,11 @@ class-utils@^0.3.5:
isobject "^3.0.0"
static-extend "^0.1.1"
classnames@^2.2.5:
version "2.2.6"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
clean-css@4.2.x:
version "4.2.1"
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.1.tgz#2d411ef76b8569b6d0c84068dabe85b0aa5e5c17"
@ -8153,6 +8158,14 @@ react-beautiful-dnd@^12.1.1:
redux "^4.0.4"
use-memo-one "^1.1.1"
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==
dependencies:
classnames "^2.2.5"
object-assign "^4.1.0"
react-dev-utils@^9.1.0:
version "9.1.0"
resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-9.1.0.tgz#3ad2bb8848a32319d760d0a84c56c14bdaae5e81"