Refactor action to reuse action on sync

This commit is contained in:
Jeremie Pardou-Piquemal 2020-07-21 22:17:05 +02:00 committed by Jérémie Pardou-Piquemal
parent 2a69a16084
commit 2592b35c76
5 changed files with 139 additions and 140 deletions

View file

@ -22,7 +22,11 @@ describe("Item interactions", () => {
.should("have.css", "top", "400px")
.should("have.css", "left", "420px");
// cy.get(".board")
// Select card
cy.get("img[src='/games/JC.jpg']")
.parents(".item")
.click(500, 500, { force: true });
cy.get("img[src='/games/JC.jpg']")
.parents(".item")
.trigger("mousedown", {

View file

@ -8,7 +8,7 @@ import { insideClass } from "../../utils";
const ActionPane = ({ children }) => {
const panZoomRotate = useRecoilValue(PanZoomRotateAtom);
const { putItemsOnTop, moveSelectedItems } = useItems();
const { putItemsOnTop, moveItems } = useItems();
const [selectedItems, setSelectedItems] = useRecoilState(selectedItemsAtom);
const wrapperRef = React.useRef(null);
const actionRef = React.useRef({});
@ -59,7 +59,7 @@ const ActionPane = ({ children }) => {
const { top, left } = e.currentTarget.getBoundingClientRect();
const currentX = (e.clientX - left) / panZoomRotate.scale;
const currentY = (e.clientY - top) / panZoomRotate.scale;
moveSelectedItems(actionRef.current.itemId, {
moveItems(selectedItems, {
x: currentX - actionRef.current.prevX,
y: currentY - actionRef.current.prevY,
});

View file

@ -1,11 +1,17 @@
import React from "react";
import { useC2C } from "../../../hooks/useC2C";
import { useSetRecoilState } from "recoil";
import { ItemListAtom } from "../";
import useItems from "./useItems";
export const SubcribeItemEvents = () => {
const [c2c] = useC2C();
const setItemList = useSetRecoilState(ItemListAtom);
const {
updateItemOrder,
moveItems,
setItemList,
removeItems,
insertItemBefore,
} = useItems();
React.useEffect(() => {
const unsub = c2c.subscribe(`batchItemsUpdate`, (updatedItems) => {
@ -22,81 +28,42 @@ export const SubcribeItemEvents = () => {
}, [c2c, setItemList]);
React.useEffect(() => {
const unsub = c2c.subscribe(`selectedItemsMove`, ({ itemIds, move }) => {
setItemList((prevList) => {
return prevList.map((item) => {
if (itemIds.includes(item.id)) {
const x = item.x + move.x;
const y = item.y + move.y;
const newItem = { ...item, x, y };
return newItem;
}
return item;
});
});
});
const unsub = c2c.subscribe(
`selectedItemsMove`,
({ itemIds, posDelta }) => {
moveItems(itemIds, posDelta, false);
}
);
return unsub;
}, [c2c, setItemList]);
}, [c2c, moveItems]);
React.useEffect(() => {
const unsub = c2c.subscribe(`updateItemListOrder`, (itemIds) => {
setItemList((prevList) => {
const itemsMap = prevList.reduce((prev, item) => {
prev[item.id] = item;
return prev;
}, {});
const result = prevList.map((item, index) => {
// Fix #114 crash when pushing new item and receive update list order
// If item id doesn't exists in map, we keep the current item
return itemsMap[itemIds[index]] || item;
});
return result;
});
updateItemOrder(itemIds, false);
});
return unsub;
}, [c2c, setItemList]);
}, [c2c, updateItemOrder]);
React.useEffect(() => {
const unsub = c2c.subscribe(`pushItem`, (newItem) => {
setItemList((prevItemList) => [
...prevItemList,
{
...newItem,
},
]);
insertItemBefore(newItem, null, false);
});
return unsub;
}, [c2c, setItemList]);
}, [c2c, insertItemBefore]);
React.useEffect(() => {
const unsub = c2c.subscribe(`insertItemBefore`, ([newItem, beforeId]) => {
setItemList((prevItemList) => {
if (beforeId) {
const insertAt = prevItemList.findIndex(({ id }) => id === beforeId);
const newItemList = [...prevItemList];
newItemList.splice(insertAt, 0, { ...newItem });
return newItemList;
} else {
return [
...prevItemList,
{
...newItem,
},
];
}
});
insertItemBefore(newItem, beforeId, false);
});
return unsub;
}, [c2c, setItemList]);
}, [c2c, insertItemBefore]);
React.useEffect(() => {
const unsub = c2c.subscribe(`removeItem`, (itemId) => {
setItemList((prevItemList) =>
prevItemList.filter((item) => item.id !== itemId)
);
const unsub = c2c.subscribe(`removeItems`, (itemIds) => {
removeItems(itemIds, false);
});
return unsub;
}, [c2c, setItemList]);
}, [c2c, removeItems]);
return null;
};

View file

@ -13,6 +13,8 @@ import { getDefaultActionsFromItem } from "./Item/allItems";
import { useTranslation } from "react-i18next";
import { nanoid } from "nanoid";
import { shuffle as shuffleArray } from "../../../utils";
import deleteIcon from "../../../images/delete.svg";
import stackIcon from "../../../images/stack.svg";
import duplicateIcon from "../../../images/duplicate.svg";
@ -32,10 +34,10 @@ const getActionsFromItem = (item) => {
export const useItemActions = () => {
const {
batchUpdateItems,
removeItem,
removeItems,
insertItemBefore,
reverseItemsOrder,
shuffleSelectedItems,
swapItems,
} = useItems();
const { t } = useTranslation();
@ -118,6 +120,11 @@ export const useItemActions = () => {
[selectedItems, batchUpdateItems]
);
const shuffleSelectedItems = React.useCallback(() => {
const shuffledItems = shuffleArray([...selectedItems]);
swapItems(selectedItems, shuffledItems);
}, [selectedItems, swapItems]);
// Tap/Untap elements
const toggleTap = useRecoilCallback(
async (snapshot) => {
@ -161,7 +168,7 @@ export const useItemActions = () => {
batchUpdateItems(selectedItems, (item) => ({
...item,
flipped: flip,
unflippedFor: undefined,
unflippedFor: [],
}));
reverseItemsOrder(selectedItems);
},
@ -216,13 +223,9 @@ export const useItemActions = () => {
[batchUpdateItems, selectedItems, currentUser.id]
);
// Remove selected items
const removeItems = React.useCallback(
() =>
selectedItems.forEach((id) => {
removeItem(id);
}),
[removeItem, selectedItems]
const removeSelectedItems = React.useCallback(
() => removeItems(selectedItems),
[removeItems, selectedItems]
);
const cloneItem = useRecoilCallback(
@ -306,7 +309,7 @@ export const useItemActions = () => {
icon: lockIcon,
},
remove: {
action: removeItems,
action: removeSelectedItems,
label: t("Remove all"),
shortcut: "r",
edit: true,
@ -324,13 +327,13 @@ export const useItemActions = () => {
shuffleSelectedItems,
cloneItem,
toggleLock,
removeItems,
removeSelectedItems,
]
);
return {
align,
remove: removeItems,
remove: removeSelectedItems,
toggleFlip,
toggleFlipSelf,
toggleLock,

View file

@ -1,7 +1,6 @@
import React from "react";
import { useC2C } from "../../../hooks/useC2C";
import { useSetRecoilState, useRecoilState } from "recoil";
import { shuffle as shuffleArray } from "../../../utils";
import { useSetRecoilState } from "recoil";
import { ItemListAtom, selectedItemsAtom } from "../";
@ -9,7 +8,7 @@ const useItems = () => {
const [c2c] = useC2C();
const setItemList = useSetRecoilState(ItemListAtom);
const [selectedItems, setSelectItems] = useRecoilState(selectedItemsAtom);
const setSelectItems = useSetRecoilState(selectedItemsAtom);
const batchUpdateItems = React.useCallback(
(ids, callbackOrItem, sync = true) => {
@ -46,15 +45,11 @@ const useItems = () => {
[batchUpdateItems]
);
const moveSelectedItems = React.useCallback(
(itemId, posDelta) => {
let ids = [itemId];
if (selectedItems.includes(itemId)) {
ids = selectedItems;
}
const moveItems = React.useCallback(
(itemIds, posDelta, sync = true) => {
setItemList((prevList) => {
const newItemList = prevList.map((item) => {
if (ids.includes(item.id)) {
if (itemIds.includes(item.id)) {
const x = item.x + posDelta.x;
const y = item.y + posDelta.y;
const newItem = { ...item, x, y };
@ -62,14 +57,40 @@ const useItems = () => {
}
return item;
});
c2c.publish(`selectedItemsMove`, {
itemIds: ids,
move: posDelta,
});
if (sync) {
c2c.publish(`selectedItemsMove`, {
itemIds,
posDelta,
});
}
return newItemList;
});
},
[setItemList, selectedItems, c2c]
[setItemList, c2c]
);
const updateItemOrder = React.useCallback(
(newOrder, sync = true) => {
setItemList((prevList) => {
const itemsMap = prevList.reduce((prev, item) => {
prev[item.id] = item;
return prev;
}, {});
const result = prevList.map((item, index) => {
// Fix #114 crash when pushing new item and receive update list order
// If item id doesn't exists in map, we keep the current item
return itemsMap[newOrder[index]] || item;
});
if (sync) {
c2c.publish(
`updateItemListOrder`,
result.map(({ id }) => id)
);
}
return result;
});
},
[c2c, setItemList]
);
const putItemsOnTop = React.useCallback(
@ -114,36 +135,42 @@ const useItems = () => {
[setItemList, c2c]
);
// Shuffle selection
const shuffleSelectedItems = React.useCallback(() => {
setItemList((prevItemList) => {
const shuffledSelectedItems = shuffleArray(
prevItemList.filter(({ id }) => selectedItems.includes(id))
);
const updatedItems = {};
const result = prevItemList.map((item) => {
if (selectedItems.includes(item.id)) {
const replaceBy = shuffledSelectedItems.pop();
const newItem = {
...replaceBy,
x: item.x,
y: item.y,
};
updatedItems[replaceBy.id] = { x: item.x, y: item.y };
return newItem;
}
return item;
const swapItems = React.useCallback(
(fromIds, toIds) => {
setItemList((prevItemList) => {
const swappedItems = toIds.map((toId) =>
prevItemList.find(({ id }) => id === toId)
);
const updatedItems = {};
const result = prevItemList.map((item) => {
if (fromIds.includes(item.id)) {
const replaceBy = swappedItems.shift();
const newItem = {
...replaceBy,
x: item.x,
y: item.y,
};
updatedItems[replaceBy.id] = {
x: item.x,
y: item.y,
};
return newItem;
}
return item;
});
c2c.publish(`batchItemsUpdate`, updatedItems);
c2c.publish(
`updateItemListOrder`,
result.map(({ id }) => id)
);
return result;
});
c2c.publish(`batchItemsUpdate`, updatedItems);
c2c.publish(
`updateItemListOrder`,
result.map(({ id }) => id)
);
return result;
});
}, [c2c, setItemList, selectedItems]);
},
[c2c, setItemList]
);
const insertItemBefore = React.useCallback(
(newItem, beforeId, sync = true) => {
@ -154,7 +181,9 @@ const useItems = () => {
c2c.publish(`insertItemBefore`, [newItem, beforeId]);
}
const newItemList = [...prevItemList];
newItemList.splice(insertAt, 0, { ...newItem });
newItemList.splice(insertAt, 0, {
...newItem,
});
return newItemList;
} else {
if (sync) {
@ -172,38 +201,34 @@ const useItems = () => {
[c2c, setItemList]
);
const pushItem = React.useCallback(
(newItem) => {
insertItemBefore(newItem);
},
[insertItemBefore]
);
const removeItem = React.useCallback(
(itemIdToRemove) => {
if (selectedItems.includes(itemIdToRemove)) {
setSelectItems((prev) => [
...prev.filter((id) => id !== itemIdToRemove),
]);
}
const removeItems = React.useCallback(
(itemsIdToRemove, sync = true) => {
setItemList((prevItemList) => {
c2c.publish(`removeItem`, itemIdToRemove);
return prevItemList.filter((item) => item.id !== itemIdToRemove);
if (sync) {
c2c.publish(`removeItems`, itemsIdToRemove);
}
return prevItemList.filter(
(item) => !itemsIdToRemove.includes(item.id)
);
});
setSelectItems((prevList) => {
return prevList.filter((id) => !itemsIdToRemove.includes(id));
});
},
[c2c, selectedItems, setItemList, setSelectItems]
[c2c, setItemList, setSelectItems]
);
return {
putItemsOnTop,
batchUpdateItems,
moveSelectedItems,
updateItemOrder,
moveItems,
updateItem,
shuffleSelectedItems,
swapItems,
reverseItemsOrder,
setItemList,
pushItem,
removeItem,
pushItem: insertItemBefore,
removeItems,
insertItemBefore,
};
};