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", "qs": "^6.9.1",
"react": "^0.0.0-experimental-38dd17ab9", "react": "^0.0.0-experimental-38dd17ab9",
"react-beautiful-dnd": "^12.1.1", "react-beautiful-dnd": "^12.1.1",
"react-contextmenu": "^2.13.0",
"react-dnd": "^9.4.0", "react-dnd": "^9.4.0",
"react-dnd-html5-backend": "^9.4.0", "react-dnd-html5-backend": "^9.4.0",
"react-dom": "^0.0.0-experimental-38dd17ab9", "react-dom": "^0.0.0-experimental-38dd17ab9",

View file

@ -62,3 +62,18 @@ html, body, #root {
width: 100%; width: 100%;
height: 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; channel: number;
weight: number; weight: number;
id: string; id: string;
}
| {
op: "RemoveItem";
timeslotitemid: string;
channel: number;
weight: number;
}; };
interface OpResult { interface OpResult {

View file

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

View file

@ -83,6 +83,13 @@ const showplan = createSlice({
case "AddItem": case "AddItem":
// no-op // no-op
break; 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: default:
throw new Error(); 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 => { export const getShowplan = (timeslotId: number): AppThunk => async dispatch => {
dispatch(showplan.actions.getShowplanStarting()); dispatch(showplan.actions.getShowplanStarting());
try { try {

View file

@ -2545,6 +2545,11 @@ class-utils@^0.3.5:
isobject "^3.0.0" isobject "^3.0.0"
static-extend "^0.1.1" 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: clean-css@4.2.x:
version "4.2.1" version "4.2.1"
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.1.tgz#2d411ef76b8569b6d0c84068dabe85b0aa5e5c17" 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" redux "^4.0.4"
use-memo-one "^1.1.1" 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: react-dev-utils@^9.1.0:
version "9.1.0" version "9.1.0"
resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-9.1.0.tgz#3ad2bb8848a32319d760d0a84c56c14bdaae5e81" resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-9.1.0.tgz#3ad2bb8848a32319d760d0a84c56c14bdaae5e81"