Fix regressions

This commit is contained in:
Jeremie Pardou-Piquemal 2021-08-22 20:43:00 +02:00 committed by Jérémie Pardou-Piquemal
parent 388437ce7e
commit 1f60acc517
10 changed files with 25 additions and 663 deletions

View file

@ -1,614 +0,0 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { nanoid } from "nanoid";
import { toast } from "react-toastify";
import { useSetRecoilState, useRecoilCallback } from "recoil";
import { useItemActions } from "../board/Items";
import { SelectedItemsAtom } from "../board";
import { useUsers } from "../users";
import { ItemMapAtom } from "../board";
import { shuffle as shuffleArray, randInt } from "../utils";
import deleteIcon from "../images/delete.svg";
import stackToCenterIcon from "../images/stackToCenter.svg";
import stackToTopLeftIcon from "../images/stackToTopLeft.svg";
import alignAsLineIcon from "../images/alignAsLine.svg";
import alignAsSquareIcon from "../images/alignAsSquare.svg";
import duplicateIcon from "../images/duplicate.svg";
import seeIcon from "../images/see.svg";
import flipIcon from "../images/flip.svg";
import lockIcon from "../images/lock.svg";
import rotateIcon from "../images/rotate.svg";
import shuffleIcon from "../images/shuffle.svg";
import tapIcon from "../images/tap.svg";
import useLocalStorage from "../hooks/useLocalStorage";
export const useGameItemActionMap = () => {
const {
batchUpdateItems,
removeItems,
insertItemBefore,
reverseItemsOrder,
swapItems,
} = useItemActions();
const { t } = useTranslation();
const [isFirstLock, setIsFirstLock] = useLocalStorage("isFirstLock", true);
const { currentUser } = useUsers();
const setSelectedItems = useSetRecoilState(SelectedItemsAtom);
const isMountedRef = React.useRef(false);
const getItemListOrSelected = useRecoilCallback(
({ snapshot }) => async (itemIds) => {
const itemMap = await snapshot.getPromise(ItemMapAtom);
if (itemIds) {
return [itemIds, itemIds.map((id) => itemMap[id])];
} else {
const selectedItems = await snapshot.getPromise(SelectedItemsAtom);
return [selectedItems, selectedItems.map((id) => itemMap[id])];
}
},
[]
);
React.useEffect(() => {
// Mounted guard
isMountedRef.current = true;
return () => {
isMountedRef.current = false;
};
}, []);
// Stack selection to Center
const stackToCenter = React.useCallback(
async (
itemIds,
{
stackThicknessMin = 0.5,
stackThicknessMax = 1,
limitCardsNumber = 32,
} = {}
) => {
const [ids, items] = await getItemListOrSelected(itemIds);
// Rule to manage thickness of the stack.
let stackThickness = stackThicknessMax;
if (items.length >= limitCardsNumber) {
stackThickness = stackThicknessMin;
}
// To avoid displacement effects.
let isSameGap = true;
for (let i = 1; i < items.length; i++) {
if (Math.abs(items[i].x - items[i - 1].x) != stackThickness) {
isSameGap = false;
break;
}
if (Math.abs(items[i].y - items[i - 1].y) != stackThickness) {
isSameGap = false;
break;
}
}
if (isSameGap == true) {
return;
}
// Compute middle position
const minMax = { min: {}, max: {} };
minMax.min.x = Math.min(...items.map(({ x }) => x));
minMax.min.y = Math.min(...items.map(({ y }) => y));
minMax.max.x = Math.max(
...items.map(({ x, id }) => x + document.getElementById(id).clientWidth)
);
minMax.max.y = Math.max(
...items.map(
({ y, id }) => y + document.getElementById(id).clientHeight
)
);
const { clientWidth, clientHeight } = document.getElementById(
items[0].id
);
let newX =
minMax.min.x + (minMax.max.x - minMax.min.x) / 2 - clientWidth / 2;
let newY =
minMax.min.y + (minMax.max.y - minMax.min.y) / 2 - clientHeight / 2;
batchUpdateItems(ids, (item) => {
const newItem = {
...item,
x: newX,
y: newY,
};
newX += stackThickness;
newY -= stackThickness;
return newItem;
});
},
[batchUpdateItems, getItemListOrSelected]
);
// Stack selection to Top Left
const stackToTopLeft = React.useCallback(
async (
itemIds,
{
stackThicknessMin = 0.5,
stackThicknessMax = 1,
limitCardsNumber = 32,
} = {}
) => {
const [ids, items] = await getItemListOrSelected(itemIds);
let { x: newX, y: newY } = items[0];
// Rule to manage thickness of the stack.
let stackThickness = stackThicknessMax;
if (items.length >= limitCardsNumber) {
stackThickness = stackThicknessMin;
}
batchUpdateItems(ids, (item) => {
const newItem = {
...item,
x: newX,
y: newY,
};
newX += stackThickness;
newY -= stackThickness;
return newItem;
});
},
[batchUpdateItems, getItemListOrSelected]
);
// Align selection to a line
const alignAsLine = React.useCallback(
async (itemIds, { gapBetweenItems = 5 } = {}) => {
// Negative value is possible for 'gapBetweenItems'.
const [ids, items] = await getItemListOrSelected(itemIds);
let { x: newX, y: newY } = items[0];
batchUpdateItems(ids, (item) => {
const { clientWidth } = document.getElementById(item.id);
const newItem = {
...item,
x: newX,
y: newY,
};
newX += clientWidth + gapBetweenItems;
return newItem;
});
},
[getItemListOrSelected, batchUpdateItems]
);
// Align selection to an array
const alignAsSquare = React.useCallback(
async (itemIds, { gapBetweenItems = 5 } = {}) => {
// Negative value is possible for 'gapBetweenItems'.
const [ids, items] = await getItemListOrSelected(itemIds);
// Count number of elements
const numberOfElements = items.length;
const numberOfColumns = Math.ceil(Math.sqrt(numberOfElements));
let { x: newX, y: newY } = items[0];
let currentColumn = 1;
batchUpdateItems(ids, (item) => {
const { clientWidth, clientHeight } = document.getElementById(item.id);
const newItem = {
...item,
x: newX,
y: newY,
};
newX += clientWidth + gapBetweenItems;
currentColumn += 1;
if (currentColumn > numberOfColumns) {
currentColumn = 1;
newX = items[0].x;
newY += clientHeight + gapBetweenItems;
}
return newItem;
});
},
[getItemListOrSelected, batchUpdateItems]
);
const shuffleItems = React.useCallback(
async (itemIds) => {
const [ids] = await getItemListOrSelected(itemIds);
ids.forEach((itemId) => {
const elem = document.getElementById(itemId);
elem.firstChild.className = "hvr-wobble-horizontal";
});
const shuffledItems = shuffleArray([...ids]);
swapItems(ids, shuffledItems);
},
[getItemListOrSelected, swapItems]
);
const randomlyRotateSelectedItems = React.useCallback(
async (itemIds, { angle, maxRotateCount }) => {
const [ids] = await getItemListOrSelected(itemIds);
batchUpdateItems(ids, (item) => {
const rotation =
((item.rotation || 0) + angle * randInt(0, maxRotateCount)) % 360;
return { ...item, rotation };
});
},
[getItemListOrSelected, batchUpdateItems]
);
// Tap/Untap elements
const toggleTap = React.useCallback(
async (itemIds) => {
const [ids, items] = await getItemListOrSelected(itemIds);
const tappedCount = items.filter(({ rotation }) => rotation === 90)
.length;
let untap = false;
if (tappedCount > ids.length / 2) {
untap = true;
}
batchUpdateItems(ids, (item) => ({
...item,
rotation: untap ? 0 : 90,
}));
},
[getItemListOrSelected, batchUpdateItems]
);
// Lock / unlock elements
const toggleLock = React.useCallback(
async (itemIds) => {
const [ids] = await getItemListOrSelected(itemIds);
batchUpdateItems(ids, (item) => ({
...item,
locked: !item.locked,
}));
// Help user on first lock
if (isFirstLock) {
toast.info(
t("You've locked your first element. Long click to select it again."),
{ autoClose: false }
);
setIsFirstLock(false);
}
},
[getItemListOrSelected, batchUpdateItems, isFirstLock, t, setIsFirstLock]
);
// Flip or reveal items
const setFlip = React.useCallback(
async (itemIds, { flip = true, reverseOrder = true } = {}) => {
batchUpdateItems(itemIds, (item) => ({
...item,
flipped: flip,
unflippedFor:
!Array.isArray(item.unflippedFor) || item.unflippedFor.length > 0
? null
: item.unflippedFor,
}));
if (reverseOrder) {
reverseItemsOrder(itemIds);
setSelectedItems((prev) => {
const reversed = [...prev];
reversed.reverse();
return reversed;
});
}
},
[batchUpdateItems, reverseItemsOrder, setSelectedItems]
);
// Toggle flip state
const toggleFlip = React.useCallback(
async (itemIds, { reverseOrder = true } = {}) => {
const [ids, items] = await getItemListOrSelected(itemIds);
const flippedCount = items.filter(({ flipped }) => flipped).length;
setFlip(ids, {
flip: flippedCount < ids.length / 2,
reverseOrder,
});
},
[getItemListOrSelected, setFlip]
);
// Rotate element
const rotate = React.useCallback(
async (itemIds, { angle }) => {
const [ids] = await getItemListOrSelected(itemIds);
batchUpdateItems(ids, (item) => ({
...item,
rotation: (item.rotation || 0) + angle,
}));
},
[getItemListOrSelected, batchUpdateItems]
);
// Reveal for player only
const setFlipSelf = React.useCallback(
async (itemIds, { flipSelf = true } = {}) => {
batchUpdateItems(itemIds, (item) => {
let { unflippedFor = [] } = item;
if (!Array.isArray(item.unflippedFor)) {
unflippedFor = [];
}
if (flipSelf && !unflippedFor.includes(currentUser.uid)) {
unflippedFor = [...unflippedFor, currentUser.uid];
}
if (!flipSelf && unflippedFor.includes(currentUser.uid)) {
unflippedFor = unflippedFor.filter((id) => id !== currentUser.uid);
}
return {
...item,
flipped: true,
unflippedFor,
};
});
},
[batchUpdateItems, currentUser.uid]
);
// Reveal for player only
const toggleFlipSelf = React.useCallback(
async (itemIds) => {
const [ids, items] = await getItemListOrSelected(itemIds);
const flippedSelfCount = items.filter(
({ unflippedFor }) =>
Array.isArray(unflippedFor) && unflippedFor.includes(currentUser.uid)
).length;
let flipSelf = true;
if (flippedSelfCount > ids.length / 2) {
flipSelf = false;
}
setFlipSelf(ids, { flipSelf });
},
[getItemListOrSelected, setFlipSelf, currentUser.uid]
);
const remove = React.useCallback(
async (itemIds) => {
const [ids] = await getItemListOrSelected(itemIds);
removeItems(ids);
},
[getItemListOrSelected, removeItems]
);
const cloneItem = React.useCallback(
async (itemIds) => {
const [, items] = await getItemListOrSelected(itemIds);
items.forEach((itemToClone) => {
const newItem = JSON.parse(JSON.stringify(itemToClone));
newItem.id = nanoid();
delete newItem.move;
insertItemBefore(newItem, itemToClone.id);
});
},
[getItemListOrSelected, insertItemBefore]
);
const actionMap = React.useMemo(
() => ({
flip: {
action: toggleFlip,
label: t("Reveal") + "/" + t("Hide"),
shortcut: "f",
icon: flipIcon,
},
reveal: {
action: (itemIds) => setFlip(itemIds, { flip: false }),
label: t("Reveal"),
icon: flipIcon,
},
hide: {
action: (itemIds) => setFlip(itemIds, { flip: true }),
label: t("Hide"),
icon: flipIcon,
},
flipSelf: {
action: toggleFlipSelf,
label: t("Reveal for me"),
shortcut: "o",
icon: seeIcon,
},
revealSelf: {
action: (itemIds) => setFlipSelf(itemIds, { flipSelf: true }),
label: t("Reveal for me"),
icon: seeIcon,
},
hideSelf: {
action: (itemIds) => setFlipSelf(itemIds, { flipSelf: false }),
label: t("Hide for me"),
icon: seeIcon,
},
tap: {
action: toggleTap,
label: t("Tap") + "/" + t("Untap"),
shortcut: "t",
icon: tapIcon,
},
stackToCenter: {
action: stackToCenter,
label: t("Stack To Center"),
shortcut: "",
multiple: true,
icon: stackToCenterIcon,
},
stack: {
action: stackToTopLeft,
label: t("Stack To Top Left"),
multiple: true,
icon: stackToTopLeftIcon,
},
alignAsLine: {
action: alignAsLine,
label: t("Align as line"),
multiple: true,
icon: alignAsLineIcon,
},
alignAsSquare: {
action: alignAsSquare,
label: t("Align as square"),
multiple: true,
icon: alignAsSquareIcon,
},
shuffle: {
action: shuffleItems,
label: t("Shuffle"),
multiple: true,
icon: shuffleIcon,
},
randomlyRotate30: {
action: (itemIds) =>
randomlyRotateSelectedItems(itemIds, {
angle: 30,
maxRotateCount: 11,
}),
label: t("Rotate randomly 30"),
multiple: false,
icon: rotateIcon,
},
randomlyRotate45: {
action: (itemIds) =>
randomlyRotateSelectedItems(itemIds, {
angle: 45,
maxRotateCount: 7,
}),
label: t("Rotate randomly 45"),
shortcut: "",
multiple: false,
icon: rotateIcon,
},
randomlyRotate60: {
action: (itemIds) =>
randomlyRotateSelectedItems(itemIds, {
angle: 60,
maxRotateCount: 5,
}),
label: t("Rotate randomly 60"),
shortcut: "",
multiple: false,
icon: rotateIcon,
},
randomlyRotate90: {
action: (itemIds) =>
randomlyRotateSelectedItems(itemIds, {
angle: 90,
maxRotateCount: 3,
}),
label: t("Rotate randomly 90"),
shortcut: "",
multiple: false,
icon: rotateIcon,
},
randomlyRotate180: {
action: (itemIds) =>
randomlyRotateSelectedItems(itemIds, {
angle: 180,
maxRotateCount: 1,
}),
label: t("Rotate randomly 180"),
shortcut: "",
multiple: false,
icon: rotateIcon,
},
rotate30: {
action: (itemIds) => rotate(itemIds, { angle: 30 }),
label: t("Rotate 30"),
shortcut: "r",
icon: rotateIcon,
},
rotate45: {
action: (itemIds) => rotate(itemIds, { angle: 45 }),
label: t("Rotate 45"),
shortcut: "r",
icon: rotateIcon,
},
rotate60: {
action: (itemIds) => rotate(itemIds, { angle: 60 }),
label: t("Rotate 60"),
shortcut: "r",
icon: rotateIcon,
},
rotate90: {
action: (itemIds) => rotate(itemIds, { angle: 90 }),
label: t("Rotate 90"),
shortcut: "r",
icon: rotateIcon,
},
rotate180: {
action: (itemIds) => rotate(itemIds, { angle: 180 }),
label: t("Rotate 180"),
shortcut: "r",
icon: rotateIcon,
},
clone: {
action: cloneItem,
label: t("Clone"),
shortcut: "c",
disableDblclick: true,
edit: true,
icon: duplicateIcon,
},
lock: {
action: toggleLock,
label: t("Unlock") + "/" + t("Lock"),
disableDblclick: true,
icon: lockIcon,
},
remove: {
action: remove,
label: t("Remove all"),
shortcut: "Delete",
edit: true,
disableDblclick: true,
icon: deleteIcon,
},
}),
[
toggleFlip,
t,
toggleFlipSelf,
toggleTap,
stackToCenter,
stackToTopLeft,
alignAsLine,
alignAsSquare,
shuffleItems,
cloneItem,
toggleLock,
remove,
setFlip,
setFlipSelf,
randomlyRotateSelectedItems,
rotate,
]
);
return { actionMap, setFlip, setFlipSelf, stack: stackToTopLeft };
};
export default useGameItemActionMap;

View file

@ -26,7 +26,7 @@ export const useGameItemActions = () => {
const {
batchUpdateItems,
removeItems,
insertItemBefore,
pushItem,
reverseItemsOrder,
swapItems,
getItems,
@ -396,10 +396,10 @@ export const useGameItemActions = () => {
const newItem = JSON.parse(JSON.stringify(itemToClone));
newItem.id = nanoid();
delete newItem.move;
insertItemBefore(newItem, itemToClone.id);
pushItem(newItem, itemToClone.id);
});
},
[getItemListOrSelected, insertItemBefore]
[getItemListOrSelected, pushItem]
);
const actionMap = React.useMemo(

View file

@ -74,8 +74,8 @@ export const SessionProvider = ({ sessionId, fromGameId, children }) => {
async (newData, sync = false) => {
const { availableItems, items, board, messages = [] } = newData;
setAvailableItems(availableItems);
// The filter prevent the empty item bug on reload
setItemList(items.filter((item) => item));
// The filter prevents the empty item bug or missing type on reload
setItemList(items.filter((item) => item && item.type));
setBoardConfig(board, false);
setMessages(messages);

View file

@ -5,9 +5,9 @@ import styled from "styled-components";
const Color = styled.div`
background-color: ${({ color }) => color};
border: 1px solid #00000022;
width: 20px;
height: 20px;
border: 2px solid #ffffff66;
width: 32px;
height: 32px;
margin: 5px;
cursor: pointer;
`;

View file

@ -2,29 +2,13 @@ import React from "react";
import { useTranslation } from "react-i18next";
import useSession from "../../hooks/useSession";
import { useItems, useBoardConfig, useMessage } from "react-sync-board";
import DownloadLink from "./DownloadLink";
import Modal from "../../ui/Modal";
const ExportModal = ({ show, setShow }) => {
const { t } = useTranslation();
const { gameId, availableItems } = useSession();
const items = useItems();
const [boardConfig] = useBoardConfig();
const { messages } = useMessage();
const getSession = React.useCallback(() => {
const currentSession = {
items: items,
board: boardConfig,
availableItems: availableItems,
messages: messages.slice(-50),
timestamp: Date.now(),
gameId: gameId,
};
return currentSession;
}, [availableItems, boardConfig, gameId, items, messages]);
const { getSession } = useSession();
return (
<Modal title={t("Save game")} setShow={setShow} show={show}>

View file

@ -1,11 +1,10 @@
import React from "react";
import { useTranslation, Trans } from "react-i18next";
import { useRecoilValue } from "recoil";
import useAsyncEffect from "use-async-effect";
import styled from "styled-components";
import { useBoardConfig } from "react-sync-board";
import Modal from "../../ui/Modal";
// import { BoardConfigAtom } from "../../board";
import { getBestTranslationFromConfig } from "../../utils/api";
@ -30,12 +29,11 @@ const InfoModal = ({ show, setShow }) => {
const [info, setInfo] = React.useState("");
// const boardConfig = useRecoilValue(BoardConfigAtom);
const [boardConfig] = useBoardConfig();
const translation = React.useMemo(
// () => getBestTranslationFromConfig(boardConfig, i18n.languages),
() => getBestTranslationFromConfig({}, i18n.languages),
[i18n.languages]
() => getBestTranslationFromConfig(boardConfig, i18n.languages),
[boardConfig, i18n.languages]
);
useAsyncEffect(

View file

@ -104,7 +104,6 @@ const SelectedItemsPane = ({ hideMenu = false, ItemFormComponent }) => {
e.key === shortcut &&
showEdit === !!whileEdit
) {
// here
action();
}
});
@ -133,10 +132,8 @@ const SelectedItemsPane = ({ hideMenu = false, ItemFormComponent }) => {
if (e.ctrlKey && filteredActions.length > 1) {
// Use second action
// here
actionMap[filteredActions[1]].action();
} else if (filteredActions.length > 0) {
// here
actionMap[filteredActions[0]].action();
}
},
@ -206,7 +203,6 @@ const SelectedItemsPane = ({ hideMenu = false, ItemFormComponent }) => {
<button
className="button clear icon-only"
key={action}
// here
onClick={() => handler()}
title={label + (shortcut ? ` (${shortcut})` : "")}
>

View file

@ -41,7 +41,7 @@ const WelcomeModal = ({ show, setShow, welcome = true }) => {
inputRef.current.select();
document.execCommand("copy");
inputRef.current.style.display = "none";
toast.info(t("Url copied to clipboard!"), { autoClose: 100000 });
toast.info(t("Url copied to clipboard!"), { autoClose: 1000 });
};
const meetUrl = `https://meet.jit.si/airboardgame__${room}`;

View file

@ -202,17 +202,17 @@ const GameListView = () => {
);
const filteredGameList = React.useMemo(() => {
return gameList
? gameList.filter((game) => {
return (
(filterCriteria.searchTerm === NULL_SEARCH_TERM ||
search(filterCriteria.searchTerm, game.defaultName)) &&
hasRequestedValues(filterCriteria.nbOfPlayers, game.playerCount) &&
hasRequestedValues(filterCriteria.durations, game.duration) &&
hasAllowedMaterialLanguage(filterCriteria, game)
);
})
: [];
if (gameList) {
return gameList.filter(
(game) =>
(filterCriteria.searchTerm === NULL_SEARCH_TERM ||
search(filterCriteria.searchTerm, game.defaultName)) &&
hasRequestedValues(filterCriteria.nbOfPlayers, game.playerCount) &&
hasRequestedValues(filterCriteria.durations, game.duration) &&
hasAllowedMaterialLanguage(filterCriteria, game)
);
}
return [];
}, [gameList, filterCriteria]);
const onChangeNbOfPlayersSlider = (values) => {

View file

@ -83,8 +83,6 @@ const WebConferenceContent = ({ users }) => {
setShowLocalAudio((prev) => !prev);
}, [setShowLocalAudio]);
// const { localUsers: users } = useUsers();
const streamMap = React.useMemo(
() =>
remoteStreams.reduce((acc, stream) => {