diff --git a/src/gameComponents/Image/Image.jsx b/src/gameComponents/Image/Image.jsx index 98f42ee5..cf4d7961 100644 --- a/src/gameComponents/Image/Image.jsx +++ b/src/gameComponents/Image/Image.jsx @@ -67,7 +67,8 @@ const Image = ({ id: currentItemId, }) => { const { currentUser, localUsers: users } = useUsers(); - const { register } = useItemInteraction("place"); + const { register: registerPlace } = useItemInteraction("place"); + const { register: registerDelete } = useItemInteraction("delete"); const { getItemList } = useItemActions(); const wrapperRef = React.useRef(null); @@ -131,7 +132,6 @@ const Image = ({ itemIds, shouldHoldItems: item.holdItems, }); - if (item.linkedItems !== newLinkedItems) { return { linkedItems: newLinkedItems, @@ -142,16 +142,45 @@ const Image = ({ [currentItemId, getItemList, setState] ); + const onDeleteItem = React.useCallback( + (itemIds) => { + setState((item) => { + const safeLinkedItems = item.linkedItems || []; + const newLinkedItems = safeLinkedItems.filter( + (id) => !itemIds.includes(id) + ); + + if (safeLinkedItems.length !== newLinkedItems.length) { + return { + linkedItems: newLinkedItems, + }; + } + }, true); + }, + [setState] + ); + + React.useEffect(() => { + const unregisterList = []; + if (currentItemId) { + unregisterList.push(registerPlace(onPlaceItem)); + } + + return () => { + unregisterList.forEach((callback) => callback()); + }; + }, [currentItemId, onPlaceItem, registerPlace]); + React.useEffect(() => { const unregisterList = []; if (currentItemId) { - unregisterList.push(register(onPlaceItem)); + unregisterList.push(registerDelete(onDeleteItem)); } return () => { unregisterList.forEach((callback) => callback()); }; - }, [currentItemId, onPlaceItem, register]); + }, [currentItemId, onDeleteItem, registerDelete]); return ( <Wrapper ref={wrapperRef}> diff --git a/src/gameComponents/useGameItemActions.jsx b/src/gameComponents/useGameItemActions.jsx index ea37b901..17eeb2aa 100644 --- a/src/gameComponents/useGameItemActions.jsx +++ b/src/gameComponents/useGameItemActions.jsx @@ -2,7 +2,12 @@ import React from "react"; import { useTranslation } from "react-i18next"; import { toast } from "react-toastify"; -import { useItemActions, useUsers, useSelectedItems } from "react-sync-board"; +import { + useItemActions, + useUsers, + useSelectedItems, + useItemInteraction, +} from "react-sync-board"; import { shuffle as shuffleArray, @@ -53,6 +58,7 @@ export const useGameItemActions = () => { swapItems, getItems, } = useItemActions(); + const { call: callPlaceInteractions } = useItemInteraction("place"); const { t } = useTranslation(); @@ -125,7 +131,7 @@ export const useGameItemActions = () => { batchUpdateItems( ids, - (item) => { + () => { const newItem = { x: newX, y: newY, @@ -136,8 +142,9 @@ export const useGameItemActions = () => { }, true ); + callPlaceInteractions(ids); }, - [batchUpdateItems, getItemListOrSelected] + [batchUpdateItems, getItemListOrSelected, callPlaceInteractions] ); // Stack selection to Top Left @@ -162,7 +169,7 @@ export const useGameItemActions = () => { batchUpdateItems( ids, - (item) => { + () => { const newItem = { x: newX, y: newY, @@ -173,8 +180,9 @@ export const useGameItemActions = () => { }, true ); + callPlaceInteractions(ids); }, - [batchUpdateItems, getItemListOrSelected] + [batchUpdateItems, getItemListOrSelected, callPlaceInteractions] ); // Align selection to a line @@ -198,8 +206,9 @@ export const useGameItemActions = () => { }, true ); + callPlaceInteractions(ids); }, - [getItemListOrSelected, batchUpdateItems] + [getItemListOrSelected, batchUpdateItems, callPlaceInteractions] ); // Align selection to an array @@ -235,8 +244,9 @@ export const useGameItemActions = () => { }, true ); + callPlaceInteractions(ids); }, - [getItemListOrSelected, batchUpdateItems] + [getItemListOrSelected, batchUpdateItems, callPlaceInteractions] ); const snapToPoint = React.useCallback( @@ -256,8 +266,9 @@ export const useGameItemActions = () => { }, true ); + callPlaceInteractions(itemIds); }, - [batchUpdateItems] + [batchUpdateItems, callPlaceInteractions] ); const roll = React.useCallback( @@ -386,8 +397,10 @@ export const useGameItemActions = () => { swapItems(ids, shuffledItems); playAudio(shuffleAudio, 0.5); + + callPlaceInteractions(ids); }, - [getItemListOrSelected, swapItems] + [getItemListOrSelected, callPlaceInteractions, swapItems] ); const randomlyRotateSelectedItems = React.useCallback( @@ -492,12 +505,13 @@ export const useGameItemActions = () => { ); if (reverseOrder) { reverseItemsOrder(itemIdsToFlip); + callPlaceInteractions(itemIds); } if (itemIdsToFlip.length) { playAudio(flipAudio, 0.2); } }, - [batchUpdateItems, getItems, reverseItemsOrder] + [batchUpdateItems, callPlaceInteractions, getItems, reverseItemsOrder] ); // Toggle flip state diff --git a/src/utils/item.js b/src/utils/item.js index a40aec0c..aa07176e 100644 --- a/src/utils/item.js +++ b/src/utils/item.js @@ -117,35 +117,49 @@ export const getHeldItems = ({ itemIds, shouldHoldItems, }) => { - const safeCurrentLinkedItems = currentLinkedItemIds || []; + const safeCurrentLinkedItems = Array.isArray(currentLinkedItemIds) + ? currentLinkedItemIds + : []; + if (shouldHoldItems) { - let before = true; - const afterItemIds = itemList - .filter(({ id }) => { - const result = !before && itemIds.includes(id); - if (id === currentItemId) { - before = false; - } - return result; + const currentItemIndex = itemList.findIndex( + ({ id }) => id === currentItemId + ); + const currentItemLayer = itemList[currentItemIndex].layer || 0; + + const afterMap = Object.fromEntries( + itemList.map(({ id, layer = 0 }, index) => { + return [ + id, + layer > currentItemLayer || + (layer === currentItemLayer && index > currentItemIndex), + ]; }) - .map(({ id }) => id); + ); + const afterItemIds = itemIds.filter((id) => afterMap[id]); + + const afterCurrentLinkedItemIds = (currentLinkedItemIds || []).filter( + (id) => afterMap[id] + ); + const newHeldItems = Object.entries( - areItemsInside(element, afterItemIds, safeCurrentLinkedItems, true) + areItemsInside(element, afterItemIds, afterCurrentLinkedItemIds, true) ) .filter(([, { inside }]) => inside) .map(([itemId]) => itemId); + + const oldLinked = new Set(safeCurrentLinkedItems); + const newLinked = new Set(newHeldItems); + if ( - safeCurrentLinkedItems.length !== newHeldItems.length || - !safeCurrentLinkedItems.every((itemId) => newHeldItems.includes(itemId)) + newLinked.size !== oldLinked.size || + !Array.from(oldLinked).every((id) => newLinked.has(id)) ) { return newHeldItems; } } else { - if ( - !Array.isArray(safeCurrentLinkedItems) || - safeCurrentLinkedItems.length !== 0 - ) { - return []; + if (safeCurrentLinkedItems.length !== 0) { + return safeCurrentLinkedItems; } }