diff --git a/backend/src/index.js b/backend/src/index.js index 3fab673d..7a577848 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -20,6 +20,7 @@ export const main = async ({ store, schedules, functions, hooks }) => { await store.createOrUpdateBox("room", { security: "public" }); await store.createOrUpdateBox("session", { security: "public" }); await store.createOrUpdateBox("user", { security: "private" }); + await store.createOrUpdateBox("files", { security: "private" }); // Add schedules schedules["daily"] = [deleteOldSession(store)]; diff --git a/package-lock.json b/package-lock.json index 1c931d6d..49a9d786 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "lodash.debounce": "^4.0.8", "marked": "^4.0.12", "memoizee": "^0.4.14", + "mime-types": "^2.1.35", "nanoid": "^3.3.0", "openvidu-browser": "^2.26.0", "p-limit": "^4.0.0", @@ -8011,7 +8012,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -8020,7 +8020,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "dependencies": { "mime-db": "1.52.0" }, @@ -16995,14 +16994,12 @@ "mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" }, "mime-types": { "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "requires": { "mime-db": "1.52.0" } diff --git a/package.json b/package.json index c3fc99f6..66abec78 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "lodash.debounce": "^4.0.8", "marked": "^4.0.12", "memoizee": "^0.4.14", + "mime-types": "^2.1.35", "nanoid": "^3.3.0", "openvidu-browser": "^2.26.0", "p-limit": "^4.0.0", diff --git a/src/gameComponents/AdvancedImage.jsx b/src/gameComponents/AdvancedImage/AdvancedImage.jsx similarity index 85% rename from src/gameComponents/AdvancedImage.jsx rename to src/gameComponents/AdvancedImage/AdvancedImage.jsx index 84a143bf..42f8627c 100644 --- a/src/gameComponents/AdvancedImage.jsx +++ b/src/gameComponents/AdvancedImage/AdvancedImage.jsx @@ -1,11 +1,11 @@ import React, { memo } from "react"; import { useUsers } from "react-sync-board"; import styled from "styled-components"; -import { media2Url } from "../mediaLibrary"; +import { media2Url } from "../../mediaLibrary"; import { FiEye } from "react-icons/fi"; -import Canvas from "./Canvas"; -import { getImage } from "../utils/image"; +import Canvas from "../Canvas"; +import { getImage } from "../../utils/image"; const UnflippedFor = styled.div` position: absolute; @@ -76,8 +76,6 @@ const AdvancedImage = ({ flipped && (!Array.isArray(unflippedFor) || !unflippedFor.includes(currentUser.uid)); - const flippable = Boolean(backUrl); - const canvasLayers = React.useMemo(() => { const result = []; if (flippedForMe) { @@ -108,16 +106,7 @@ const AdvancedImage = ({ }, [frontUrl, backUrl]); return ( - { - flippable && e.target.classList.add("hvr-curl-top-right"); - e.target.classList.add("hovered"); - }} - onMouseLeave={(e) => { - flippable && e.target.classList.remove("hvr-curl-top-right"); - e.target.classList.remove("hovered"); - }} - > + { return { @@ -158,6 +160,13 @@ const LayersForm = ({ value, onChange }) => { const ImageForm = ({ initialValues }) => { const { t } = useTranslation(); + const { + input: { onChange: onWidthChange }, + } = useField("width"); + const { + input: { onChange: onHeightChange }, + } = useField("height"); + return ( <> diff --git a/src/gameComponents/AdvancedImage/index.js b/src/gameComponents/AdvancedImage/index.js new file mode 100644 index 00000000..b5543626 --- /dev/null +++ b/src/gameComponents/AdvancedImage/index.js @@ -0,0 +1,50 @@ +import i18n from "../../i18n"; +import createItemTemplate from "../utils"; + +import AdvancedImage from "./AdvancedImage"; +import AdvancedImageFormFields from "./AdvancedImageFormFields"; + +const RectTemplate = createItemTemplate({ + type: "advancedImage", + component: AdvancedImage, + defaultActions: (item) => { + let actions = ["stack", "shuffle", "clone", "lock", "remove"]; + if (item.layers?.length) { + actions = ["nextImage"].concat(actions); + } + if (item.back) { + return ["flip", "flipSelf"].concat(actions); + } else { + return actions; + } + }, + availableActions: (item) => { + let actions = [ + "tap", + "rotate", + "randomlyRotate", + "stack", + "alignAsLine", + "alignAsSquare", + "shuffle", + "clone", + "lock", + "remove", + ]; + if (item.layers?.length) { + actions = ["prevImageForLayer", "nextImageForLayer", "rollLayer"].concat( + actions + ); + } + if (item.back) { + return ["flip", "flipSelf"].concat(actions); + } else { + return actions; + } + }, + form: AdvancedImageFormFields, + name: i18n.t("Advanced Image"), + template: { layers: [], front: "/default.png" }, +}); + +export default RectTemplate; diff --git a/src/gameComponents/Anchor.jsx b/src/gameComponents/Anchor/Anchor.jsx similarity index 94% rename from src/gameComponents/Anchor.jsx rename to src/gameComponents/Anchor/Anchor.jsx index 73adaf6f..96885715 100644 --- a/src/gameComponents/Anchor.jsx +++ b/src/gameComponents/Anchor/Anchor.jsx @@ -10,9 +10,9 @@ import { } from "react-sync-board"; import useAsyncEffect from "use-async-effect"; -import { getItemElement, fastItemIntersect } from "../utils"; -import useGameItemActions from "./useGameItemActions"; -import useGlobalConf from "../hooks/useGlobalConf"; +import { getItemElement, fastItemIntersect } from "../../utils"; +import useGameItemActions from "../useGameItemActions"; +import useGlobalConf from "../../hooks/useGlobalConf"; const StyledAnchor = styled.div` ${({ highlight, editMode, color }) => css` diff --git a/src/gameComponents/forms/AnchorFormFields.jsx b/src/gameComponents/Anchor/AnchorFormFields.jsx similarity index 100% rename from src/gameComponents/forms/AnchorFormFields.jsx rename to src/gameComponents/Anchor/AnchorFormFields.jsx diff --git a/src/gameComponents/Anchor/index.js b/src/gameComponents/Anchor/index.js new file mode 100644 index 00000000..09f47825 --- /dev/null +++ b/src/gameComponents/Anchor/index.js @@ -0,0 +1,19 @@ +import i18n from "../../i18n"; +import createItemTemplate from "../utils"; + +import Anchor from "./Anchor"; +import AnchorFormFields from "./AnchorFormFields"; + +const AnchorTemplate = createItemTemplate({ + type: "anchor", + component: Anchor, + defaultActions: ["clone", "lock", "remove"], + availableActions: ["clone", "lock", "remove"], + form: AnchorFormFields, + name: i18n.t("Anchor"), + template: {}, + resizeDirections: {}, + excludeFields: { rotation: true, family: true, grid: true }, +}); + +export default AnchorTemplate; diff --git a/src/gameComponents/Canvas.jsx b/src/gameComponents/Canvas.jsx index bdc1ae6f..0b0bd3d2 100644 --- a/src/gameComponents/Canvas.jsx +++ b/src/gameComponents/Canvas.jsx @@ -2,6 +2,7 @@ import React from "react"; import styled from "styled-components"; import { getImage } from "../utils/image"; +import { useAsync } from "@react-hookz/web/esm/useAsync"; const defaultSize = 50; @@ -105,9 +106,60 @@ const ImageWrapper = styled.div` align-items: center; `; +const Error = styled.div` + min-width: 50px; + min-height: 50px; + &:after { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + content: "\\274c"; + font-size: 25px; + color: #fff; + line-height: 50px; + text-align: center; + } +`; + +const Loading = styled.div` + background: transparent; + background: linear-gradient( + 90deg, + transparent 8%, + #f5f5f53d 18%, + transparent 33% + ); + background-size: 200% 100%; + animation: 2s shine linear infinite; + min-width: 50px; + min-height: 50px; +`; + const NoCanvas = ({ layers, width, height }) => { const [firstImage, ...rest] = layers; + const [state, actions] = useAsync(async () => { + await getImage(firstImage.url); + return true; + }, false); + + React.useEffect(() => { + if (firstImage.url) { + actions.reset(); + actions.execute(); + } + }, [actions, firstImage.url]); + + if (state.status === "error") { + return ; + } + + if (state.status === "loading") { + return ; + } + return (
{firstImage && ( diff --git a/src/gameComponents/CheckerBoard.jsx b/src/gameComponents/CheckerBoard/CheckerBoard.jsx similarity index 100% rename from src/gameComponents/CheckerBoard.jsx rename to src/gameComponents/CheckerBoard/CheckerBoard.jsx diff --git a/src/gameComponents/forms/CheckerBoardFormFields.jsx b/src/gameComponents/CheckerBoard/CheckerBoardFormFields.jsx similarity index 100% rename from src/gameComponents/forms/CheckerBoardFormFields.jsx rename to src/gameComponents/CheckerBoard/CheckerBoardFormFields.jsx diff --git a/src/gameComponents/CheckerBoard/index.js b/src/gameComponents/CheckerBoard/index.js new file mode 100644 index 00000000..76550330 --- /dev/null +++ b/src/gameComponents/CheckerBoard/index.js @@ -0,0 +1,25 @@ +import i18n from "../../i18n"; +import createItemTemplate from "../utils"; + +import CheckerBoard from "./CheckerBoard"; +import CheckerBoardFormFields from "./CheckerBoardFormFields"; + +const CheckerBoardTemplate = createItemTemplate({ + type: "checkerboard", + component: CheckerBoard, + defaultActions: ["clone", "lock", "remove"], + availableActions: ["clone", "lock", "remove"], + form: CheckerBoardFormFields, + name: i18n.t("Checkerboard"), + template: { + width: 50, + height: 50, + color: "#CCC", + alternateColor: "#888", + colCount: 3, + rowCount: 3, + layer: -1, + }, +}); + +export default CheckerBoardTemplate; diff --git a/src/gameComponents/Counter.jsx b/src/gameComponents/Counter/Counter.jsx similarity index 99% rename from src/gameComponents/Counter.jsx rename to src/gameComponents/Counter/Counter.jsx index 36ecebc8..d1d2d965 100644 --- a/src/gameComponents/Counter.jsx +++ b/src/gameComponents/Counter/Counter.jsx @@ -48,7 +48,7 @@ const Counter = ({ color = "#CCC", label = "", textColor = "#000", - fontSize = "22", + fontSize = 22, setState, }) => { const setValue = (e) => { diff --git a/src/gameComponents/forms/CounterFormFields.jsx b/src/gameComponents/Counter/CounterFormFields.jsx similarity index 100% rename from src/gameComponents/forms/CounterFormFields.jsx rename to src/gameComponents/Counter/CounterFormFields.jsx diff --git a/src/gameComponents/Counter/index.js b/src/gameComponents/Counter/index.js new file mode 100644 index 00000000..7019ee98 --- /dev/null +++ b/src/gameComponents/Counter/index.js @@ -0,0 +1,23 @@ +import i18n from "../../i18n"; +import createItemTemplate from "../utils"; + +import Component from "./Counter"; +import FormComponent from "./CounterFormFields"; + +const Template = createItemTemplate({ + type: "counter", + component: Component, + form: FormComponent, + defaultActions: ["prevImage", "nextImage", "clone", "lock", "remove"], + availableActions: ["prevImage", "nextImage", "clone", "lock", "remove"], + name: i18n.t("Counter"), + template: { + value: 0, + color: "#CCC", + textColor: "#000", + fontSize: "22", + }, + resizeDirections: {}, +}); + +export default Template; diff --git a/src/gameComponents/Cube.jsx b/src/gameComponents/Cube/Cube.jsx similarity index 100% rename from src/gameComponents/Cube.jsx rename to src/gameComponents/Cube/Cube.jsx diff --git a/src/gameComponents/forms/CubeFormFields.jsx b/src/gameComponents/Cube/CubeFormFields.jsx similarity index 100% rename from src/gameComponents/forms/CubeFormFields.jsx rename to src/gameComponents/Cube/CubeFormFields.jsx diff --git a/src/gameComponents/Cube/index.js b/src/gameComponents/Cube/index.js new file mode 100644 index 00000000..40f023ef --- /dev/null +++ b/src/gameComponents/Cube/index.js @@ -0,0 +1,27 @@ +import i18n from "../../i18n"; +import createItemTemplate, { sizeResize } from "../utils"; + +import Component from "./Cube"; +import FormComponent from "./CubeFormFields"; + +const Template = createItemTemplate({ + type: "cube", + component: Component, + form: FormComponent, + defaultActions: ["clone", "lock", "remove"], + availableActions: [ + "stack", + "alignAsLine", + "alignAsSquare", + "shuffle", + "clone", + "lock", + "remove", + ], + name: i18n.t("Cube"), + template: { size: 50, color: "#b3b3b3" }, + resize: sizeResize, + resizeDirections: { b: true }, +}); + +export default Template; diff --git a/src/gameComponents/Cylinder.jsx b/src/gameComponents/Cylinder/Cylinder.jsx similarity index 100% rename from src/gameComponents/Cylinder.jsx rename to src/gameComponents/Cylinder/Cylinder.jsx diff --git a/src/gameComponents/forms/CylinderFormFields.jsx b/src/gameComponents/Cylinder/CylinderFormFields.jsx similarity index 100% rename from src/gameComponents/forms/CylinderFormFields.jsx rename to src/gameComponents/Cylinder/CylinderFormFields.jsx diff --git a/src/gameComponents/Cylinder/index.js b/src/gameComponents/Cylinder/index.js new file mode 100644 index 00000000..19e5911a --- /dev/null +++ b/src/gameComponents/Cylinder/index.js @@ -0,0 +1,27 @@ +import i18n from "../../i18n"; +import createItemTemplate, { sizeResize } from "../utils"; + +import Component from "./Cylinder"; +import FormComponent from "./CylinderFormFields"; + +const Template = createItemTemplate({ + type: "cylinder", + component: Component, + form: FormComponent, + defaultActions: ["clone", "lock", "remove"], + availableActions: [ + "stack", + "alignAsLine", + "alignAsSquare", + "shuffle", + "clone", + "lock", + "remove", + ], + name: i18n.t("Cylinder"), + template: { size: 50, color: "#b3b3b3" }, + resize: sizeResize, + resizeDirections: { b: true }, +}); + +export default Template; diff --git a/src/gameComponents/Dice.jsx b/src/gameComponents/Die/Die.jsx similarity index 96% rename from src/gameComponents/Dice.jsx rename to src/gameComponents/Die/Die.jsx index 07ef7a2b..7e019425 100644 --- a/src/gameComponents/Dice.jsx +++ b/src/gameComponents/Die/Die.jsx @@ -2,7 +2,7 @@ import React, { memo } from "react"; import styled, { css } from "styled-components"; import { useTranslation } from "react-i18next"; -import useGameItemActions from "./useGameItemActions"; +import useGameItemActions from "../useGameItemActions"; const DicePane = styled.div` ${({ color }) => css` diff --git a/src/gameComponents/forms/DiceFormFields.jsx b/src/gameComponents/Die/DieFormFields.jsx similarity index 100% rename from src/gameComponents/forms/DiceFormFields.jsx rename to src/gameComponents/Die/DieFormFields.jsx diff --git a/src/gameComponents/Die/index.js b/src/gameComponents/Die/index.js new file mode 100644 index 00000000..f79c6258 --- /dev/null +++ b/src/gameComponents/Die/index.js @@ -0,0 +1,25 @@ +import i18n from "../../i18n"; +import createItemTemplate from "../utils"; + +import Component from "./Die"; +import FormComponent from "./DieFormFields"; + +const Template = createItemTemplate({ + type: "dice", + component: Component, + form: FormComponent, + defaultActions: ["roll", "clone", "lock", "remove"], + availableActions: [ + "roll", + "clone", + "lock", + "remove", + "alignAsLine", + "alignAsSquare", + ], + name: i18n.t("Die"), + template: { value: 0, color: "#CCC", textColor: "#fff", fontSize: "35" }, + resizeDirections: {}, +}); + +export default Template; diff --git a/src/gameComponents/DiceImage.jsx b/src/gameComponents/DieImage/DieImage.jsx similarity index 83% rename from src/gameComponents/DiceImage.jsx rename to src/gameComponents/DieImage/DieImage.jsx index cf12fe90..b3dbc4ac 100644 --- a/src/gameComponents/DiceImage.jsx +++ b/src/gameComponents/DieImage/DieImage.jsx @@ -1,9 +1,8 @@ import React, { memo } from "react"; -import styled from "styled-components"; import { useItemInteraction } from "react-sync-board"; -import { media2Url } from "../mediaLibrary"; -import useGameItemActions from "./useGameItemActions"; -import Canvas from "./Canvas"; +import { media2Url } from "../../mediaLibrary"; +import useGameItemActions from "../useGameItemActions"; +import Canvas from "../Canvas"; const Dice = ({ id, diff --git a/src/gameComponents/forms/DiceImageFormFields.jsx b/src/gameComponents/DieImage/DieImageFormFields.jsx similarity index 97% rename from src/gameComponents/forms/DiceImageFormFields.jsx rename to src/gameComponents/DieImage/DieImageFormFields.jsx index 45183a59..3d814d0a 100644 --- a/src/gameComponents/forms/DiceImageFormFields.jsx +++ b/src/gameComponents/DieImage/DieImageFormFields.jsx @@ -1,12 +1,12 @@ import React from "react"; import { useTranslation } from "react-i18next"; import { Field } from "react-final-form"; +import { useItemActions } from "react-sync-board"; import Label from "../../ui/formUtils/Label"; import Hint from "../../ui/formUtils/Hint"; import { ImageField } from "../../mediaLibrary"; -import { useItemActions } from "react-sync-board"; -import { nanoid } from "nanoid"; +import { uid } from "../../utils"; const Form = ({ initialValues }) => { const { t } = useTranslation(); @@ -23,7 +23,7 @@ const Form = ({ initialValues }) => { batchUpdateItems([initialValues.id], (prevItem) => { const newItem = { ...prevItem }; newItem.images = [...newItem.images]; - newItem.images.push({ id: nanoid(), type: "empty" }); + newItem.images.push({ id: uid(), type: "empty" }); return newItem; }); } diff --git a/src/gameComponents/DieImage/index.js b/src/gameComponents/DieImage/index.js new file mode 100644 index 00000000..6c34da59 --- /dev/null +++ b/src/gameComponents/DieImage/index.js @@ -0,0 +1,66 @@ +import i18n from "../../i18n"; +import { uid } from "../../utils"; +import createItemTemplate from "../utils"; + +import Component from "./DieImage"; +import FormComponent from "./DieImageFormFields"; + +const defaultDiceImages = () => [ + { + id: uid(), + type: "external", + content: "/game_assets/dice/one.svg", + }, + { + id: uid(), + type: "external", + content: "/game_assets/dice/two.svg", + }, + { + id: uid(), + type: "external", + content: "/game_assets/dice/three.svg", + }, + { + id: uid(), + type: "external", + content: "/game_assets/dice/four.svg", + }, + { + id: uid(), + type: "external", + content: "/game_assets/dice/five.svg", + }, + { + id: uid(), + type: "external", + content: "/game_assets/dice/six.svg", + }, +]; + +const Template = createItemTemplate({ + type: "diceImage", + component: Component, + form: FormComponent, + defaultActions: ["roll", "prevImage", "nextImage", "clone", "lock", "remove"], + availableActions: [ + "roll", + "previous", + "next", + "clone", + "lock", + "remove", + "alignAsLine", + "alignAsSquare", + ], + name: i18n.t("Image die"), + template: () => ({ + value: 0, + images: defaultDiceImages(), + side: 6, + width: 50, + height: 50, + }), +}); + +export default Template; diff --git a/src/gameComponents/Generator.jsx b/src/gameComponents/Generator/Generator.jsx similarity index 98% rename from src/gameComponents/Generator.jsx rename to src/gameComponents/Generator/Generator.jsx index 573bc421..983f1095 100644 --- a/src/gameComponents/Generator.jsx +++ b/src/gameComponents/Generator/Generator.jsx @@ -2,12 +2,12 @@ import React, { memo } from "react"; import styled, { css } from "styled-components"; import { useItemActions, useItemInteraction, useUsers } from "react-sync-board"; import { FiMove } from "react-icons/fi"; - -import { uid, getItemElement } from "../utils"; -import itemTemplates from "./itemTemplates"; import { useTranslation } from "react-i18next"; import debounce from "lodash.debounce"; +import { uid, getItemElement } from "../../utils"; +import itemTemplates from "../itemTemplates"; + const StyledShape = styled.div` ${({ color }) => css` box-shadow: rgba(60, 64, 67, 0.3) 0px 1px 2px 0px, diff --git a/src/gameComponents/forms/GeneratorFormFields.jsx b/src/gameComponents/Generator/GeneratorFormFields.jsx similarity index 100% rename from src/gameComponents/forms/GeneratorFormFields.jsx rename to src/gameComponents/Generator/GeneratorFormFields.jsx diff --git a/src/gameComponents/Generator/index.js b/src/gameComponents/Generator/index.js new file mode 100644 index 00000000..225fa623 --- /dev/null +++ b/src/gameComponents/Generator/index.js @@ -0,0 +1,19 @@ +import i18n from "../../i18n"; +import createItemTemplate from "../utils"; + +import Component from "./Generator"; +import FormComponent from "./GeneratorFormFields"; + +const Template = createItemTemplate({ + type: "generator", + component: Component, + form: FormComponent, + defaultActions: ["clone", "lock", "remove"], + availableActions: ["clone", "lock", "remove"], + excludeFields: { rotation: true }, + name: i18n.t("Generator"), + template: { layer: 0, color: "#ccc" }, + resizeDirections: {}, +}); + +export default Template; diff --git a/src/gameComponents/Hexagon.jsx b/src/gameComponents/Hexagon/Hexagon.jsx similarity index 97% rename from src/gameComponents/Hexagon.jsx rename to src/gameComponents/Hexagon/Hexagon.jsx index 62f093eb..3fd4e19a 100644 --- a/src/gameComponents/Hexagon.jsx +++ b/src/gameComponents/Hexagon/Hexagon.jsx @@ -2,7 +2,7 @@ import React, { memo } from "react"; import styled, { css } from "styled-components"; const StyledHexagon = styled.div` - ${({ size = 50, vertical }) => css` + ${({ size, vertical }) => css` width: ${vertical ? 0.866025 * size : size}px; height: ${vertical ? size : 0.866025 * size}px; display: flex; @@ -28,7 +28,7 @@ const StyledHexagon = styled.div` `; const Hexagon = ({ - size, + size = 50, color = "#ccc", flippedColor = "#ccc", flipped = false, diff --git a/src/gameComponents/forms/HexagonFormFields.jsx b/src/gameComponents/Hexagon/HexagonFormFields.jsx similarity index 100% rename from src/gameComponents/forms/HexagonFormFields.jsx rename to src/gameComponents/Hexagon/HexagonFormFields.jsx diff --git a/src/gameComponents/Hexagon/index.js b/src/gameComponents/Hexagon/index.js new file mode 100644 index 00000000..d1cfb12c --- /dev/null +++ b/src/gameComponents/Hexagon/index.js @@ -0,0 +1,34 @@ +import i18n from "../../i18n"; +import createItemTemplate, { sizeResize } from "../utils"; + +import Component from "./Hexagon"; +import FormComponent from "./HexagonFormFields"; + +const Template = createItemTemplate({ + type: "hexagon", + component: Component, + form: FormComponent, + defaultActions: ["clone", "lock", "remove"], + availableActions: [ + "flip", + "stack", + "alignAsLine", + "alignAsSquare", + "shuffle", + "clone", + "lock", + "remove", + ], + name: i18n.t("Hexagon"), + template: { + size: 50, + color: "#ccc", + flippedColor: "#ccc", + textColor: "#000", + fontSize: "16", + }, + resize: sizeResize, + resizeDirections: { b: true }, +}); + +export default Template; diff --git a/src/gameComponents/Image.jsx b/src/gameComponents/Image/Image.jsx similarity index 86% rename from src/gameComponents/Image.jsx rename to src/gameComponents/Image/Image.jsx index 822901a8..32bbe84c 100644 --- a/src/gameComponents/Image.jsx +++ b/src/gameComponents/Image/Image.jsx @@ -1,11 +1,11 @@ import React, { memo } from "react"; import { useUsers } from "react-sync-board"; import styled from "styled-components"; -import { media2Url } from "../mediaLibrary"; - import { FiEye } from "react-icons/fi"; -import Canvas from "./Canvas"; -import { getImage } from "../utils/image"; + +import Canvas from "../Canvas"; +import { media2Url } from "../../mediaLibrary"; +import { getImage } from "../../utils/image"; const UnflippedFor = styled.div` position: absolute; @@ -91,8 +91,6 @@ const Image = ({ flipped && (!Array.isArray(unflippedFor) || !unflippedFor.includes(currentUser.uid)); - const flippable = Boolean(backContent); - const layers = React.useMemo(() => { const result = []; if (!flippedForMe) { @@ -115,16 +113,7 @@ const Image = ({ }, [imageContent, backContent]); return ( - { - flippable && e.target.classList.add("hvr-curl-top-right"); - e.target.classList.add("hovered"); - }} - onMouseLeave={(e) => { - flippable && e.target.classList.remove("hvr-curl-top-right"); - e.target.classList.remove("hovered"); - }} - > + {flippedForMe && backText && } diff --git a/src/gameComponents/forms/ImageFormFields.jsx b/src/gameComponents/Image/ImageFormFields.jsx similarity index 70% rename from src/gameComponents/forms/ImageFormFields.jsx rename to src/gameComponents/Image/ImageFormFields.jsx index c4b6e2b3..aed8a126 100644 --- a/src/gameComponents/forms/ImageFormFields.jsx +++ b/src/gameComponents/Image/ImageFormFields.jsx @@ -1,13 +1,23 @@ import React from "react"; import { Field } from "react-final-form"; import { useTranslation } from "react-i18next"; +import { useField } from "react-final-form"; + +import { getImage } from "../../utils/image"; import Label from "../../ui/formUtils/Label"; -import { ImageField } from "../../mediaLibrary"; +import { ImageField, media2Url } from "../../mediaLibrary"; const ImageForm = ({ initialValues }) => { const { t } = useTranslation(); + const { + input: { onChange: onWidthChange }, + } = useField("width"); + const { + input: { onChange: onHeightChange }, + } = useField("height"); + return ( <> diff --git a/src/gameComponents/Image/index.js b/src/gameComponents/Image/index.js new file mode 100644 index 00000000..d5ec27e5 --- /dev/null +++ b/src/gameComponents/Image/index.js @@ -0,0 +1,72 @@ +import i18n from "../../i18n"; +import createItemTemplate from "../utils"; + +import Component from "./Image"; +import FormComponent from "./ImageFormFields"; + +const Template = createItemTemplate({ + type: "image", + component: Component, + form: FormComponent, + defaultActions: (item) => { + if (item.backContent) { + return [ + "flip", + "flipSelf", + "tap", + "stack", + "shuffle", + "clone", + "lock", + "remove", + ]; + } else { + return ["tap", "stack", "shuffle", "clone", "lock", "remove"]; + } + }, + availableActions: (item) => { + if (item.backContent) { + return [ + "flip", + "flipSelf", + "tap", + "rotate", + "randomlyRotate", + "stack", + "alignAsLine", + "alignAsSquare", + "shuffle", + "clone", + "lock", + "remove", + ]; + } else { + return [ + "tap", + "rotate", + "randomlyRotate", + "stack", + "alignAsLine", + "alignAsSquare", + "shuffle", + "clone", + "lock", + "remove", + ]; + } + }, + name: i18n.t("Image"), + template: {}, + async mapMedia(item, fn) { + const result = await Promise.all([ + fn(item.content), + fn(item.backContent), + fn(item.overlay?.content), + ]); + item.content = result[0]; + item.backContent = result[1]; + item.overlay = { content: result[2] }; + }, +}); + +export default Template; diff --git a/src/gameComponents/ItemForm.jsx b/src/gameComponents/ItemForm.jsx index 296ba82c..17d4d4d3 100644 --- a/src/gameComponents/ItemForm.jsx +++ b/src/gameComponents/ItemForm.jsx @@ -105,6 +105,19 @@ const ItemForm = ({ items, types, extraExcludeFields }) => { ); }, [items]); + const { + locked = false, + rotation = 0, + layer = 0, + groupId = "", + grid: { + type: gridType = "", + size: gridSize = 1, + offset: { x: gridOffsetX = 0, gridOffsetY = 0 } = {}, + } = {}, + actions = [], + } = initialValues; + return ( <> {!excludeFields.locked && ( @@ -113,7 +126,7 @@ const ItemForm = ({ items, types, extraExcludeFields }) => { name="locked" component="input" type="checkbox" - initialValue={initialValues.locked} + initialValue={locked} /> {t("Locked?")} {t("Lock action help")} @@ -122,7 +135,7 @@ const ItemForm = ({ items, types, extraExcludeFields }) => { {!excludeFields.rotation && (