implement removing
This commit is contained in:
parent
ea5663c838
commit
f2928ab266
6 changed files with 106 additions and 7 deletions
|
@ -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",
|
||||||
|
|
15
src/App.css
15
src/App.css
|
@ -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;
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
13
yarn.lock
13
yarn.lock
|
@ -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"
|
||||||
|
|
Loading…
Reference in a new issue