Split item state from order to minimize render
This commit is contained in:
parent
93e681e32e
commit
25b75645f3
14 changed files with 246 additions and 203 deletions
|
@ -1,40 +1,49 @@
|
|||
import React from "react";
|
||||
import { useRecoilValue } from "recoil";
|
||||
import { useRecoilCallback } from "recoil";
|
||||
|
||||
import useGameStorage from "./Board/game/useGameStorage";
|
||||
|
||||
import { AvailableItemListAtom, BoardConfigAtom, ItemListAtom } from "./Board/";
|
||||
|
||||
import throttle from "lodash.throttle";
|
||||
import {
|
||||
AvailableItemListAtom,
|
||||
BoardConfigAtom,
|
||||
AllItemsSelector,
|
||||
} from "./Board/";
|
||||
|
||||
export const AutoSave = () => {
|
||||
const availableItemList = useRecoilValue(AvailableItemListAtom);
|
||||
const boardConfig = useRecoilValue(BoardConfigAtom);
|
||||
const itemList = useRecoilValue(ItemListAtom);
|
||||
|
||||
const [, setGameLocalSave] = useGameStorage();
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const updateAutoSave = React.useCallback(
|
||||
throttle(
|
||||
(game) => {
|
||||
if (game.items.length) {
|
||||
setGameLocalSave(game);
|
||||
}
|
||||
},
|
||||
5000,
|
||||
{ trailing: true }
|
||||
),
|
||||
[]
|
||||
const updateAutoSave = useRecoilCallback(
|
||||
({ snapshot }) => async () => {
|
||||
const availableItemList = await snapshot.getPromise(
|
||||
AvailableItemListAtom
|
||||
);
|
||||
const boardConfig = await snapshot.getPromise(BoardConfigAtom);
|
||||
const itemList = await snapshot.getPromise(AllItemsSelector);
|
||||
const game = {
|
||||
items: itemList,
|
||||
board: boardConfig,
|
||||
availableItems: availableItemList,
|
||||
};
|
||||
if (game.items.length) {
|
||||
setGameLocalSave(game);
|
||||
}
|
||||
},
|
||||
[setGameLocalSave]
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
updateAutoSave({
|
||||
items: itemList,
|
||||
board: boardConfig,
|
||||
availableItems: availableItemList,
|
||||
});
|
||||
}, [itemList, boardConfig, availableItemList, updateAutoSave]);
|
||||
let mounted = true;
|
||||
|
||||
const cancel = setInterval(() => {
|
||||
if (!mounted) return;
|
||||
updateAutoSave();
|
||||
}, 5000);
|
||||
|
||||
return () => {
|
||||
mounted = false;
|
||||
clearInterval(cancel);
|
||||
};
|
||||
}, [updateAutoSave]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React, { memo } from "react";
|
||||
import { useRecoilValue } from "recoil";
|
||||
import { selectedItemsAtom } from "../../Selector";
|
||||
import { ItemsFamily } from "../../";
|
||||
import debounce from "lodash.debounce";
|
||||
|
||||
import styled, { css } from "styled-components";
|
||||
|
@ -67,7 +68,11 @@ const ItemWrapper = styled.div.attrs(({ rotation, loaded, locked }) => {
|
|||
`}
|
||||
`;
|
||||
|
||||
const Item = ({ setState, state, isSelected }) => {
|
||||
const Item = ({
|
||||
setState,
|
||||
state: { type, x, y, rotation = 0, id, locked, layer, ...rest },
|
||||
isSelected,
|
||||
}) => {
|
||||
const itemRef = React.useRef(null);
|
||||
const sizeRef = React.useRef({});
|
||||
const [unlock, setUnlock] = React.useState(false);
|
||||
|
@ -94,13 +99,11 @@ const Item = ({ setState, state, isSelected }) => {
|
|||
};
|
||||
}, []);
|
||||
|
||||
const Component = getComponent(state.type);
|
||||
|
||||
const rotation = state.rotation || 0;
|
||||
const Component = getComponent(type);
|
||||
|
||||
const updateState = React.useCallback(
|
||||
(callbackOrItem, sync = true) => setState(state.id, callbackOrItem, sync),
|
||||
[setState, state.id]
|
||||
(callbackOrItem, sync = true) => setState(id, callbackOrItem, sync),
|
||||
[setState, id]
|
||||
);
|
||||
|
||||
// Update actual dimension. Usefull when image with own dimensions.
|
||||
|
@ -154,7 +157,7 @@ const Item = ({ setState, state, isSelected }) => {
|
|||
return (
|
||||
<div
|
||||
style={{
|
||||
transform: `translate(${state.x}px, ${state.y}px)`,
|
||||
transform: `translate(${x}px, ${y}px)`,
|
||||
display: "inline-block",
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
|
@ -163,14 +166,14 @@ const Item = ({ setState, state, isSelected }) => {
|
|||
>
|
||||
<ItemWrapper
|
||||
rotation={rotation}
|
||||
locked={state.locked && !unlock}
|
||||
locked={locked && !unlock}
|
||||
selected={isSelected}
|
||||
ref={itemRef}
|
||||
layer={state.layer}
|
||||
layer={layer}
|
||||
loaded={loaded}
|
||||
id={state.id}
|
||||
id={id}
|
||||
>
|
||||
<Component {...state} x={0} y={0} setState={updateState} />
|
||||
<Component {...rest} x={0} y={0} setState={updateState} />
|
||||
</ItemWrapper>
|
||||
</div>
|
||||
);
|
||||
|
@ -192,9 +195,10 @@ const MemoizedItem = memo(
|
|||
|
||||
const BaseItem = ({ setState, state }) => {
|
||||
const selectedItems = useRecoilValue(selectedItemsAtom);
|
||||
const realState = useRecoilValue(ItemsFamily(state.id));
|
||||
return (
|
||||
<MemoizedItem
|
||||
state={state}
|
||||
state={realState}
|
||||
setState={setState}
|
||||
isSelected={selectedItems.includes(state.id)}
|
||||
/>
|
||||
|
|
|
@ -7,7 +7,7 @@ import { useRecoilValue } from "recoil";
|
|||
import { Form, Field } from "react-final-form";
|
||||
import AutoSave from "../../Form/AutoSave";
|
||||
|
||||
import { ItemListAtom } from "../../";
|
||||
import { ItemsFamily } from "../../";
|
||||
|
||||
import Label from "../../Form/Label";
|
||||
|
||||
|
@ -18,9 +18,8 @@ import "rc-slider/assets/index.css";
|
|||
|
||||
const ItemFormFactory = ({ itemId, onSubmitHandler }) => {
|
||||
const { t } = useTranslation();
|
||||
const itemList = useRecoilValue(ItemListAtom);
|
||||
|
||||
const item = itemList.find(({ id }) => id === itemId);
|
||||
const item = useRecoilValue(ItemsFamily(itemId));
|
||||
if (!item) return null;
|
||||
|
||||
const FieldsComponent = getFormFieldComponent(item.type);
|
||||
|
|
|
@ -9,7 +9,7 @@ const ItemList = () => {
|
|||
const itemList = useRecoilValue(ItemListAtom);
|
||||
|
||||
return itemList.map((item) => (
|
||||
<Item key={item.id} state={item} setState={updateItem} />
|
||||
<Item key={item.id} state={{ id: item.id }} setState={updateItem} />
|
||||
));
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import React from "react";
|
||||
import { useC2C } from "../../../hooks/useC2C";
|
||||
import useItems from "./useItems";
|
||||
import { useRecoilCallback } from "recoil";
|
||||
|
||||
import { ItemsFamily } from "../";
|
||||
|
||||
export const SubcribeItemEvents = () => {
|
||||
const [c2c] = useC2C();
|
||||
|
@ -8,24 +11,25 @@ export const SubcribeItemEvents = () => {
|
|||
const {
|
||||
updateItemOrder,
|
||||
moveItems,
|
||||
setItemList,
|
||||
removeItems,
|
||||
insertItemBefore,
|
||||
} = useItems();
|
||||
|
||||
const batchUpdate = useRecoilCallback(
|
||||
({ set }) => (updatedItems) => {
|
||||
for (const [id, newItem] of Object.entries(updatedItems)) {
|
||||
set(ItemsFamily(id), (item) => ({ ...item, ...newItem }));
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
const unsub = c2c.subscribe(`batchItemsUpdate`, (updatedItems) => {
|
||||
setItemList((prevList) => {
|
||||
return prevList.map((item) => {
|
||||
if (item.id in updatedItems) {
|
||||
return { ...item, ...updatedItems[item.id] };
|
||||
}
|
||||
return item;
|
||||
});
|
||||
});
|
||||
batchUpdate(updatedItems);
|
||||
});
|
||||
return unsub;
|
||||
}, [c2c, setItemList]);
|
||||
}, [c2c, batchUpdate]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const unsub = c2c.subscribe(
|
||||
|
|
|
@ -7,7 +7,7 @@ import { selectedItemsAtom } from "../Selector";
|
|||
import { useUsers } from "../../users";
|
||||
|
||||
import intersection from "lodash.intersection";
|
||||
import { ItemListAtom } from "../";
|
||||
import { ItemsFamily } from "../";
|
||||
import { getDefaultActionsFromItem } from "./Item/allItems";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
@ -48,10 +48,11 @@ export const useItemActions = () => {
|
|||
const [availableActions, setAvailableActions] = React.useState([]);
|
||||
const isMountedRef = React.useRef(false);
|
||||
|
||||
const getSelectedItemList = React.useCallback(
|
||||
async (snapshot) => {
|
||||
const itemList = await snapshot.getPromise(ItemListAtom);
|
||||
return itemList.filter(({ id }) => selectedItems.includes(id));
|
||||
const getSelectedItemList = useRecoilCallback(
|
||||
({ snapshot }) => async () => {
|
||||
return await Promise.all(
|
||||
selectedItems.map((id) => snapshot.getPromise(ItemsFamily(id)))
|
||||
);
|
||||
},
|
||||
[selectedItems]
|
||||
);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import React from "react";
|
||||
import { useC2C } from "../../../hooks/useC2C";
|
||||
import { useSetRecoilState } from "recoil";
|
||||
import { useSetRecoilState, useRecoilCallback } from "recoil";
|
||||
|
||||
import { ItemListAtom, selectedItemsAtom } from "../";
|
||||
import { ItemListAtom, selectedItemsAtom, ItemsFamily } from "../";
|
||||
|
||||
const useItems = () => {
|
||||
const [c2c] = useC2C();
|
||||
|
@ -10,32 +10,42 @@ const useItems = () => {
|
|||
const setItemList = useSetRecoilState(ItemListAtom);
|
||||
const setSelectItems = useSetRecoilState(selectedItemsAtom);
|
||||
|
||||
const batchUpdateItems = React.useCallback(
|
||||
(ids, callbackOrItem, sync = true) => {
|
||||
const batchUpdateItems = useRecoilCallback(
|
||||
({ set }) => (itemIds, callbackOrItem, sync = true) => {
|
||||
let callback = callbackOrItem;
|
||||
if (typeof callbackOrItem === "object") {
|
||||
callback = () => callbackOrItem;
|
||||
}
|
||||
setItemList((prevList) => {
|
||||
const updatedItems = {};
|
||||
const updatedList = prevList.map((item) => {
|
||||
if (ids.includes(item.id)) {
|
||||
const newItem = {
|
||||
...callback(item),
|
||||
id: item.id,
|
||||
};
|
||||
updatedItems[newItem.id] = newItem;
|
||||
return newItem;
|
||||
}
|
||||
return item;
|
||||
const updatedItems = {};
|
||||
itemIds.forEach((id) => {
|
||||
set(ItemsFamily(id), (item) => {
|
||||
const newItem = {
|
||||
...callback(item),
|
||||
id: item.id,
|
||||
};
|
||||
updatedItems[item.id] = newItem;
|
||||
return newItem;
|
||||
});
|
||||
if (sync) {
|
||||
c2c.publish(`batchItemsUpdate`, updatedItems);
|
||||
}
|
||||
return updatedList;
|
||||
});
|
||||
if (sync) {
|
||||
c2c.publish(`batchItemsUpdate`, updatedItems);
|
||||
}
|
||||
},
|
||||
[c2c]
|
||||
);
|
||||
|
||||
const setItemListFull = useRecoilCallback(
|
||||
({ set }) => (items) => {
|
||||
setItemList(
|
||||
items.map(({ id }) => ({
|
||||
id,
|
||||
}))
|
||||
);
|
||||
items.forEach((item) => {
|
||||
set(ItemsFamily(item.id), item);
|
||||
});
|
||||
},
|
||||
[c2c, setItemList]
|
||||
[setItemList]
|
||||
);
|
||||
|
||||
const updateItem = React.useCallback(
|
||||
|
@ -45,28 +55,23 @@ const useItems = () => {
|
|||
[batchUpdateItems]
|
||||
);
|
||||
|
||||
const moveItems = React.useCallback(
|
||||
(itemIds, posDelta, sync = true) => {
|
||||
setItemList((prevList) => {
|
||||
const newItemList = prevList.map((item) => {
|
||||
if (itemIds.includes(item.id)) {
|
||||
const x = item.x + posDelta.x;
|
||||
const y = item.y + posDelta.y;
|
||||
const newItem = { ...item, x, y };
|
||||
return newItem;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
if (sync) {
|
||||
c2c.publish(`selectedItemsMove`, {
|
||||
itemIds,
|
||||
posDelta,
|
||||
});
|
||||
}
|
||||
return newItemList;
|
||||
const moveItems = useRecoilCallback(
|
||||
({ set }) => async (itemIds, posDelta, sync = true) => {
|
||||
itemIds.forEach((id) => {
|
||||
set(ItemsFamily(id), (item) => ({
|
||||
...item,
|
||||
x: item.x + posDelta.x,
|
||||
y: item.y + posDelta.y,
|
||||
}));
|
||||
});
|
||||
if (sync) {
|
||||
c2c.publish(`selectedItemsMove`, {
|
||||
itemIds,
|
||||
posDelta,
|
||||
});
|
||||
}
|
||||
},
|
||||
[setItemList, c2c]
|
||||
[c2c]
|
||||
);
|
||||
|
||||
const updateItemOrder = React.useCallback(
|
||||
|
@ -135,80 +140,97 @@ const useItems = () => {
|
|||
[setItemList, c2c]
|
||||
);
|
||||
|
||||
const swapItems = React.useCallback(
|
||||
(fromIds, toIds) => {
|
||||
setItemList((prevItemList) => {
|
||||
const swappedItems = toIds.map((toId) =>
|
||||
prevItemList.find(({ id }) => id === toId)
|
||||
);
|
||||
const swapItems = useRecoilCallback(
|
||||
({ snapshot, set }) => async (fromIds, toIds) => {
|
||||
const fromItems = await Promise.all(
|
||||
fromIds.map((id) => snapshot.getPromise(ItemsFamily(id)))
|
||||
);
|
||||
const toItems = await Promise.all(
|
||||
toIds.map((id) => snapshot.getPromise(ItemsFamily(id)))
|
||||
);
|
||||
|
||||
const updatedItems = {};
|
||||
const replaceMapItems = toIds.reduce((theMap, id) => {
|
||||
theMap[id] = fromItems.shift();
|
||||
return theMap;
|
||||
}, {});
|
||||
|
||||
const updatedItems = toItems.reduce((prev, toItem) => {
|
||||
const replaceBy = replaceMapItems[toItem.id];
|
||||
const newItem = {
|
||||
...toItem,
|
||||
x: replaceBy.x,
|
||||
y: replaceBy.y,
|
||||
};
|
||||
set(ItemsFamily(toItem.id), newItem);
|
||||
prev[toItem.id] = newItem;
|
||||
return prev;
|
||||
}, {});
|
||||
|
||||
c2c.publish(`batchItemsUpdate`, updatedItems);
|
||||
|
||||
const replaceMap = fromIds.reduce((theMap, id) => {
|
||||
theMap[id] = toIds.shift();
|
||||
return theMap;
|
||||
}, {});
|
||||
|
||||
setItemList((prevItemList) => {
|
||||
const result = prevItemList.map((item) => {
|
||||
if (fromIds.includes(item.id)) {
|
||||
const replaceBy = swappedItems.shift();
|
||||
const newItem = {
|
||||
...replaceBy,
|
||||
x: item.x,
|
||||
y: item.y,
|
||||
return {
|
||||
id: replaceMap[item.id],
|
||||
};
|
||||
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, setItemList]
|
||||
}
|
||||
);
|
||||
|
||||
const insertItemBefore = React.useCallback(
|
||||
(newItem, beforeId, sync = true) => {
|
||||
const insertItemBefore = useRecoilCallback(
|
||||
({ set }) => (newItem, beforeId, sync = true) => {
|
||||
set(ItemsFamily(newItem.id), newItem);
|
||||
setItemList((prevItemList) => {
|
||||
if (sync) {
|
||||
c2c.publish(`insertItemBefore`, [newItem, beforeId]);
|
||||
}
|
||||
if (beforeId) {
|
||||
const insertAt = prevItemList.findIndex(({ id }) => id === beforeId);
|
||||
|
||||
const newItemList = [...prevItemList];
|
||||
newItemList.splice(insertAt, 0, {
|
||||
...newItem,
|
||||
id: newItem.id,
|
||||
});
|
||||
return newItemList;
|
||||
} else {
|
||||
return [
|
||||
...prevItemList,
|
||||
{
|
||||
...newItem,
|
||||
id: newItem.id,
|
||||
},
|
||||
];
|
||||
}
|
||||
});
|
||||
if (sync) {
|
||||
c2c.publish(`insertItemBefore`, [newItem, beforeId]);
|
||||
}
|
||||
},
|
||||
[c2c, setItemList]
|
||||
);
|
||||
|
||||
const removeItems = React.useCallback(
|
||||
(itemsIdToRemove, sync = true) => {
|
||||
const removeItems = useRecoilCallback(
|
||||
({ set }) => (itemsIdToRemove, sync = true) => {
|
||||
setItemList((prevItemList) => {
|
||||
if (sync) {
|
||||
c2c.publish(`removeItems`, itemsIdToRemove);
|
||||
}
|
||||
return prevItemList.filter(
|
||||
(item) => !itemsIdToRemove.includes(item.id)
|
||||
);
|
||||
});
|
||||
itemsIdToRemove.forEach((id) => set(ItemsFamily(id), undefined));
|
||||
if (sync) {
|
||||
c2c.publish(`removeItems`, itemsIdToRemove);
|
||||
}
|
||||
setSelectItems((prevList) => {
|
||||
return prevList.filter((id) => !itemsIdToRemove.includes(id));
|
||||
});
|
||||
|
@ -224,7 +246,7 @@ const useItems = () => {
|
|||
updateItem,
|
||||
swapItems,
|
||||
reverseItemsOrder,
|
||||
setItemList,
|
||||
setItemList: setItemListFull,
|
||||
pushItem: insertItemBefore,
|
||||
removeItems,
|
||||
insertItemBefore,
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
} from "recoil";
|
||||
import styled from "styled-components";
|
||||
|
||||
import { PanZoomRotateAtom, ItemListAtom, BoardConfigAtom } from "./";
|
||||
import { PanZoomRotateAtom, BoardConfigAtom, AllItemsSelector } from "./";
|
||||
import { insideClass, isPointInsideRect } from "../../utils";
|
||||
|
||||
export const selectedItemsAtom = atom({
|
||||
|
@ -86,7 +86,7 @@ const Selector = ({ children }) => {
|
|||
const throttledSetSelected = useRecoilCallback(
|
||||
({ snapshot }) => async (selector) => {
|
||||
if (stateRef.current.moving) {
|
||||
const itemList = await snapshot.getPromise(ItemListAtom);
|
||||
const itemList = await snapshot.getPromise(AllItemsSelector);
|
||||
|
||||
const selected = findSelected(itemList, selector).map(({ id }) => id);
|
||||
setSelected((prevSelected) => {
|
||||
|
@ -141,7 +141,7 @@ const Selector = ({ children }) => {
|
|||
const onMouseUp = useRecoilCallback(
|
||||
({ snapshot }) => async () => {
|
||||
if (stateRef.current.moving) {
|
||||
const itemList = await snapshot.getPromise(ItemListAtom);
|
||||
const itemList = await snapshot.getPromise(AllItemsSelector);
|
||||
const selected = findSelected(itemList, stateRef.current).map(
|
||||
({ id }) => id
|
||||
);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { atom } from "recoil";
|
||||
import { atom, atomFamily, selector } from "recoil";
|
||||
|
||||
export const AvailableItemListAtom = atom({
|
||||
key: "availableItemList",
|
||||
|
@ -15,4 +15,20 @@ export const ItemListAtom = atom({
|
|||
default: [],
|
||||
});
|
||||
|
||||
export default { ItemListAtom, BoardConfigAtom, AvailableItemListAtom };
|
||||
export const ItemsFamily = atomFamily({
|
||||
key: "Items",
|
||||
default: () => {},
|
||||
});
|
||||
|
||||
export const AllItemsSelector = selector({
|
||||
key: "AllItemsSelector",
|
||||
get: ({ get }) => get(ItemListAtom).map(({ id }) => get(ItemsFamily(id))),
|
||||
});
|
||||
|
||||
export default {
|
||||
ItemListAtom,
|
||||
BoardConfigAtom,
|
||||
AvailableItemListAtom,
|
||||
ItemsFamily,
|
||||
AllItemsSelector,
|
||||
};
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
import { useRecoilValue } from "recoil";
|
||||
|
||||
import { AvailableItemListAtom, BoardConfigAtom, ItemListAtom } from "./atoms";
|
||||
import {
|
||||
AvailableItemListAtom,
|
||||
BoardConfigAtom,
|
||||
AllItemsSelector,
|
||||
} from "./atoms";
|
||||
|
||||
import useLocalStorage from "../../../hooks/useLocalStorage";
|
||||
//import useLocalStorage from 'react-use-localstorage';
|
||||
|
@ -8,7 +12,7 @@ import useLocalStorage from "../../../hooks/useLocalStorage";
|
|||
export const useGameStorage = () => {
|
||||
const availableItemList = useRecoilValue(AvailableItemListAtom);
|
||||
const boardConfig = useRecoilValue(BoardConfigAtom);
|
||||
const itemList = useRecoilValue(ItemListAtom);
|
||||
const itemList = useRecoilValue(AllItemsSelector);
|
||||
|
||||
const [gameLocalSave, setGameLocalSave] = useLocalStorage("savedGame", {
|
||||
items: itemList,
|
||||
|
|
|
@ -5,4 +5,6 @@ export {
|
|||
AvailableItemListAtom,
|
||||
BoardConfigAtom,
|
||||
ItemListAtom,
|
||||
ItemsFamily,
|
||||
AllItemsSelector,
|
||||
} from "./game/atoms";
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import React from "react";
|
||||
import { useRecoilValue } from "recoil";
|
||||
import { useRecoilCallback } from "recoil";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import useGameStorage from "./Board/game/useGameStorage";
|
||||
|
||||
import { AvailableItemListAtom, BoardConfigAtom, ItemListAtom } from "./Board/";
|
||||
|
||||
import throttle from "lodash.throttle";
|
||||
import {
|
||||
AvailableItemListAtom,
|
||||
BoardConfigAtom,
|
||||
AllItemsSelector,
|
||||
} from "./Board/";
|
||||
|
||||
const generateDownloadURI = (data) => {
|
||||
return (
|
||||
|
@ -15,10 +17,6 @@ const generateDownloadURI = (data) => {
|
|||
};
|
||||
|
||||
export const DownloadGameLink = () => {
|
||||
const availableItemList = useRecoilValue(AvailableItemListAtom);
|
||||
const boardConfig = useRecoilValue(BoardConfigAtom);
|
||||
const itemList = useRecoilValue(ItemListAtom);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [downloadURI, setDownloadURI] = React.useState({});
|
||||
|
@ -26,29 +24,40 @@ export const DownloadGameLink = () => {
|
|||
|
||||
const [, setGameLocalSave] = useGameStorage();
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const updateSaveLink = React.useCallback(
|
||||
throttle(
|
||||
(game) => {
|
||||
if (game.items.length) {
|
||||
setDownloadURI(generateDownloadURI(game));
|
||||
setDate(Date.now());
|
||||
setGameLocalSave(game);
|
||||
}
|
||||
},
|
||||
5000,
|
||||
{ trailing: true }
|
||||
),
|
||||
[]
|
||||
const updateSaveLink = useRecoilCallback(
|
||||
({ snapshot }) => async () => {
|
||||
const availableItemList = await snapshot.getPromise(
|
||||
AvailableItemListAtom
|
||||
);
|
||||
const boardConfig = await snapshot.getPromise(BoardConfigAtom);
|
||||
const itemList = await snapshot.getPromise(AllItemsSelector);
|
||||
const game = {
|
||||
items: itemList,
|
||||
board: boardConfig,
|
||||
availableItems: availableItemList,
|
||||
};
|
||||
if (game.items.length) {
|
||||
setDownloadURI(generateDownloadURI(game));
|
||||
setDate(Date.now());
|
||||
setGameLocalSave(game);
|
||||
}
|
||||
},
|
||||
[setGameLocalSave]
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
updateSaveLink({
|
||||
items: itemList,
|
||||
board: boardConfig,
|
||||
availableItems: availableItemList,
|
||||
});
|
||||
}, [itemList, boardConfig, availableItemList, updateSaveLink]);
|
||||
let mounted = true;
|
||||
|
||||
const cancel = setInterval(() => {
|
||||
if (!mounted) return;
|
||||
updateSaveLink();
|
||||
}, 5000);
|
||||
|
||||
return () => {
|
||||
mounted = false;
|
||||
clearInterval(cancel);
|
||||
};
|
||||
}, [updateSaveLink]);
|
||||
|
||||
return (
|
||||
<a
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import React from "react";
|
||||
import { useRecoilState, useRecoilValue, useRecoilCallback } from "recoil";
|
||||
import { useRecoilState, useRecoilValue } from "recoil";
|
||||
|
||||
import { useC2C } from "../hooks/useC2C";
|
||||
|
||||
import { useItems } from "../components/Board/Items";
|
||||
import { AvailableItemListAtom, ItemListAtom } from "./Board";
|
||||
import { AvailableItemListAtom, AllItemsSelector } from "./Board";
|
||||
import useBoardConfig from "./useBoardConfig";
|
||||
|
||||
import { nanoid } from "nanoid";
|
||||
|
@ -17,7 +17,7 @@ const fetchGame = async (url) => {
|
|||
export const SubscribeGameEvents = () => {
|
||||
const [c2c, joined, isMaster] = useC2C();
|
||||
const { setItemList } = useItems();
|
||||
const itemList = useRecoilValue(ItemListAtom);
|
||||
const itemList = useRecoilValue(AllItemsSelector);
|
||||
const [availableItemList, setAvailableItemList] = useRecoilState(
|
||||
AvailableItemListAtom
|
||||
);
|
||||
|
@ -54,8 +54,8 @@ export const SubscribeGameEvents = () => {
|
|||
};
|
||||
}, [c2c, isMaster, joined]);
|
||||
|
||||
/*const loadGame = useRecoilCallback(
|
||||
async (snapshot, game) => {
|
||||
const loadGame = React.useCallback(
|
||||
(game) => {
|
||||
if (game.board.url) {
|
||||
fetchGame(game.board.url).then((result) => {
|
||||
setAvailableItemList(
|
||||
|
@ -68,31 +68,17 @@ export const SubscribeGameEvents = () => {
|
|||
);
|
||||
}
|
||||
setItemList(game.items);
|
||||
game.items.forEach((item)=>{
|
||||
const setItemPosition = await snapshot.getPromise();
|
||||
});
|
||||
|
||||
setBoardConfig(game.board);
|
||||
},
|
||||
[setAvailableItemList, setBoardConfig, setItemList]
|
||||
);*/
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
const unsub = [];
|
||||
unsub.push(
|
||||
c2c.subscribe("loadGame", (game) => {
|
||||
if (game.board.url) {
|
||||
fetchGame(game.board.url).then((result) => {
|
||||
setAvailableItemList(
|
||||
result.availableItems.map((item) => ({ id: nanoid(), ...item }))
|
||||
);
|
||||
});
|
||||
} else {
|
||||
setAvailableItemList(
|
||||
game.availableItems.map((item) => ({ id: nanoid(), ...item }))
|
||||
);
|
||||
}
|
||||
setItemList(game.items);
|
||||
setBoardConfig(game.board);
|
||||
loadGame(game);
|
||||
})
|
||||
);
|
||||
unsub.push(
|
||||
|
@ -103,7 +89,7 @@ export const SubscribeGameEvents = () => {
|
|||
return () => {
|
||||
unsub.forEach((u) => u());
|
||||
};
|
||||
}, [c2c, setAvailableItemList, setItemList, setBoardConfig]);
|
||||
}, [c2c, setBoardConfig, loadGame]);
|
||||
|
||||
// Load game from master if any
|
||||
React.useEffect(() => {
|
||||
|
|
|
@ -47,16 +47,3 @@ export const shuffle = (a) => {
|
|||
}
|
||||
return a;
|
||||
};
|
||||
|
||||
export const shuffleSelectedItems = (itemList, selectedItemIds) => {
|
||||
const shuffledSelectedItems = shuffle(
|
||||
itemList.filter(({ id }) => selectedItemIds.includes(id))
|
||||
);
|
||||
|
||||
return itemList.map((item) => {
|
||||
if (selectedItemIds.includes(item.id)) {
|
||||
return { ...shuffledSelectedItems.pop(), x: item.x, y: item.y };
|
||||
}
|
||||
return item;
|
||||
});
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue