Refactor action to reuse action on sync
This commit is contained in:
parent
2a69a16084
commit
2592b35c76
5 changed files with 139 additions and 140 deletions
|
@ -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", {
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue