Kaynağa Gözat

Add action edition

Jeremie Pardou-Piquemal 3 yıl önce
ebeveyn
işleme
2163184367

+ 2 - 1
.eslintrc

@@ -27,7 +27,8 @@
       }
     ],
     "react/prop-types": "off",
-    "jsx-a11y/anchor-is-valid": "off"
+    "jsx-a11y/anchor-is-valid": "off",
+    "no-unused-vars": "warn"
   },
   "plugins": ["react-hooks"],
   "extends": [

+ 0 - 9
src/App.test.js

@@ -1,9 +0,0 @@
-import React from "react";
-import { render } from "@testing-library/react";
-import App from "./App";
-
-test("renders learn react link", () => {
-  const { getByText } = render(<App />);
-  const linkElement = getByText(/learn react/i);
-  expect(linkElement).toBeInTheDocument();
-});

+ 46 - 0
src/components/boardComponents/ActionsField.js

@@ -0,0 +1,46 @@
+import React from "react";
+
+import Label from "../../ui/formUtils/Label";
+
+import useItemActions from "./useItemActions";
+import { Field } from "react-final-form";
+
+const ActionsField = ({
+  value: globalValue,
+  onChange: globalOnChange,
+  availableActions,
+}) => {
+  const { actionMap } = useItemActions();
+  return (
+    <div>
+      {availableActions.map((action) => {
+        const { label } = actionMap[action];
+        return (
+          <Label key={action}>
+            <Field value={globalValue.includes(action)} type="checkbox">
+              {({ input: { value } }) => {
+                const toggleAction = (e) => {
+                  if (e.target.checked) {
+                    globalOnChange([...globalValue, action]);
+                  } else {
+                    globalOnChange(globalValue.filter((act) => act !== action));
+                  }
+                };
+                return (
+                  <input
+                    type="checkbox"
+                    checked={value}
+                    onChange={toggleAction}
+                  />
+                );
+              }}
+            </Field>
+            <span className="checkable">{label}</span>
+          </Label>
+        );
+      })}
+    </div>
+  );
+};
+
+export default ActionsField;

+ 21 - 3
src/components/boardComponents/ItemFormFactory.js

@@ -11,7 +11,13 @@ import { ItemMapAtom } from "../Board/";
 
 import Label from "../../ui/formUtils/Label";
 
-import { getFormFieldComponent } from ".";
+import {
+  getFormFieldComponent,
+  getDefaultActionsFromItem,
+  getAvailableActionsFromItem,
+} from ".";
+
+import ActionsField from "./ActionsField";
 
 import Slider from "rc-slider";
 import "rc-slider/assets/index.css";
@@ -21,6 +27,8 @@ const ItemFormFactory = ({ itemId, onSubmitHandler }) => {
 
   const itemMap = useRecoilValue(ItemMapAtom);
   const item = itemMap[itemId];
+  const [defaultActions] = React.useState(getDefaultActionsFromItem(item));
+  const [availableActions] = React.useState(getAvailableActionsFromItem(item));
 
   if (!item) return null;
 
@@ -40,7 +48,6 @@ const ItemFormFactory = ({ itemId, onSubmitHandler }) => {
           <div style={{ display: "none" }}>
             <Field name="id" component="input" initialValue={item.id} />
           </div>
-
           <Label>
             <Field
               name="locked"
@@ -103,8 +110,19 @@ const ItemFormFactory = ({ itemId, onSubmitHandler }) => {
               }}
             </Field>
           </Label>
-
           <FieldsComponent initialValues={item} />
+          <h3>{t("Available actions")}</h3>
+          <Label>
+            <Field name="actions" initialValue={item.actions || defaultActions}>
+              {({ input: { onChange, value } }) => (
+                <ActionsField
+                  onChange={onChange}
+                  value={value}
+                  availableActions={availableActions}
+                />
+              )}
+            </Field>
+          </Label>
         </div>
       )}
     />

+ 63 - 1
src/components/boardComponents/index.js

@@ -28,6 +28,7 @@ export const itemMap = {
   rect: {
     component: Rect,
     defaultActions: ["lock", "remove"],
+    availableActions: ["stack", "shuffle", "clone", "lock", "remove"],
     form: RectFormFields,
     label: i18n.t("Rectangle"),
     template: {},
@@ -35,6 +36,7 @@ export const itemMap = {
   cube: {
     component: Cube,
     defaultActions: ["clone", "lock", "remove"],
+    availableActions: ["stack", "shuffle", "clone", "lock", "remove"],
     form: CubeFormFields,
     label: i18n.t("Cube"),
     template: {},
@@ -42,6 +44,7 @@ export const itemMap = {
   round: {
     component: Round,
     defaultActions: ["clone", "lock", "remove"],
+    availableActions: ["stack", "shuffle", "clone", "lock", "remove"],
     form: RoundFormFields,
     label: i18n.t("Round"),
     template: {},
@@ -49,6 +52,7 @@ export const itemMap = {
   token: {
     component: Token,
     defaultActions: ["clone", "lock", "remove"],
+    availableActions: ["stack", "shuffle", "clone", "lock", "remove"],
     form: TokenFormFields,
     label: i18n.t("Token"),
     template: {},
@@ -56,6 +60,7 @@ export const itemMap = {
   meeple: {
     component: Meeple,
     defaultActions: ["clone", "lock", "remove"],
+    availableActions: ["stack", "shuffle", "clone", "lock", "remove"],
     form: MeepleFormFields,
     label: i18n.t("Meeple"),
     template: {},
@@ -63,6 +68,7 @@ export const itemMap = {
   jewel: {
     component: Jewel,
     defaultActions: ["clone", "lock", "remove"],
+    availableActions: ["stack", "shuffle", "clone", "lock", "remove"],
     form: JewelFormFields,
     label: i18n.t("Jewel"),
     template: {},
@@ -85,6 +91,37 @@ export const itemMap = {
         return ["tap", "stack", "shuffle", "clone", "lock", "remove"];
       }
     },
+    availableActions: (item) => {
+      if (item.backContent) {
+        return [
+          "flip",
+          "flipSelf",
+          "tap",
+          "rotate30",
+          "rotate45",
+          "rotate60",
+          "rotate90",
+          "stack",
+          "shuffle",
+          "clone",
+          "lock",
+          "remove",
+        ];
+      } else {
+        return [
+          "tap",
+          "rotate30",
+          "rotate45",
+          "rotate60",
+          "rotate90",
+          "stack",
+          "shuffle",
+          "clone",
+          "lock",
+          "remove",
+        ];
+      }
+    },
     form: ImageFormFields,
     label: i18n.t("Image"),
     template: {},
@@ -92,6 +129,7 @@ export const itemMap = {
   counter: {
     component: Counter,
     defaultActions: ["clone", "lock", "remove"],
+    availableActions: ["clone", "lock", "remove"],
     form: CounterFormFields,
     label: i18n.t("Counter"),
     template: {},
@@ -99,13 +137,15 @@ export const itemMap = {
   dice: {
     component: Dice,
     defaultActions: ["clone", "lock", "remove"],
+    availableActions: ["clone", "lock", "remove"],
     form: DiceFormFields,
     label: i18n.t("Dice"),
     template: {},
   },
   note: {
     component: Note,
-    defaultActions: ["clone", "lock", "remove"],
+    defaultActions: ["shuffle", "clone", "lock", "remove"],
+    availableActions: ["shuffle", "clone", "lock", "remove"],
     form: NoteFormFields,
     label: i18n.t("Note"),
     template: {},
@@ -113,6 +153,7 @@ export const itemMap = {
   zone: {
     component: Zone,
     defaultActions: ["clone", "lock", "remove"],
+    availableActions: ["clone", "lock", "remove"],
     form: ZoneFormFields,
     label: i18n.t("Zone"),
     template: {},
@@ -141,5 +182,26 @@ export const getDefaultActionsFromItem = (item) => {
     }
     return actions;
   }
+
+  return [];
+};
+
+export const getAvailableActionsFromItem = (item) => {
+  if (item.type in itemMap) {
+    const actions = itemMap[item.type].availableActions;
+    if (typeof actions === "function") {
+      return actions(item);
+    }
+    return actions;
+  }
+
   return [];
 };
+
+export const getActionsFromItem = (item) => {
+  const { actions = getDefaultActionsFromItem(item) } = item;
+  // Filter availableActions to keep same order
+  return getAvailableActionsFromItem(item).filter((action) =>
+    actions.includes(action)
+  );
+};

+ 27 - 32
src/components/boardComponents/useItemActions.js

@@ -8,7 +8,7 @@ import { useUsers } from "../users";
 
 import intersection from "lodash.intersection";
 import { ItemMapAtom } from "../Board";
-import { getDefaultActionsFromItem } from "./";
+import { getActionsFromItem } from "./";
 
 import { useTranslation } from "react-i18next";
 import { nanoid } from "nanoid";
@@ -25,12 +25,6 @@ import rotateIcon from "../../images/rotate.svg";
 import shuffleIcon from "../../images/shuffle.svg";
 import tapIcon from "../../images/tap.svg";
 
-const getActionsFromItem = (item) => {
-  const defaultActions = getDefaultActionsFromItem(item);
-  const { actions = [] } = item;
-  return defaultActions.concat(actions);
-};
-
 export const useItemActions = () => {
   const {
     batchUpdateItems,
@@ -70,11 +64,12 @@ export const useItemActions = () => {
       if (selectedItems.length > 0) {
         // Prevent set state on unmounted component
         if (!isMountedRef.current) return;
-        setAvailableActions(
-          selectedItemList.reduce((acc, item) => {
-            return intersection(acc, getActionsFromItem(item));
-          }, getActionsFromItem(selectedItemList[0]))
-        );
+
+        const allActions = selectedItemList.reduce((acc, item) => {
+          return intersection(acc, getActionsFromItem(item));
+        }, getActionsFromItem(selectedItemList[0]));
+
+        setAvailableActions(allActions);
       } else {
         setAvailableActions([]);
       }
@@ -268,26 +263,6 @@ export const useItemActions = () => {
         shortcut: "t",
         icon: tapIcon,
       },
-      rotate90: {
-        action: rotate.bind(null, 90),
-        label: t("Rotate 90"),
-        icon: rotateIcon,
-      },
-      rotate60: {
-        action: rotate.bind(null, 60),
-        label: t("Rotate 60"),
-        icon: rotateIcon,
-      },
-      rotate45: {
-        action: rotate.bind(null, 45),
-        label: t("Rotate 45"),
-        icon: rotateIcon,
-      },
-      rotate30: {
-        action: rotate.bind(null, 30),
-        label: t("Rotate 30"),
-        icon: rotateIcon,
-      },
       stack: {
         action: align,
         label: t("Stack"),
@@ -302,6 +277,26 @@ export const useItemActions = () => {
         multiple: true,
         icon: shuffleIcon,
       },
+      rotate30: {
+        action: rotate.bind(null, 30),
+        label: t("Rotate 30"),
+        icon: rotateIcon,
+      },
+      rotate45: {
+        action: rotate.bind(null, 45),
+        label: t("Rotate 45"),
+        icon: rotateIcon,
+      },
+      rotate60: {
+        action: rotate.bind(null, 60),
+        label: t("Rotate 60"),
+        icon: rotateIcon,
+      },
+      rotate90: {
+        action: rotate.bind(null, 90),
+        label: t("Rotate 90"),
+        icon: rotateIcon,
+      },
       clone: {
         action: cloneItem,
         label: t("Clone"),

+ 12 - 1
src/games/testGame.js

@@ -17,7 +17,18 @@ const genGame = () => {
     width: 100,
     x: 400,
     y: 400,
-    actions: ["rotate90", "rotate45"],
+    actions: [
+      "flip",
+      "flipSelf",
+      "tap",
+      "rotate45",
+      "rotate90",
+      "stack",
+      "shuffle",
+      "clone",
+      "lock",
+      "remove",
+    ],
   });
 
   items.push({