Fully implement adding and moving

This commit is contained in:
Marks Polakovs 2019-11-26 10:35:49 +00:00
parent 9b60cd178f
commit ea5663c838
3 changed files with 124 additions and 21 deletions

View file

@ -152,14 +152,21 @@ export function searchForTracks(
});
}
export type UpdateOp = {
export type UpdateOp =
| {
op: "MoveItem";
timeslotitemid: string;
oldchannel: number;
oldweight: number;
channel: number;
weight: number;
};
}
| {
op: "AddItem";
channel: number;
weight: number;
id: string;
};
interface OpResult {
status: boolean;

View file

@ -20,7 +20,14 @@ import {
import useDebounce from "../lib/useDebounce";
import { useSelector, useDispatch } from "react-redux";
import { RootState } from "../rootReducer";
import { Plan, PlanItem, getShowplan, itemId, moveItem } from "./state";
import {
Plan,
PlanItem,
getShowplan,
itemId,
moveItem,
addItem
} from "./state";
const CML_CACHE: { [recordid_trackid: string]: Track } = {};
@ -81,8 +88,7 @@ function CentralMusicLibrary() {
}
searchForTracks("", track).then(tracks => {
tracks.forEach(track => {
const id =
track.album.recordid.toString(10) + "-" + track.trackid.toString(10);
const id = itemId(track);
if (!(id in CML_CACHE)) {
CML_CACHE[id] = track;
}
@ -102,7 +108,7 @@ function CentralMusicLibrary() {
{(provided, snapshot) => (
<div ref={provided.innerRef} {...provided.droppableProps}>
{items.map((item, index) => (
<Item key={item.trackid} item={item} index={index} />
<Item key={itemId(item)} item={item} index={index} />
))}
{provided.placeholder}
</div>
@ -153,7 +159,15 @@ const Showplanner: React.FC<{ timeslotId: number }> = function({ timeslotId }) {
}
if (result.draggableId[0] === "T") {
// this is a track from the CML
//TODO
const data = CML_CACHE[result.draggableId];
const newItem: TimeslotItem = {
type: "central",
timeslotitemid: "CHANGEME",
channel: parseInt(result.destination!.droppableId, 10),
weight: result.destination!.index,
...data
};
dispatch(addItem(timeslotId, newItem));
} else {
// this is a normal move (ghosts aren't draggable)
dispatch(

View file

@ -80,10 +80,28 @@ const showplan = createSlice({
item.channel = op.channel;
item.weight = op.weight;
break;
case "AddItem":
// no-op
break;
default:
throw new Error();
}
});
},
insertGhost(state, action: PayloadAction<ItemGhost>) {
state.plan!.push(action.payload);
},
replaceGhost(
state,
action: PayloadAction<{ ghostId: string; newItemData: TimeslotItem }>
) {
const idx = state.plan!.findIndex(
x => itemId(x) === action.payload.ghostId
);
if (idx < 0) {
throw new Error();
}
state.plan![idx] = action.payload.newItemData;
}
}
});
@ -195,7 +213,6 @@ export const moveItem = (
});
// Then, and only then, put the item in its new place
console.log("Moving over");
ops.push({
op: "MoveItem",
timeslotitemid: (itemToMove as TimeslotItem).timeslotitemid,
@ -205,13 +222,6 @@ export const moveItem = (
weight: newWeight
});
console.log(
"TL;DR of opset is\r\n" +
ops
.map(x => `${x.oldchannel}x${x.oldweight} -> ${x.channel}x${x.weight}`)
.join(";\r\n")
);
dispatch(showplan.actions.applyOps(ops));
const result = await api.updateShowplan(timeslotid, ops);
if (!result.every(x => x.status)) {
@ -221,6 +231,78 @@ export const moveItem = (
}
};
export const addItem = (
timeslotId: number,
newItemData: TimeslotItem
): AppThunk => async (dispatch, getState) => {
const plan = cloneDeep(getState().showplan.plan!);
const ops: api.UpdateOp[] = [];
// This is basically a simplified version of the second case above
// Before we add the new item to the plan, we increment everything below it
const planColumn = plan
.filter(x => x.channel === newItemData.channel)
.sort((a, b) => a.weight - b.weight);
for (let i = newItemData.weight; i < planColumn.length; i++) {
const item = planColumn[i];
ops.push({
op: "MoveItem",
timeslotitemid: itemId(item),
oldchannel: item.channel,
oldweight: item.weight,
channel: item.channel,
weight: item.weight + 1
});
item.weight += 1;
}
// Okay, we have a hole.
// Now, we're going to insert a "ghost" item into the plan while we wait for it to save
// Note that we're going to flush the move-over operations to Redux now, so that the hole
// is there - then, once we get a timeslotitemid, replace it with a proper item
dispatch(showplan.actions.applyOps(ops));
const ghostId = Math.random().toString(10);
const newItemTitle =
newItemData.type === "central"
? newItemData.artist + "-" + newItemData.title
: newItemData.title;
const ghost: ItemGhost = {
ghostid: ghostId,
channel: newItemData.channel,
weight: newItemData.weight,
title: newItemTitle
};
const idForServer =
newItemData.type === "central"
? `${newItemData.album.recordid}-${newItemData.trackid}`
: `ManagedDB-${newItemData.auxid}`;
dispatch(showplan.actions.insertGhost(ghost));
ops.push({
op: "AddItem",
channel: newItemData.channel,
weight: newItemData.weight,
id: idForServer
});
const result = await api.updateShowplan(timeslotId, ops);
if (!result.every(x => x.status)) {
dispatch(showplan.actions.planSaveError("Server says no!"));
return;
}
const lastResult = result[result.length - 1]; // this is the add op
const newItemId = lastResult.timeslotitemid!;
newItemData.timeslotitemid = newItemId;
dispatch(
showplan.actions.replaceGhost({
ghostId: "G" + ghostId,
newItemData
})
);
};
export const getShowplan = (timeslotId: number): AppThunk => async dispatch => {
dispatch(showplan.actions.getShowplanStarting());
try {