123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542 |
- // import React from "react";
- // import { useTranslation } from "react-i18next";
- import i18n from "../i18n";
- import { nanoid } from "nanoid";
- import { toast } from "react-toastify";
- import { shuffle as shuffleArray, randInt } from "../views/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";
- const stackToCenter = async (
- itemIds,
- {
- stackThicknessMin = 0.5,
- stackThicknessMax = 1,
- limitCardsNumber = 32,
- } = {},
- { getItem, batchUpdateItems }
- ) => {
- const items = itemIds.map(getItem);
- // 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(itemIds, (item) => {
- const newItem = {
- ...item,
- x: newX,
- y: newY,
- };
- newX += stackThickness;
- newY -= stackThickness;
- return newItem;
- });
- };
- // Stack selection to Top Left
- export const stackToTopLeft = async (
- itemIds,
- {
- stackThicknessMin = 0.5,
- stackThicknessMax = 1,
- limitCardsNumber = 32,
- } = {},
- { getItem, batchUpdateItems }
- ) => {
- const items = await Promise.all(itemIds.map(getItem));
- let { x: newX, y: newY } = items[0];
- // Rule to manage thickness of the stack.
- let stackThickness = stackThicknessMax;
- if (items.length >= limitCardsNumber) {
- stackThickness = stackThicknessMin;
- }
- batchUpdateItems(itemIds, (item) => {
- const newItem = {
- ...item,
- x: newX,
- y: newY,
- };
- newX += stackThickness;
- newY -= stackThickness;
- return newItem;
- });
- };
- // Align selection to a line
- const alignAsLine = async (
- itemIds,
- { gapBetweenItems = 5 } = {},
- { getItem, batchUpdateItems }
- ) => {
- // Negative value is possible for 'gapBetweenItems'.
- let { x: newX, y: newY } = await getItem(itemIds[0]);
- batchUpdateItems(itemIds, (item) => {
- const { clientWidth } = document.getElementById(item.id);
- const newItem = {
- ...item,
- x: newX,
- y: newY,
- };
- newX += clientWidth + gapBetweenItems;
- return newItem;
- });
- };
- // Align selection to an array
- const alignAsSquare = async (
- itemIds,
- { gapBetweenItems = 5 } = {},
- { batchUpdateItems, getItem }
- ) => {
- // Negative value is possible for 'gapBetweenItems'.
- const items = await Promise.all(itemIds.map(getItem));
- // 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(itemIds, (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;
- });
- };
- const shuffleItems = async (itemIds, _, { swapItems }) => {
- itemIds.forEach((itemId) => {
- const elem = document.getElementById(itemId);
- elem.firstChild.className = "hvr-wobble-horizontal";
- });
- const shuffledItems = shuffleArray([...itemIds]);
- swapItems(itemIds, shuffledItems);
- };
- const randomlyRotateSelectedItems = async (
- itemIds,
- { angle, maxRotateCount },
- { batchUpdateItems }
- ) => {
- batchUpdateItems(itemIds, (item) => {
- const rotation =
- ((item.rotation || 0) + angle * randInt(0, maxRotateCount)) % 360;
- return { ...item, rotation };
- });
- };
- // Tap/Untap elements
- const toggleTap = async (itemIds, _, { getItem, batchUpdateItems }) => {
- const items = await Promise.all(itemIds.map(getItem));
- const tappedCount = items.filter(({ rotation }) => rotation === 90).length;
- let untap = false;
- if (tappedCount > itemIds.length / 2) {
- untap = true;
- }
- batchUpdateItems(itemIds, (item) => ({
- ...item,
- rotation: untap ? 0 : 90,
- }));
- };
- // Lock / unlock elements
- const toggleLock = async (itemIds, _, { batchUpdateItems }) => {
- batchUpdateItems(itemIds, (item) => ({
- ...item,
- locked: !item.locked,
- }));
- const isFirstLock = !window.localStorage.getItem("isFirstLock");
- // Help user on first lock
- if (isFirstLock) {
- toast.info(
- i18n.t(
- "You've locked your first element. Long click to select it again."
- ),
- { autoClose: false }
- );
- window.localStorage.setItem("isFirstLock", "false");
- }
- };
- // Flip or reveal items
- export const setFlip = async (
- itemIds,
- { flip = true, reverseOrder = true } = {},
- { batchUpdateItems, reverseItemsOrder }
- ) => {
- batchUpdateItems(itemIds, (item) => ({
- ...item,
- flipped: flip,
- unflippedFor:
- !Array.isArray(item.unflippedFor) || item.unflippedFor.length > 0
- ? null
- : item.unflippedFor,
- }));
- if (reverseOrder) {
- reverseItemsOrder(itemIds);
- }
- };
- // Toggle flip state
- const toggleFlip = async (
- itemIds,
- { reverseOrder = true } = {},
- baseActions
- ) => {
- const items = await Promise.all(itemIds.map(baseActions.getItem));
- const flippedCount = items.filter(({ flipped }) => flipped).length;
- setFlip(
- itemIds,
- {
- flip: flippedCount < itemIds.length / 2,
- reverseOrder,
- },
- baseActions
- );
- };
- // Rotate element
- const rotate = async (itemIds, { angle }, { batchUpdateItems }) => {
- batchUpdateItems(itemIds, (item) => ({
- ...item,
- rotation: (item.rotation || 0) + angle,
- }));
- };
- // Reveal for player only
- export const setFlipSelf = async (
- itemIds,
- { flipSelf = true } = {},
- { batchUpdateItems }
- ) => {
- 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,
- };
- });
- };
- // Reveal for player only
- const toggleFlipSelf = async (itemIds, params, actions) => {
- const items = await Promise.all(itemIds.map(actions.getItem));
- const flippedSelfCount = items.filter(
- ({ unflippedFor }) =>
- Array.isArray(unflippedFor) && unflippedFor.includes(currentUser.uid)
- ).length;
- let flipSelf = true;
- if (flippedSelfCount > itemIds.length / 2) {
- flipSelf = false;
- }
- setFlipSelf(itemIds, { ...params, flipSelf }, actions);
- };
- const remove = async (itemIds, _, { removeItems }) => {
- removeItems(itemIds);
- };
- const cloneItem = async (itemIds, _, { getItem, insertItemBefore }) => {
- const items = await Promise.all(itemIds.map(getItem));
- items.forEach((itemToClone) => {
- const newItem = JSON.parse(JSON.stringify(itemToClone));
- newItem.id = nanoid();
- delete newItem.move;
- insertItemBefore(newItem, itemToClone.id);
- });
- };
- const actionMap = {
- flip: {
- action: toggleFlip,
- label: i18n.t("Reveal") + "/" + i18n.t("Hide"),
- shortcut: "f",
- icon: flipIcon,
- },
- reveal: {
- action: (itemIds, _, baseActions) =>
- setFlip(itemIds, { flip: false }, baseActions),
- label: i18n.t("Reveal"),
- icon: flipIcon,
- },
- hide: {
- action: (itemIds, _, baseActions) =>
- setFlip(itemIds, { flip: true }, baseActions),
- label: i18n.t("Hide"),
- icon: flipIcon,
- },
- flipSelf: {
- action: toggleFlipSelf,
- label: i18n.t("Reveal for me"),
- shortcut: "o",
- icon: seeIcon,
- },
- revealSelf: {
- action: (itemIds, _, baseActions) =>
- setFlipSelf(itemIds, { flipSelf: true }, baseActions),
- label: i18n.t("Reveal for me"),
- icon: seeIcon,
- },
- hideSelf: {
- action: (itemIds, _, baseActions) =>
- setFlipSelf(itemIds, { flipSelf: false }, baseActions),
- label: i18n.t("Hide for me"),
- icon: seeIcon,
- },
- tap: {
- action: toggleTap,
- label: i18n.t("Tap") + "/" + i18n.t("Untap"),
- shortcut: "t",
- icon: tapIcon,
- },
- stackToCenter: {
- action: stackToCenter,
- label: i18n.t("Stack To Center"),
- shortcut: "",
- multiple: true,
- icon: stackToCenterIcon,
- },
- stack: {
- action: stackToTopLeft,
- label: i18n.t("Stack To Top Left"),
- multiple: true,
- icon: stackToTopLeftIcon,
- },
- alignAsLine: {
- action: alignAsLine,
- label: i18n.t("Align as line"),
- multiple: true,
- icon: alignAsLineIcon,
- },
- alignAsSquare: {
- action: alignAsSquare,
- label: i18n.t("Align as square"),
- multiple: true,
- icon: alignAsSquareIcon,
- },
- shuffle: {
- action: shuffleItems,
- label: i18n.t("Shuffle"),
- multiple: true,
- icon: shuffleIcon,
- },
- randomlyRotate30: {
- action: (itemIds, _, baseActions) =>
- randomlyRotateSelectedItems(
- itemIds,
- {
- angle: 30,
- maxRotateCount: 11,
- },
- baseActions
- ),
- label: i18n.t("Rotate randomly 30"),
- multiple: false,
- icon: rotateIcon,
- },
- randomlyRotate45: {
- action: (itemIds, _, baseActions) =>
- randomlyRotateSelectedItems(
- itemIds,
- {
- angle: 45,
- maxRotateCount: 7,
- },
- baseActions
- ),
- label: i18n.t("Rotate randomly 45"),
- shortcut: "",
- multiple: false,
- icon: rotateIcon,
- },
- randomlyRotate60: {
- action: (itemIds, _, baseActions) =>
- randomlyRotateSelectedItems(
- itemIds,
- {
- angle: 60,
- maxRotateCount: 5,
- },
- baseActions
- ),
- label: i18n.t("Rotate randomly 60"),
- shortcut: "",
- multiple: false,
- icon: rotateIcon,
- },
- randomlyRotate90: {
- action: (itemIds, _, baseActions) =>
- randomlyRotateSelectedItems(
- itemIds,
- {
- angle: 90,
- maxRotateCount: 3,
- },
- baseActions
- ),
- label: i18n.t("Rotate randomly 90"),
- shortcut: "",
- multiple: false,
- icon: rotateIcon,
- },
- randomlyRotate180: {
- action: (itemIds, _, baseActions) =>
- randomlyRotateSelectedItems(
- itemIds,
- {
- angle: 180,
- maxRotateCount: 1,
- },
- baseActions
- ),
- label: i18n.t("Rotate randomly 180"),
- shortcut: "",
- multiple: false,
- icon: rotateIcon,
- },
- rotate30: {
- action: (itemIds, _, baseActions) =>
- rotate(itemIds, { angle: 30 }, baseActions),
- label: i18n.t("Rotate 30"),
- shortcut: "r",
- icon: rotateIcon,
- },
- rotate45: {
- action: (itemIds, _, baseActions) =>
- rotate(itemIds, { angle: 45 }, baseActions),
- label: i18n.t("Rotate 45"),
- shortcut: "r",
- icon: rotateIcon,
- },
- rotate60: {
- action: (itemIds, _, baseActions) =>
- rotate(itemIds, { angle: 60 }, baseActions),
- label: i18n.t("Rotate 60"),
- shortcut: "r",
- icon: rotateIcon,
- },
- rotate90: {
- action: (itemIds, _, baseActions) =>
- rotate(itemIds, { angle: 90 }, baseActions),
- label: i18n.t("Rotate 90"),
- shortcut: "r",
- icon: rotateIcon,
- },
- rotate180: {
- action: (itemIds, _, baseActions) =>
- rotate(itemIds, { angle: 180 }, baseActions),
- label: i18n.t("Rotate 180"),
- shortcut: "r",
- icon: rotateIcon,
- },
- clone: {
- action: cloneItem,
- label: i18n.t("Clone"),
- shortcut: "c",
- disableDblclick: true,
- edit: true,
- icon: duplicateIcon,
- },
- lock: {
- action: toggleLock,
- label: i18n.t("Unlock") + "/" + i18n.t("Lock"),
- disableDblclick: true,
- icon: lockIcon,
- },
- remove: {
- action: remove,
- label: i18n.t("Remove all"),
- shortcut: "Delete",
- edit: true,
- disableDblclick: true,
- icon: deleteIcon,
- },
- };
- export default actionMap;
|