Improve perf and way items are synchronized

This commit is contained in:
Jeremie Pardou-Piquemal 2020-06-14 10:23:20 +02:00
parent 31f6c2807c
commit 0e087ed1c3
7 changed files with 86 additions and 63 deletions

5
package-lock.json generated
View file

@ -8540,6 +8540,11 @@
"lodash._reinterpolate": "^3.0.0"
}
},
"lodash.throttle": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
"integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ="
},
"lodash.uniq": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",

View file

@ -8,6 +8,7 @@
"cors": "^2.8.5",
"lodash.debounce": "^4.0.8",
"lodash.findlast": "^4.6.0",
"lodash.throttle": "^4.1.1",
"memoizee": "^0.4.14",
"nanoid": "^3.1.9",
"randomcolor": "^0.5.4",

View file

@ -5,8 +5,10 @@ import { selectedItemsAtom } from '../components/Selector';
import { ItemListAtom } from '../components/Items';
import { useRecoilState } from 'recoil';
import { insideClass, isPointInsideRect, isPointInsideItem } from '../utils';
import { useC2C } from '../hooks/useC2C';
const ActionPane = ({ children }) => {
const [c2c] = useC2C();
const panZoomRotate = useRecoilValue(PanZoomRotateState);
const [itemList, setItemList] = useRecoilState(ItemListAtom);
const [selectedItems, setSelectedItems] = useRecoilState(selectedItemsAtom);
@ -45,17 +47,20 @@ const ActionPane = ({ children }) => {
ids = selectedItems;
}
setItemList((prevList) => {
return prevList.map((item) => {
const newItemList = prevList.map((item) => {
if (ids.includes(item.id)) {
const x = item.x + posDelta.x;
const y = item.y + posDelta.y;
return { ...item, x, y };
const newItem = { ...item, x, y };
return newItem;
}
return item;
});
c2c.publish(`selectedItemsMove`, { itemIds: ids, move: posDelta });
return newItemList;
});
},
[setItemList, selectedItems]
[setItemList, selectedItems, c2c]
);
const onMouseMouve = (e) => {
@ -80,6 +85,23 @@ const ActionPane = ({ children }) => {
}
};
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;
});
});
});
return unsub;
}, [c2c, setItemList]);
return (
<div
onMouseDown={onMouseDown}

View file

@ -1,10 +1,8 @@
import React from 'react';
import { DraggableCore } from 'react-draggable';
import { useC2C } from '../hooks/useC2C';
import { PanZoomRotateState } from '../components/PanZoomRotate';
import { useRecoilValue } from 'recoil';
import { selectedItemsAtom } from './Selector';
import { nanoid } from 'nanoid';
const Rect = ({ width, height, color }) => {
return (
@ -50,7 +48,7 @@ const Image = ({
}
const onDblClick = (e) => {
updateState({ flipped: !flipped });
updateState((prevItem) => ({ ...prevItem, flipped: !prevItem.flipped }));
};
if (flipped && backContent) {
@ -134,8 +132,8 @@ const Item = ({ setState, state }) => {
const rotation = state.rotation || 0;
const updateState = React.useCallback(
(modif) => {
setState({ ...state, ...modif });
(callbackOrItem) => {
setState(state.id, callbackOrItem);
},
[setState, state]
);
@ -148,11 +146,11 @@ const Item = ({ setState, state }) => {
if (entry.contentBoxSize) {
const { inlineSize: width, blockSize: height } = entry.contentBoxSize;
if (state.actualWidth !== width || state.actualHeight !== height) {
setState({
...state,
updateState((prevState) => ({
...prevState,
actualWidth: width,
actualHeight: height,
});
}));
}
}
});
@ -162,7 +160,7 @@ const Item = ({ setState, state }) => {
return () => {
observer.unobserve(currentElem);
};
}, [setState, state]);
}, [updateState, state]);
const content = (
<div
@ -200,27 +198,12 @@ const Item = ({ setState, state }) => {
const SyncedItem = ({ setState, state }) => {
const [c2c] = useC2C();
const versionsRef = React.useRef([]);
React.useEffect(() => {
if (versionsRef.current.includes(state.version)) {
versionsRef.current = versionsRef.current.filter((v) => {
return v !== state.version;
});
} else {
c2c.publish(`itemStateUpdate.${state.id}`, {
...state,
});
}
}, [c2c, setState, state]);
React.useEffect(() => {
const unsub = c2c.subscribe(
`itemStateUpdate.${state.id}`,
(newItemState) => {
const nextVersion = nanoid();
versionsRef.current.push(nextVersion);
setState({ ...newItemState, version: nextVersion });
setState(state.id, newItemState, false);
}
);
return unsub;

View file

@ -1,7 +1,7 @@
import React from 'react';
import { useRecoilState, atom, selector, useRecoilValue } from 'recoil';
import { useRecoilState, atom } from 'recoil';
import Item from '../components/Item';
import { selectedItemsAtom } from './Selector';
import { useC2C } from '../hooks/useC2C';
export const ItemListAtom = atom({
key: 'itemList',
@ -9,24 +9,34 @@ export const ItemListAtom = atom({
});
const Items = ({}) => {
const [c2c] = useC2C();
const [itemList, setItemList] = useRecoilState(ItemListAtom);
const updateItemState = React.useCallback(
(newState) => {
const updateItem = React.useCallback(
(id, callbackOrItem, sync = true) => {
let callback = callbackOrItem;
if (typeof callbackOrItem === 'object') {
callback = (item) => callbackOrItem;
}
setItemList((prevList) => {
return prevList.map((item) => {
if (item.id === newState.id) {
return { ...item, ...newState, id: item.id };
if (item.id === id) {
const newItem = {
...callback(item),
id: item.id, // Prevent id modification
};
sync && c2c.publish(`itemStateUpdate.${id}`, newItem);
return newItem;
}
return item;
});
});
},
[setItemList]
[setItemList, c2c]
);
return itemList.map((item) => (
<Item key={item.id} state={item} setState={updateItemState} />
<Item key={item.id} state={item} setState={updateItem} />
));
};

View file

@ -81,7 +81,7 @@ const Selector = ({ children }) => {
const selected = findSelected(itemList, stateRef.current).map(
({ id }) => id
);
setSelected(findSelected(itemList, stateRef.current).map(({ id }) => id));
setSelected(selected);
stateRef.current = { moving: false };
setSelector({ ...stateRef.current });
wrapperRef.current.style.cursor = 'auto';

View file

@ -3,34 +3,36 @@ import { urlAlphabet } from 'nanoid';
const genGame = () => {
const items = [];
items.push({
type: 'image',
content: '/games/card.jpg',
backContent: '/games/back.jpg',
width: 100,
flipped: true,
x: 100,
y: 100,
});
[...Array(10).keys()].forEach(() => {
items.push({
type: 'image',
content: '/games/card.jpg',
backContent: '/games/back.jpg',
width: 100,
flipped: true,
x: 100,
y: 100,
});
items.push({
type: 'image',
content: '/games/card.jpg',
backContent: '/games/back.jpg',
width: 100,
flipped: true,
x: 100,
y: 100,
});
items.push({
type: 'image',
content: '/games/card.jpg',
backContent: '/games/back.jpg',
width: 100,
flipped: true,
x: 100,
y: 100,
});
items.push({
type: 'image',
content: '/games/8diamond.png',
backContent: '/games/back.jpg',
width: 100,
flipped: true,
x: 100,
y: 100,
items.push({
type: 'image',
content: '/games/8diamond.png',
backContent: '/games/back.jpg',
width: 100,
flipped: true,
x: 100,
y: 100,
});
});
return {