Browse Source

Update react-syncboard with infinite board

Jeremie Pardou-Piquemal 2 years ago
parent
commit
32376cee5a

+ 11 - 5
cypress/integration/board.spec.js

@@ -14,7 +14,7 @@ describe("Board interactions", () => {
     cy.get(".board-pane", { timeout: 10000 }).should(
       "have.css",
       "transform",
-      "matrix(0.48, 0, 0, 0.48, 260, 60)"
+      "matrix(0.48, 0, 0, 0.48, -11693.9, -11917.9)"
     );
     cy.get(".item")
       .first()
@@ -52,7 +52,7 @@ describe("Board interactions", () => {
     cy.get(".board-pane").should(
       "have.css",
       "transform",
-      "matrix(0.48, 0, 0, 0.48, 360, 160)"
+      "matrix(0.48, 0, 0, 0.48, -11593.9, -11817.9)"
     );
   });
 
@@ -81,12 +81,14 @@ describe("Board interactions", () => {
     cy.get(".board-pane").should(
       "have.css",
       "transform",
-      "matrix(0.48, 0, 0, 0.48, 360, 160)"
+      "matrix(0.48, 0, 0, 0.48, -11593.9, -11817.9)"
     );
   });
 
   it("Pan board with left click when is main action", () => {
-    cy.get("[title^='Switch to move mode']").click();
+    cy.get("[title^='Switch to move mode']").click({
+      scrollBehavior: false,
+    });
 
     cy.get(".board")
       .trigger("pointerdown", {
@@ -96,6 +98,8 @@ describe("Board interactions", () => {
         clientX: 150,
         clientY: 200,
         pointerId: 1,
+        force: true,
+        scrollBehavior: false,
       })
       .trigger("pointermove", {
         button: 0,
@@ -105,16 +109,18 @@ describe("Board interactions", () => {
         clientY: 400,
         pointerId: 1,
         force: true,
+        scrollBehavior: false,
       })
       .trigger("pointerup", {
         force: true,
         pointerId: 1,
         isPrimary: true,
+        scrollBehavior: false,
       });
     cy.get(".board-pane").should(
       "have.css",
       "transform",
-      "matrix(0.48, 0, 0, 0.48, 260, 60)"
+      "matrix(0.48, 0, 0, 0.48, -11443.9, -11717.9)"
     );
   });
 });

+ 3 - 5
cypress/integration/item.spec.js

@@ -15,7 +15,7 @@ describe("Item interactions", () => {
     cy.get(".board-pane", { timeout: 10000 }).should(
       "have.css",
       "transform",
-      "matrix(0.48, 0, 0, 0.48, 260, 60)"
+      "matrix(0.48, 0, 0, 0.48, -11693.9, -11917.9)"
     );
     cy.get(".item")
       .first()
@@ -111,13 +111,11 @@ describe("Item interactions", () => {
 
   it("should hide menu", () => {
     // Select card
-    cy.get("img[src='/game_assets/JC.jpg']")
-      .parents(".item")
-      .click(500, 500, { force: true });
+    cy.get("img[src='/game_assets/JC.jpg']").click(500, 500, { force: true });
 
     cy.get("img[alt='Edit']").should("exist");
 
-    cy.get("img[alt='Hide menu']").click();
+    cy.get("img[alt='Hide menu']").click({ scrollBehavior: false });
 
     cy.get("img[alt='Edit']").should("not.exist");
   });

+ 1 - 1
cypress/integration/message.spec.js

@@ -14,7 +14,7 @@ describe("Messages interactions", () => {
     cy.get(".board-pane", { timeout: 10000 }).should(
       "have.css",
       "transform",
-      "matrix(0.48, 0, 0, 0.48, 260, 60)"
+      "matrix(0.48, 0, 0, 0.48, -11693.9, -11917.9)"
     );
   });
 

+ 13 - 24
cypress/integration/selection.spec.js

@@ -14,7 +14,7 @@ describe("Selection action", () => {
     cy.get(".board-pane", { timeout: 10000 }).should(
       "have.css",
       "transform",
-      "matrix(0.48, 0, 0, 0.48, 260, 60)"
+      "matrix(0.48, 0, 0, 0.48, -11693.9, -11917.9)"
     );
     cy.get(".item")
       .first()
@@ -24,46 +24,35 @@ describe("Selection action", () => {
   });
 
   it("should select multiple items with left click ", () => {
+    const posInit = {
+      clientX: 400,
+      clientY: 210,
+    };
     cy.get(".board")
       .trigger("pointerdown", {
-        x: 400,
-        y: 400,
         button: 0,
-        clientX: 400,
-        clientY: 400,
         pointerId: 1,
         isPrimary: true,
+        scrollBehavior: false,
+        force: true,
+        ...posInit,
       })
       .trigger("pointermove", {
-        x: 700,
-        y: 150,
         button: 0,
-        clientX: 700,
-        clientY: 150,
         force: true,
         pointerId: 1,
         isPrimary: true,
+        scrollBehavior: false,
+        clientX: posInit.clientX + 300,
+        clientY: posInit.clientY + 250,
       });
 
-    cy.get(".selector").should(
+    cy.get(".selection").should(
       "have.css",
       "transform",
-      "matrix(1, 0, 0, 1, 291.667, 312.5)"
+      "matrix(1, 0, 0, 1, 498.08, 274.08)"
     );
 
-    cy.get(".board").trigger("pointermove", {
-      x: 601,
-      y: 151,
-      button: 0,
-      clientX: 601,
-      clientY: 151,
-      pointerId: 1,
-      isPrimary: true,
-      force: true,
-    });
-
-    cy.wait(500);
-
     cy.get(".board").trigger("pointerup", {
       isPrimary: true,
       force: true,

+ 1 - 1
cypress/integration/session.spec.js

@@ -18,7 +18,7 @@ describe("Session management", () => {
     cy.get(".board-pane", { timeout: 10000 }).should(
       "have.css",
       "transform",
-      "matrix(0.48, 0, 0, 0.48, 260, 60)"
+      "matrix(0.48, 0, 0, 0.48, -11693.9, -11917.9)"
     );
     cy.get(".item")
       .first()

+ 5 - 5
cypress/integration/studio.spec.js

@@ -36,7 +36,7 @@ describe("Studio", () => {
     cy.get(".board-pane").should(
       "have.css",
       "transform",
-      "matrix(0.24, 0, 0, 0.24, 260, 60)"
+      "matrix(0.48, 0, 0, 0.48, -11500, -11700)"
     );
   });
 
@@ -45,7 +45,7 @@ describe("Studio", () => {
     cy.get(".board-pane").should(
       "have.css",
       "transform",
-      "matrix(0.24, 0, 0, 0.24, 260, 60)"
+      "matrix(0.48, 0, 0, 0.48, -11500, -11700)"
     );
 
     // save
@@ -70,7 +70,7 @@ describe("Studio", () => {
     cy.get(".board-pane").should(
       "have.css",
       "transform",
-      "matrix(0.24, 0, 0, 0.24, 260, 60)"
+      "matrix(0.48, 0, 0, 0.48, -11500, -11700)"
     );
     // Add an item
     cy.get("[title^='Add an item']").click({ force: true });
@@ -100,7 +100,7 @@ describe("Studio", () => {
     cy.get(".board-pane").should(
       "have.css",
       "transform",
-      "matrix(0.24, 0, 0, 0.24, 260, 60)"
+      "matrix(0.48, 0, 0, 0.48, -11500, -11700)"
     );
 
     // Edit title
@@ -132,7 +132,7 @@ describe("Studio", () => {
       cy.get(".board-pane").should(
         "have.css",
         "transform",
-        "matrix(0.24, 0, 0, 0.24, 260, 60)"
+        "matrix(0.48, 0, 0, 0.48, -11500, -11700)"
       );
       // Add an item
       cy.get("[title^='Add an item']").click({ force: true });

+ 195 - 337
package-lock.json

@@ -20,9 +20,9 @@
         "i18next": "^19.4.5",
         "i18next-browser-languagedetector": "^4.3.1",
         "lodash.debounce": "^4.0.8",
-        "marked": "^2.0.0",
+        "marked": "^4.0.12",
         "memoizee": "^0.4.14",
-        "nanoid": "^3.1.29",
+        "nanoid": "^3.3.0",
         "openvidu-browser": "^2.17.0",
         "randomcolor": "^0.5.4",
         "rc-slider": "^9.7.2",
@@ -37,7 +37,7 @@
         "react-query": "^3.13.4",
         "react-router": "^5.2.0",
         "react-router-dom": "^5.2.0",
-        "react-sync-board": "^0.3.4",
+        "react-sync-board": "^0.4.4",
         "react-toastify": "^6.1.0",
         "react-useportal": "^1.0.14",
         "recoil": "^0.3.1",
@@ -72,6 +72,72 @@
         "wait-on": "^5.3.0"
       }
     },
+    "../react-syncboard": {
+      "name": "react-sync-board",
+      "version": "0.4.2",
+      "extraneous": true,
+      "license": "MIT",
+      "dependencies": {
+        "@emotion/react": "^11.4.0",
+        "@emotion/styled": "^11.3.0",
+        "@react-hookz/web": "^12.3.0",
+        "color2k": "^1.2.4",
+        "dayjs": "^1.10.5",
+        "fast-deep-equal": "^3.1.3",
+        "lodash.findlast": "^4.6.0",
+        "nanoid": "^3.1.23",
+        "randomcolor": "^0.6.2"
+      },
+      "devDependencies": {
+        "@babel/core": "^7.14.6",
+        "@babel/preset-env": "^7.14.7",
+        "@babel/preset-react": "^7.14.5",
+        "@rollup/plugin-alias": "^3.1.2",
+        "@rollup/plugin-babel": "^5.3.0",
+        "@rollup/plugin-commonjs": "^19.0.0",
+        "@rollup/plugin-image": "^2.0.6",
+        "@rollup/plugin-node-resolve": "^13.0.0",
+        "@scripters/use-socket.io": "git+https://github.com/jrmi/use-socket.io.git#updated",
+        "@storybook/addon-actions": "^6.4.17",
+        "@storybook/addon-essentials": "^6.4.17",
+        "@storybook/addon-links": "^6.4.17",
+        "@storybook/react": "^6.4.17",
+        "babel-eslint": "^10.1.0",
+        "babel-jest": "^26.3.0",
+        "babel-loader": "^8.1.0",
+        "coveralls": "^3.1.0",
+        "eslint": "^7.9.0",
+        "eslint-config-airbnb-base": "^14.2.0",
+        "eslint-config-prettier": "^8.3.0",
+        "eslint-import-resolver-alias": "^1.1.2",
+        "eslint-plugin-import": "^2.23.4",
+        "eslint-plugin-prettier": "^3.4.0",
+        "eslint-plugin-react": "^7.24.0",
+        "eslint-plugin-react-hooks": "^4.2.0",
+        "eslint-plugin-storybook": "^0.5.6",
+        "final-form": "^4.20.2",
+        "jest": "^27.0.5",
+        "postcss-preset-env": "^6.7.0",
+        "rc-slider": "^9.7.2",
+        "react": "^17.0.0",
+        "react-color": "^2.19.3",
+        "react-dom": "^17.0.0",
+        "react-final-form": "^6.5.3",
+        "react-useportal": "^1.0.14",
+        "recoil": "^0.3.1",
+        "rollup": "^2.52.7",
+        "rollup-plugin-analyzer": "^4.0.0",
+        "rollup-plugin-css-only": "^3.1.0",
+        "rollup-plugin-terser": "^7.0.2",
+        "styled-components": "^5.3.0",
+        "wire.io": "^3.1.2"
+      },
+      "peerDependencies": {
+        "react": ">= 17",
+        "react-dom": ">= 17",
+        "recoil": ">= 0.3.1"
+      }
+    },
     "node_modules/@babel/cli": {
       "version": "7.14.3",
       "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.14.3.tgz",
@@ -1015,10 +1081,12 @@
       }
     },
     "node_modules/@babel/helper-plugin-utils": {
-      "version": "7.13.0",
-      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz",
-      "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==",
-      "dev": true
+      "version": "7.16.7",
+      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz",
+      "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==",
+      "engines": {
+        "node": ">=6.9.0"
+      }
     },
     "node_modules/@babel/helper-remap-async-to-generator": {
       "version": "7.13.0",
@@ -1834,11 +1902,11 @@
       }
     },
     "node_modules/@babel/plugin-syntax-jsx": {
-      "version": "7.16.0",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.0.tgz",
-      "integrity": "sha512-8zv2+xiPHwly31RK4RmnEYY5zziuF3O7W2kIDW+07ewWDh6Oi0dRq8kwvulRkFgt6DB97RlKs5c1y068iPlCUg==",
+      "version": "7.16.7",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz",
+      "integrity": "sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q==",
       "dependencies": {
-        "@babel/helper-plugin-utils": "^7.14.5"
+        "@babel/helper-plugin-utils": "^7.16.7"
       },
       "engines": {
         "node": ">=6.9.0"
@@ -1847,14 +1915,6 @@
         "@babel/core": "^7.0.0-0"
       }
     },
-    "node_modules/@babel/plugin-syntax-jsx/node_modules/@babel/helper-plugin-utils": {
-      "version": "7.14.5",
-      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz",
-      "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==",
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
     "node_modules/@babel/plugin-syntax-logical-assignment-operators": {
       "version": "7.10.4",
       "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz",
@@ -2468,12 +2528,6 @@
         "@babel/core": "^7.0.0-0"
       }
     },
-    "node_modules/@babel/plugin-transform-react-jsx-self/node_modules/@babel/helper-plugin-utils": {
-      "version": "7.13.0",
-      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz",
-      "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==",
-      "dev": true
-    },
     "node_modules/@babel/plugin-transform-react-jsx-source": {
       "version": "7.14.2",
       "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.14.2.tgz",
@@ -2486,12 +2540,6 @@
         "@babel/core": "^7.0.0-0"
       }
     },
-    "node_modules/@babel/plugin-transform-react-jsx-source/node_modules/@babel/helper-plugin-utils": {
-      "version": "7.13.0",
-      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz",
-      "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==",
-      "dev": true
-    },
     "node_modules/@babel/plugin-transform-regenerator": {
       "version": "7.13.15",
       "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.13.15.tgz",
@@ -2735,11 +2783,14 @@
       }
     },
     "node_modules/@babel/runtime": {
-      "version": "7.12.1",
-      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.1.tgz",
-      "integrity": "sha512-J5AIf3vPj3UwXaAzb5j1xM4WAQDX3EMgemF8rjCP3SoW09LfRKAXQKt6CoVYl230P6iWdRcBbnLDDdnqWxZSCA==",
+      "version": "7.17.2",
+      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz",
+      "integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==",
       "dependencies": {
         "regenerator-runtime": "^0.13.4"
+      },
+      "engines": {
+        "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/runtime-corejs3": {
@@ -3051,9 +3102,9 @@
       "dev": true
     },
     "node_modules/@emotion/babel-plugin": {
-      "version": "11.3.0",
-      "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.3.0.tgz",
-      "integrity": "sha512-UZKwBV2rADuhRp+ZOGgNWg2eYgbzKzQXfQPtJbu/PLy8onurxlNCLvxMQEvlr1/GudguPI5IU9qIY1+2z1M5bA==",
+      "version": "11.7.2",
+      "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.7.2.tgz",
+      "integrity": "sha512-6mGSCWi9UzXut/ZAN6lGFu33wGR3SJisNl3c0tvlmb8XChH1b2SUvxvnOh7hvLpqyRdHHU9AiazV3Cwbk5SXKQ==",
       "dependencies": {
         "@babel/helper-module-imports": "^7.12.13",
         "@babel/plugin-syntax-jsx": "^7.12.13",
@@ -3066,23 +3117,12 @@
         "escape-string-regexp": "^4.0.0",
         "find-root": "^1.1.0",
         "source-map": "^0.5.7",
-        "stylis": "^4.0.3"
+        "stylis": "4.0.13"
       },
       "peerDependencies": {
         "@babel/core": "^7.0.0"
       }
     },
-    "node_modules/@emotion/babel-plugin/node_modules/@babel/runtime": {
-      "version": "7.16.3",
-      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz",
-      "integrity": "sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==",
-      "dependencies": {
-        "regenerator-runtime": "^0.13.4"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
     "node_modules/@emotion/babel-plugin/node_modules/@emotion/memoize": {
       "version": "0.7.5",
       "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.5.tgz",
@@ -3100,15 +3140,15 @@
       }
     },
     "node_modules/@emotion/cache": {
-      "version": "11.6.0",
-      "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.6.0.tgz",
-      "integrity": "sha512-ElbsWY1KMwEowkv42vGo0UPuLgtPYfIs9BxxVrmvsaJVvktknsHYYlx5NQ5g6zLDcOTyamlDc7FkRg2TAcQDKQ==",
+      "version": "11.7.1",
+      "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.7.1.tgz",
+      "integrity": "sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A==",
       "dependencies": {
         "@emotion/memoize": "^0.7.4",
         "@emotion/sheet": "^1.1.0",
         "@emotion/utils": "^1.0.0",
         "@emotion/weak-memoize": "^0.2.5",
-        "stylis": "^4.0.10"
+        "stylis": "4.0.13"
       }
     },
     "node_modules/@emotion/hash": {
@@ -3130,12 +3170,12 @@
       "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw=="
     },
     "node_modules/@emotion/react": {
-      "version": "11.6.0",
-      "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.6.0.tgz",
-      "integrity": "sha512-23MnRZFBN9+D1lHXC5pD6z4X9yhPxxtHr6f+iTGz6Fv6Rda0GdefPrsHL7otsEf+//7uqCdT5QtHeRxHCERzuw==",
+      "version": "11.7.1",
+      "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.7.1.tgz",
+      "integrity": "sha512-DV2Xe3yhkF1yT4uAUoJcYL1AmrnO5SVsdfvu+fBuS7IbByDeTVx9+wFmvx9Idzv7/78+9Mgx2Hcmr7Fex3tIyw==",
       "dependencies": {
         "@babel/runtime": "^7.13.10",
-        "@emotion/cache": "^11.6.0",
+        "@emotion/cache": "^11.7.1",
         "@emotion/serialize": "^1.0.2",
         "@emotion/sheet": "^1.1.0",
         "@emotion/utils": "^1.0.0",
@@ -3155,17 +3195,6 @@
         }
       }
     },
-    "node_modules/@emotion/react/node_modules/@babel/runtime": {
-      "version": "7.16.3",
-      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz",
-      "integrity": "sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==",
-      "dependencies": {
-        "regenerator-runtime": "^0.13.4"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
     "node_modules/@emotion/serialize": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.2.tgz",
@@ -3208,17 +3237,6 @@
         }
       }
     },
-    "node_modules/@emotion/styled/node_modules/@babel/runtime": {
-      "version": "7.16.3",
-      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz",
-      "integrity": "sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==",
-      "dependencies": {
-        "regenerator-runtime": "^0.13.4"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
     "node_modules/@emotion/styled/node_modules/@emotion/is-prop-valid": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.1.1.tgz",
@@ -3445,6 +3463,29 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/@react-hookz/deep-equal": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@react-hookz/deep-equal/-/deep-equal-1.0.1.tgz",
+      "integrity": "sha512-6k/pU2jNlgYvKOy84vpCAZ8MGVwybvAdjzrh4UicCVOCPxz0LSBws1OE6O5TO4sPHaSw+yLfNzNK8RicGFc1Kw=="
+    },
+    "node_modules/@react-hookz/web": {
+      "version": "12.3.0",
+      "resolved": "https://registry.npmjs.org/@react-hookz/web/-/web-12.3.0.tgz",
+      "integrity": "sha512-Uujp2RfX/oDEtAbdkV0W/nhiQ5ZYVH+MEq04T7/8TstyiwoIlvsRbEic6c6gQA6sQ/lw93cZ8NP8R7AYjiqDvg==",
+      "dependencies": {
+        "@react-hookz/deep-equal": "^1.0.1"
+      },
+      "peerDependencies": {
+        "js-cookie": "^3.0.1",
+        "react": "^16.8 || ^17 || ^18",
+        "react-dom": "^16.8 || ^17 || ^18"
+      },
+      "peerDependenciesMeta": {
+        "js-cookie": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@samverschueren/stream-to-observable": {
       "version": "0.3.1",
       "resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.1.tgz",
@@ -3535,15 +3576,6 @@
         "node": ">=10"
       }
     },
-    "node_modules/@testing-library/dom/node_modules/@babel/runtime": {
-      "version": "7.14.0",
-      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.0.tgz",
-      "integrity": "sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA==",
-      "dev": true,
-      "dependencies": {
-        "regenerator-runtime": "^0.13.4"
-      }
-    },
     "node_modules/@testing-library/dom/node_modules/ansi-styles": {
       "version": "4.3.0",
       "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -3719,15 +3751,6 @@
         "react-dom": "*"
       }
     },
-    "node_modules/@testing-library/react/node_modules/@babel/runtime": {
-      "version": "7.14.0",
-      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.0.tgz",
-      "integrity": "sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA==",
-      "dev": true,
-      "dependencies": {
-        "regenerator-runtime": "^0.13.4"
-      }
-    },
     "node_modules/@testing-library/user-event": {
       "version": "13.1.9",
       "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.1.9.tgz",
@@ -3744,15 +3767,6 @@
         "@testing-library/dom": ">=7.21.4"
       }
     },
-    "node_modules/@testing-library/user-event/node_modules/@babel/runtime": {
-      "version": "7.14.0",
-      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.0.tgz",
-      "integrity": "sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA==",
-      "dev": true,
-      "dependencies": {
-        "regenerator-runtime": "^0.13.4"
-      }
-    },
     "node_modules/@types/aria-query": {
       "version": "4.2.1",
       "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.1.tgz",
@@ -5500,9 +5514,9 @@
       "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
     },
     "node_modules/color2k": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/color2k/-/color2k-1.1.0.tgz",
-      "integrity": "sha512-8N4IsYKavkGJTwiiwT7CWi9PRnzr2fPHRi4TKMJ/xo2cMdogq+sjbN645Zc/N3bWy+nQPcNxcbsO/v/mf8sSzw=="
+      "version": "1.2.5",
+      "resolved": "https://registry.npmjs.org/color2k/-/color2k-1.2.5.tgz",
+      "integrity": "sha512-G39qNMGyM/fhl8hcy1YqpfXzQ810zSGyiJAgdMFlreCI7Hpwu3Jpu4tuBM/Oxu1Bek1FwyaBbtrtdkTr4HDhLA=="
     },
     "node_modules/colorette": {
       "version": "1.2.2",
@@ -6209,9 +6223,9 @@
       "dev": true
     },
     "node_modules/dayjs": {
-      "version": "1.10.4",
-      "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.4.tgz",
-      "integrity": "sha512-RI/Hh4kqRc1UKLOAf/T5zdMMX5DQIlDxwUe3wSyMMnEbGunnpENCdbUgM+dW7kXidZqCttBrmw7BhN4TMddkCw=="
+      "version": "1.10.7",
+      "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.7.tgz",
+      "integrity": "sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig=="
     },
     "node_modules/debug": {
       "version": "4.3.2",
@@ -9373,11 +9387,6 @@
       "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=",
       "dev": true
     },
-    "node_modules/lodash.throttle": {
-      "version": "4.1.1",
-      "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
-      "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ="
-    },
     "node_modules/lodash.truncate": {
       "version": "4.4.2",
       "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz",
@@ -9595,14 +9604,14 @@
       }
     },
     "node_modules/marked": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/marked/-/marked-2.0.0.tgz",
-      "integrity": "sha512-NqRSh2+LlN2NInpqTQnS614Y/3NkVMFFU6sJlRFEpxJ/LHuK/qJECH7/fXZjk4VZstPW/Pevjil/VtSONsLc7Q==",
+      "version": "4.0.12",
+      "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.12.tgz",
+      "integrity": "sha512-hgibXWrEDNBWgGiK18j/4lkS6ihTe9sxtV4Q1OQppb/0zzyPSzoFANBa5MfsG/zgsWklmNnhm0XACZOH/0HBiQ==",
       "bin": {
-        "marked": "bin/marked"
+        "marked": "bin/marked.js"
       },
       "engines": {
-        "node": ">= 8.16.2"
+        "node": ">= 12"
       }
     },
     "node_modules/match-sorter": {
@@ -9614,14 +9623,6 @@
         "remove-accents": "0.4.2"
       }
     },
-    "node_modules/match-sorter/node_modules/@babel/runtime": {
-      "version": "7.13.10",
-      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz",
-      "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==",
-      "dependencies": {
-        "regenerator-runtime": "^0.13.4"
-      }
-    },
     "node_modules/material-colors": {
       "version": "1.2.6",
       "resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz",
@@ -9800,9 +9801,9 @@
       }
     },
     "node_modules/nanoid": {
-      "version": "3.1.29",
-      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.29.tgz",
-      "integrity": "sha512-dW2pUSGZ8ZnCFIlBIA31SV8huOGCHb6OwzVCc7A69rb/a+SgPBwfmLvK5TKQ3INPbRkcI8a/Owo0XbiTNH19wg==",
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.0.tgz",
+      "integrity": "sha512-JzxqqT5u/x+/KOFSd7JP15DOo9nOoHpx6DYatqIHUW2+flybkm+mdcraotSQR5WcnZr+qhGVh8Ted0KdfSMxlg==",
       "bin": {
         "nanoid": "bin/nanoid.cjs"
       },
@@ -11050,18 +11051,6 @@
       "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz",
       "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ=="
     },
-    "node_modules/postcss/node_modules/nanoid": {
-      "version": "3.1.23",
-      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz",
-      "integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==",
-      "dev": true,
-      "bin": {
-        "nanoid": "bin/nanoid.cjs"
-      },
-      "engines": {
-        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
-      }
-    },
     "node_modules/prelude-ls": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -11345,14 +11334,6 @@
         "react-dom": ">=16.9.0"
       }
     },
-    "node_modules/rc-util/node_modules/@babel/runtime": {
-      "version": "7.14.0",
-      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.0.tgz",
-      "integrity": "sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA==",
-      "dependencies": {
-        "regenerator-runtime": "^0.13.4"
-      }
-    },
     "node_modules/react": {
       "version": "17.0.1",
       "resolved": "https://registry.npmjs.org/react/-/react-17.0.1.tgz",
@@ -11540,18 +11521,17 @@
       }
     },
     "node_modules/react-sync-board": {
-      "version": "0.3.4",
-      "resolved": "https://registry.npmjs.org/react-sync-board/-/react-sync-board-0.3.4.tgz",
-      "integrity": "sha512-IxrzlL4edJiuZwrTTzbNc/q7Z7RxKuFLQZK559NgyxucpNT9TVCBdmk8MzywY0A8jJWOvfKbaKpdbjH8TiUTAA==",
+      "version": "0.4.4",
+      "resolved": "https://registry.npmjs.org/react-sync-board/-/react-sync-board-0.4.4.tgz",
+      "integrity": "sha512-jwxwn33lB8EmiKPAIG4XlsKKFhPD0WjVZI1SeUBlmWuvNmGxt1UFUCl5dLmtaVLmzeArNTBVrb9EP52WO/Ci7A==",
       "dependencies": {
         "@emotion/react": "^11.4.0",
         "@emotion/styled": "^11.3.0",
+        "@react-hookz/web": "^12.3.0",
         "color2k": "^1.2.4",
         "dayjs": "^1.10.5",
         "fast-deep-equal": "^3.1.3",
-        "lodash.debounce": "^4.0.8",
         "lodash.findlast": "^4.6.0",
-        "lodash.throttle": "^4.1.1",
         "nanoid": "^3.1.23",
         "randomcolor": "^0.6.2"
       },
@@ -11561,16 +11541,6 @@
         "recoil": ">= 0.3.1"
       }
     },
-    "node_modules/react-sync-board/node_modules/color2k": {
-      "version": "1.2.4",
-      "resolved": "https://registry.npmjs.org/color2k/-/color2k-1.2.4.tgz",
-      "integrity": "sha512-DiwdBwc0BryPFFXoCrW8XQGXl2rEtMToODybxZjKnN5IJXt/tV/FsN02pCK/b7KcWvJEioz3c74lQSmayFvS4Q=="
-    },
-    "node_modules/react-sync-board/node_modules/dayjs": {
-      "version": "1.10.7",
-      "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.7.tgz",
-      "integrity": "sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig=="
-    },
     "node_modules/react-sync-board/node_modules/randomcolor": {
       "version": "0.6.2",
       "resolved": "https://registry.npmjs.org/randomcolor/-/randomcolor-0.6.2.tgz",
@@ -12861,9 +12831,9 @@
       }
     },
     "node_modules/stylis": {
-      "version": "4.0.10",
-      "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.10.tgz",
-      "integrity": "sha512-m3k+dk7QeJw660eIKRRn3xPF6uuvHs/FFzjX3HQ5ove0qYsiygoAhwn5a3IYKaZPo5LrYD0rfVmtv1gNY1uYwg=="
+      "version": "4.0.13",
+      "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz",
+      "integrity": "sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag=="
     },
     "node_modules/supports-color": {
       "version": "5.5.0",
@@ -14679,10 +14649,9 @@
       }
     },
     "@babel/helper-plugin-utils": {
-      "version": "7.13.0",
-      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz",
-      "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==",
-      "dev": true
+      "version": "7.16.7",
+      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz",
+      "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA=="
     },
     "@babel/helper-remap-async-to-generator": {
       "version": "7.13.0",
@@ -15413,18 +15382,11 @@
       }
     },
     "@babel/plugin-syntax-jsx": {
-      "version": "7.16.0",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.0.tgz",
-      "integrity": "sha512-8zv2+xiPHwly31RK4RmnEYY5zziuF3O7W2kIDW+07ewWDh6Oi0dRq8kwvulRkFgt6DB97RlKs5c1y068iPlCUg==",
+      "version": "7.16.7",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz",
+      "integrity": "sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q==",
       "requires": {
-        "@babel/helper-plugin-utils": "^7.14.5"
-      },
-      "dependencies": {
-        "@babel/helper-plugin-utils": {
-          "version": "7.14.5",
-          "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz",
-          "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ=="
-        }
+        "@babel/helper-plugin-utils": "^7.16.7"
       }
     },
     "@babel/plugin-syntax-logical-assignment-operators": {
@@ -15938,14 +15900,6 @@
       "dev": true,
       "requires": {
         "@babel/helper-plugin-utils": "^7.12.13"
-      },
-      "dependencies": {
-        "@babel/helper-plugin-utils": {
-          "version": "7.13.0",
-          "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz",
-          "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==",
-          "dev": true
-        }
       }
     },
     "@babel/plugin-transform-react-jsx-source": {
@@ -15955,14 +15909,6 @@
       "dev": true,
       "requires": {
         "@babel/helper-plugin-utils": "^7.13.0"
-      },
-      "dependencies": {
-        "@babel/helper-plugin-utils": {
-          "version": "7.13.0",
-          "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz",
-          "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==",
-          "dev": true
-        }
       }
     },
     "@babel/plugin-transform-regenerator": {
@@ -16174,9 +16120,9 @@
       }
     },
     "@babel/runtime": {
-      "version": "7.12.1",
-      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.1.tgz",
-      "integrity": "sha512-J5AIf3vPj3UwXaAzb5j1xM4WAQDX3EMgemF8rjCP3SoW09LfRKAXQKt6CoVYl230P6iWdRcBbnLDDdnqWxZSCA==",
+      "version": "7.17.2",
+      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz",
+      "integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==",
       "requires": {
         "regenerator-runtime": "^0.13.4"
       }
@@ -16462,9 +16408,9 @@
       }
     },
     "@emotion/babel-plugin": {
-      "version": "11.3.0",
-      "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.3.0.tgz",
-      "integrity": "sha512-UZKwBV2rADuhRp+ZOGgNWg2eYgbzKzQXfQPtJbu/PLy8onurxlNCLvxMQEvlr1/GudguPI5IU9qIY1+2z1M5bA==",
+      "version": "11.7.2",
+      "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.7.2.tgz",
+      "integrity": "sha512-6mGSCWi9UzXut/ZAN6lGFu33wGR3SJisNl3c0tvlmb8XChH1b2SUvxvnOh7hvLpqyRdHHU9AiazV3Cwbk5SXKQ==",
       "requires": {
         "@babel/helper-module-imports": "^7.12.13",
         "@babel/plugin-syntax-jsx": "^7.12.13",
@@ -16477,17 +16423,9 @@
         "escape-string-regexp": "^4.0.0",
         "find-root": "^1.1.0",
         "source-map": "^0.5.7",
-        "stylis": "^4.0.3"
+        "stylis": "4.0.13"
       },
       "dependencies": {
-        "@babel/runtime": {
-          "version": "7.16.3",
-          "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz",
-          "integrity": "sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==",
-          "requires": {
-            "regenerator-runtime": "^0.13.4"
-          }
-        },
         "@emotion/memoize": {
           "version": "0.7.5",
           "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.5.tgz",
@@ -16501,15 +16439,15 @@
       }
     },
     "@emotion/cache": {
-      "version": "11.6.0",
-      "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.6.0.tgz",
-      "integrity": "sha512-ElbsWY1KMwEowkv42vGo0UPuLgtPYfIs9BxxVrmvsaJVvktknsHYYlx5NQ5g6zLDcOTyamlDc7FkRg2TAcQDKQ==",
+      "version": "11.7.1",
+      "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.7.1.tgz",
+      "integrity": "sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A==",
       "requires": {
         "@emotion/memoize": "^0.7.4",
         "@emotion/sheet": "^1.1.0",
         "@emotion/utils": "^1.0.0",
         "@emotion/weak-memoize": "^0.2.5",
-        "stylis": "^4.0.10"
+        "stylis": "4.0.13"
       }
     },
     "@emotion/hash": {
@@ -16531,27 +16469,17 @@
       "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw=="
     },
     "@emotion/react": {
-      "version": "11.6.0",
-      "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.6.0.tgz",
-      "integrity": "sha512-23MnRZFBN9+D1lHXC5pD6z4X9yhPxxtHr6f+iTGz6Fv6Rda0GdefPrsHL7otsEf+//7uqCdT5QtHeRxHCERzuw==",
+      "version": "11.7.1",
+      "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.7.1.tgz",
+      "integrity": "sha512-DV2Xe3yhkF1yT4uAUoJcYL1AmrnO5SVsdfvu+fBuS7IbByDeTVx9+wFmvx9Idzv7/78+9Mgx2Hcmr7Fex3tIyw==",
       "requires": {
         "@babel/runtime": "^7.13.10",
-        "@emotion/cache": "^11.6.0",
+        "@emotion/cache": "^11.7.1",
         "@emotion/serialize": "^1.0.2",
         "@emotion/sheet": "^1.1.0",
         "@emotion/utils": "^1.0.0",
         "@emotion/weak-memoize": "^0.2.5",
         "hoist-non-react-statics": "^3.3.1"
-      },
-      "dependencies": {
-        "@babel/runtime": {
-          "version": "7.16.3",
-          "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz",
-          "integrity": "sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==",
-          "requires": {
-            "regenerator-runtime": "^0.13.4"
-          }
-        }
       }
     },
     "@emotion/serialize": {
@@ -16583,14 +16511,6 @@
         "@emotion/utils": "^1.0.0"
       },
       "dependencies": {
-        "@babel/runtime": {
-          "version": "7.16.3",
-          "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz",
-          "integrity": "sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==",
-          "requires": {
-            "regenerator-runtime": "^0.13.4"
-          }
-        },
         "@emotion/is-prop-valid": {
           "version": "1.1.1",
           "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.1.1.tgz",
@@ -16786,6 +16706,19 @@
         }
       }
     },
+    "@react-hookz/deep-equal": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@react-hookz/deep-equal/-/deep-equal-1.0.1.tgz",
+      "integrity": "sha512-6k/pU2jNlgYvKOy84vpCAZ8MGVwybvAdjzrh4UicCVOCPxz0LSBws1OE6O5TO4sPHaSw+yLfNzNK8RicGFc1Kw=="
+    },
+    "@react-hookz/web": {
+      "version": "12.3.0",
+      "resolved": "https://registry.npmjs.org/@react-hookz/web/-/web-12.3.0.tgz",
+      "integrity": "sha512-Uujp2RfX/oDEtAbdkV0W/nhiQ5ZYVH+MEq04T7/8TstyiwoIlvsRbEic6c6gQA6sQ/lw93cZ8NP8R7AYjiqDvg==",
+      "requires": {
+        "@react-hookz/deep-equal": "^1.0.1"
+      }
+    },
     "@samverschueren/stream-to-observable": {
       "version": "0.3.1",
       "resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.1.tgz",
@@ -16856,15 +16789,6 @@
         "pretty-format": "^26.6.2"
       },
       "dependencies": {
-        "@babel/runtime": {
-          "version": "7.14.0",
-          "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.0.tgz",
-          "integrity": "sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA==",
-          "dev": true,
-          "requires": {
-            "regenerator-runtime": "^0.13.4"
-          }
-        },
         "ansi-styles": {
           "version": "4.3.0",
           "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -16991,17 +16915,6 @@
       "requires": {
         "@babel/runtime": "^7.12.5",
         "@testing-library/dom": "^7.28.1"
-      },
-      "dependencies": {
-        "@babel/runtime": {
-          "version": "7.14.0",
-          "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.0.tgz",
-          "integrity": "sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA==",
-          "dev": true,
-          "requires": {
-            "regenerator-runtime": "^0.13.4"
-          }
-        }
       }
     },
     "@testing-library/user-event": {
@@ -17011,17 +16924,6 @@
       "dev": true,
       "requires": {
         "@babel/runtime": "^7.12.5"
-      },
-      "dependencies": {
-        "@babel/runtime": {
-          "version": "7.14.0",
-          "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.0.tgz",
-          "integrity": "sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA==",
-          "dev": true,
-          "requires": {
-            "regenerator-runtime": "^0.13.4"
-          }
-        }
       }
     },
     "@types/aria-query": {
@@ -18412,9 +18314,9 @@
       "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
     },
     "color2k": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/color2k/-/color2k-1.1.0.tgz",
-      "integrity": "sha512-8N4IsYKavkGJTwiiwT7CWi9PRnzr2fPHRi4TKMJ/xo2cMdogq+sjbN645Zc/N3bWy+nQPcNxcbsO/v/mf8sSzw=="
+      "version": "1.2.5",
+      "resolved": "https://registry.npmjs.org/color2k/-/color2k-1.2.5.tgz",
+      "integrity": "sha512-G39qNMGyM/fhl8hcy1YqpfXzQ810zSGyiJAgdMFlreCI7Hpwu3Jpu4tuBM/Oxu1Bek1FwyaBbtrtdkTr4HDhLA=="
     },
     "colorette": {
       "version": "1.2.2",
@@ -18958,9 +18860,9 @@
       "dev": true
     },
     "dayjs": {
-      "version": "1.10.4",
-      "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.4.tgz",
-      "integrity": "sha512-RI/Hh4kqRc1UKLOAf/T5zdMMX5DQIlDxwUe3wSyMMnEbGunnpENCdbUgM+dW7kXidZqCttBrmw7BhN4TMddkCw=="
+      "version": "1.10.7",
+      "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.7.tgz",
+      "integrity": "sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig=="
     },
     "debug": {
       "version": "4.3.2",
@@ -21433,11 +21335,6 @@
       "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=",
       "dev": true
     },
-    "lodash.throttle": {
-      "version": "4.1.1",
-      "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
-      "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ="
-    },
     "lodash.truncate": {
       "version": "4.4.2",
       "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz",
@@ -21605,9 +21502,9 @@
       }
     },
     "marked": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/marked/-/marked-2.0.0.tgz",
-      "integrity": "sha512-NqRSh2+LlN2NInpqTQnS614Y/3NkVMFFU6sJlRFEpxJ/LHuK/qJECH7/fXZjk4VZstPW/Pevjil/VtSONsLc7Q=="
+      "version": "4.0.12",
+      "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.12.tgz",
+      "integrity": "sha512-hgibXWrEDNBWgGiK18j/4lkS6ihTe9sxtV4Q1OQppb/0zzyPSzoFANBa5MfsG/zgsWklmNnhm0XACZOH/0HBiQ=="
     },
     "match-sorter": {
       "version": "6.3.0",
@@ -21616,16 +21513,6 @@
       "requires": {
         "@babel/runtime": "^7.12.5",
         "remove-accents": "0.4.2"
-      },
-      "dependencies": {
-        "@babel/runtime": {
-          "version": "7.13.10",
-          "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz",
-          "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==",
-          "requires": {
-            "regenerator-runtime": "^0.13.4"
-          }
-        }
       }
     },
     "material-colors": {
@@ -21777,9 +21664,9 @@
       }
     },
     "nanoid": {
-      "version": "3.1.29",
-      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.29.tgz",
-      "integrity": "sha512-dW2pUSGZ8ZnCFIlBIA31SV8huOGCHb6OwzVCc7A69rb/a+SgPBwfmLvK5TKQ3INPbRkcI8a/Owo0XbiTNH19wg=="
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.0.tgz",
+      "integrity": "sha512-JzxqqT5u/x+/KOFSd7JP15DOo9nOoHpx6DYatqIHUW2+flybkm+mdcraotSQR5WcnZr+qhGVh8Ted0KdfSMxlg=="
     },
     "nanomatch": {
       "version": "1.2.13",
@@ -22668,14 +22555,6 @@
         "colorette": "^1.2.2",
         "nanoid": "^3.1.23",
         "source-map-js": "^0.6.2"
-      },
-      "dependencies": {
-        "nanoid": {
-          "version": "3.1.23",
-          "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz",
-          "integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==",
-          "dev": true
-        }
       }
     },
     "postcss-value-parser": {
@@ -22901,16 +22780,6 @@
         "@babel/runtime": "^7.12.5",
         "react-is": "^16.12.0",
         "shallowequal": "^1.1.0"
-      },
-      "dependencies": {
-        "@babel/runtime": {
-          "version": "7.14.0",
-          "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.0.tgz",
-          "integrity": "sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA==",
-          "requires": {
-            "regenerator-runtime": "^0.13.4"
-          }
-        }
       }
     },
     "react": {
@@ -23040,32 +22909,21 @@
       }
     },
     "react-sync-board": {
-      "version": "0.3.4",
-      "resolved": "https://registry.npmjs.org/react-sync-board/-/react-sync-board-0.3.4.tgz",
-      "integrity": "sha512-IxrzlL4edJiuZwrTTzbNc/q7Z7RxKuFLQZK559NgyxucpNT9TVCBdmk8MzywY0A8jJWOvfKbaKpdbjH8TiUTAA==",
+      "version": "0.4.4",
+      "resolved": "https://registry.npmjs.org/react-sync-board/-/react-sync-board-0.4.4.tgz",
+      "integrity": "sha512-jwxwn33lB8EmiKPAIG4XlsKKFhPD0WjVZI1SeUBlmWuvNmGxt1UFUCl5dLmtaVLmzeArNTBVrb9EP52WO/Ci7A==",
       "requires": {
         "@emotion/react": "^11.4.0",
         "@emotion/styled": "^11.3.0",
+        "@react-hookz/web": "^12.3.0",
         "color2k": "^1.2.4",
         "dayjs": "^1.10.5",
         "fast-deep-equal": "^3.1.3",
-        "lodash.debounce": "^4.0.8",
         "lodash.findlast": "^4.6.0",
-        "lodash.throttle": "^4.1.1",
         "nanoid": "^3.1.23",
         "randomcolor": "^0.6.2"
       },
       "dependencies": {
-        "color2k": {
-          "version": "1.2.4",
-          "resolved": "https://registry.npmjs.org/color2k/-/color2k-1.2.4.tgz",
-          "integrity": "sha512-DiwdBwc0BryPFFXoCrW8XQGXl2rEtMToODybxZjKnN5IJXt/tV/FsN02pCK/b7KcWvJEioz3c74lQSmayFvS4Q=="
-        },
-        "dayjs": {
-          "version": "1.10.7",
-          "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.7.tgz",
-          "integrity": "sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig=="
-        },
         "randomcolor": {
           "version": "0.6.2",
           "resolved": "https://registry.npmjs.org/randomcolor/-/randomcolor-0.6.2.tgz",
@@ -24077,9 +23935,9 @@
       }
     },
     "stylis": {
-      "version": "4.0.10",
-      "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.10.tgz",
-      "integrity": "sha512-m3k+dk7QeJw660eIKRRn3xPF6uuvHs/FFzjX3HQ5ove0qYsiygoAhwn5a3IYKaZPo5LrYD0rfVmtv1gNY1uYwg=="
+      "version": "4.0.13",
+      "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz",
+      "integrity": "sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag=="
     },
     "supports-color": {
       "version": "5.5.0",

+ 3 - 3
package.json

@@ -15,9 +15,9 @@
     "i18next": "^19.4.5",
     "i18next-browser-languagedetector": "^4.3.1",
     "lodash.debounce": "^4.0.8",
-    "marked": "^2.0.0",
+    "marked": "^4.0.12",
     "memoizee": "^0.4.14",
-    "nanoid": "^3.1.29",
+    "nanoid": "^3.3.0",
     "openvidu-browser": "^2.17.0",
     "randomcolor": "^0.5.4",
     "rc-slider": "^9.7.2",
@@ -32,7 +32,7 @@
     "react-query": "^3.13.4",
     "react-router": "^5.2.0",
     "react-router-dom": "^5.2.0",
-    "react-sync-board": "^0.3.4",
+    "react-sync-board": "^0.4.4",
     "react-toastify": "^6.1.0",
     "react-useportal": "^1.0.14",
     "recoil": "^0.3.1",

+ 3 - 4
src/MainRoute.jsx

@@ -36,7 +36,7 @@ const MainRoute = () => {
           return (
             <Redirect
               to={{
-                pathName: `/session/${uid()}/`,
+                pathname: `/session/${uid()}/`,
                 state: { fromGame: gameId },
               }}
             />
@@ -53,7 +53,7 @@ const MainRoute = () => {
           return (
             <Redirect
               to={{
-                pathName: `/session/${sessionId}`,
+                pathname: `/session/${sessionId}/`,
                 state: { fromGame: gameId },
               }}
             />
@@ -115,10 +115,9 @@ const MainRoute = () => {
           match: {
             params: { roomId },
           },
-          location: { state: { showInvite = false } = {} } = {},
         }) => (
           <WithSocketIO>
-            <RoomView roomId={roomId} showInvite={showInvite} />
+            <RoomView roomId={roomId} />
           </WithSocketIO>
         )}
       </Route>

+ 2 - 2
src/gameComponents/Generator.jsx

@@ -2,7 +2,7 @@ import React, { memo } from "react";
 import styled, { css } from "styled-components";
 import { useItemActions, useItemInteraction, useWire } from "react-sync-board";
 
-import { uid } from "../utils";
+import { uid, getItemElement } from "../utils";
 import itemTemplates from "./itemTemplates";
 import { useTranslation } from "react-i18next";
 import debounce from "lodash.debounce";
@@ -158,7 +158,7 @@ const Generator = ({ color = "#ccc", item, id, currentItemId, setState }) => {
 
       if (currentItemRef.current) {
         // Get size from current item if any
-        const currentDomItem = document.getElementById(currentItemRef.current);
+        const currentDomItem = getItemElement(currentItemRef.current);
         if (currentDomItem) {
           targetWidth = currentDomItem.clientWidth;
           targetHeight = currentDomItem.clientHeight;

+ 1 - 1
src/gameComponents/Image.jsx

@@ -79,7 +79,7 @@ const Image = ({
 }) => {
   const { currentUser, localUsers: users } = useUsers();
 
-  const imageContent = media2Url(content);
+  const imageContent = media2Url(content) || "/default.png";
   const backContent = media2Url(rawBackContent);
   const overlayContent = media2Url(overlay?.content);
 

+ 2 - 2
src/gameComponents/Zone.jsx

@@ -3,7 +3,7 @@ import { memo } from "react";
 import styled, { css } from "styled-components";
 import { useItemInteraction } from "react-sync-board";
 
-import { isItemInsideElement } from "..//utils";
+import { isItemInsideElement, getItemElement } from "../utils";
 import useGameItemActions from "./useGameItemActions";
 
 const ZoneWrapper = styled.div`
@@ -39,7 +39,7 @@ const Zone = ({ width, height, label, onItem }) => {
   const onInsideItem = React.useCallback(
     (itemIds) => {
       const insideItems = itemIds.filter((itemId) =>
-        isItemInsideElement(document.getElementById(itemId), zoneRef.current)
+        isItemInsideElement(getItemElement(itemId), zoneRef.current)
       );
       if (!insideItems.length) return;
 

+ 183 - 0
src/gameComponents/boardBackgrounds.jsx

@@ -0,0 +1,183 @@
+import React from "react";
+import { useTranslation } from "react-i18next";
+
+import i18n from "../i18n";
+import { ImageField, media2Url } from "../mediaLibrary";
+
+import ColorPicker from "../ui/formUtils/ColorPicker";
+
+const drawToDataURL = (width, height, draw) => {
+  const canvas = document.createElement("canvas");
+  canvas.width = width;
+  canvas.height = height;
+  const ctx = canvas.getContext("2d");
+  draw(ctx);
+  return canvas.toDataURL();
+};
+
+const ColorBgForm = ({ value, onChange }) => {
+  const { t } = useTranslation();
+
+  const onColorChange = (clr) => {
+    onChange({ ...value, color: clr });
+  };
+  return (
+    <label>
+      {t("Color")}
+      <ColorPicker value={value.color} onChange={onColorChange} />
+    </label>
+  );
+};
+
+const CustomBgForm = ({ value, onChange }) => {
+  const { t } = useTranslation();
+
+  const onImageChange = (imgVal) => {
+    onChange({ ...value, img: imgVal });
+  };
+  const onColorChange = (clr) => {
+    onChange({ ...value, color: clr });
+  };
+  return (
+    <>
+      <label>
+        {t("Color")}
+        <ColorPicker value={value.color} onChange={onColorChange} />
+      </label>
+      <label>
+        {t("Image")}
+        <ImageField value={value.img} onChange={onImageChange} />
+      </label>
+    </>
+  );
+};
+
+const backgrounds = [
+  {
+    type: "default",
+    name: i18n.t("Default"),
+    form: null,
+    getStyle() {
+      return {
+        background:
+          "radial-gradient(circle, hsla(218, 30%, 40%, 0.7), hsla(218, 40%, 40%, 0.1) 50%),  url(/board.png)",
+      };
+    },
+  },
+  {
+    type: "grid",
+    name: i18n.t("Grid"),
+    form: ColorBgForm,
+    getStyle({ color = "#269" } = {}) {
+      // Create grid background
+      const dataUrl = drawToDataURL(200, 200, (ctx) => {
+        ctx.lineWidth = 4;
+        ctx.strokeStyle = "rgba(255,255,255,0.2)";
+        ctx.beginPath();
+        ctx.moveTo(200, 2);
+        ctx.lineTo(2, 2);
+        ctx.lineTo(2, 200);
+        ctx.stroke();
+      });
+      return {
+        backgroundColor: color,
+        backgroundImage: `url(${dataUrl})`,
+      };
+    },
+  },
+  {
+    type: "dot",
+    name: i18n.t("Dot"),
+    form: ColorBgForm,
+    getStyle({ color = "#ccc" } = {}) {
+      const dotColor = "#ffffff11";
+      const style = {
+        backgroundImage: `radial-gradient(${dotColor} 30%, transparent 20%), radial-gradient(${dotColor} 30%, transparent 20%)`,
+        backgroundColor: color,
+        backgroundPosition: "0 0, 100px 100px",
+        backgroundSize: "200px 200px",
+      };
+      return style;
+    },
+  },
+  {
+    type: "square",
+    name: i18n.t("Square"),
+    form: ColorBgForm,
+    getStyle({ color = "#269" } = {}) {
+      const dataUrl = drawToDataURL(100, 100, (ctx) => {
+        ctx.fillStyle = "rgba(255,255,255,0.1)";
+        ctx.fillRect(0, 0, 50, 50);
+      });
+      return {
+        backgroundColor: color,
+        backgroundImage: `url(${dataUrl})`,
+      };
+    },
+  },
+  {
+    type: "hstripe",
+    name: i18n.t("Horizontal Stripe"),
+    form: ColorBgForm,
+    getStyle({ color = "#269" } = {}) {
+      const dataUrl = drawToDataURL(200, 200, (ctx) => {
+        ctx.fillStyle = "rgba(255,255,255,0.1)";
+        ctx.fillRect(0, 0, 200, 100);
+      });
+      return {
+        backgroundColor: color,
+        backgroundImage: `url(${dataUrl})`,
+      };
+    },
+  },
+  {
+    type: "vstripe",
+    name: i18n.t("Vertical Stripe"),
+    form: ColorBgForm,
+    getStyle({ color = "#269" } = {}) {
+      const dataUrl = drawToDataURL(200, 200, (ctx) => {
+        ctx.fillStyle = "rgba(255,255,255,0.1)";
+        ctx.fillRect(0, 0, 100, 200);
+      });
+      return {
+        backgroundColor: color,
+        backgroundImage: `url(${dataUrl})`,
+      };
+    },
+  },
+  {
+    type: "xstripe",
+    name: i18n.t("Crossed stripes"),
+    form: ColorBgForm,
+    getStyle({ color = "#269" } = {}) {
+      const dataUrl = drawToDataURL(200, 200, (ctx) => {
+        ctx.fillStyle = "rgba(255,255,255,0.1)";
+        ctx.fillRect(0, 0, 100, 200);
+        ctx.fillRect(0, 0, 200, 100);
+      });
+      return {
+        backgroundColor: color,
+        backgroundImage: `url(${dataUrl})`,
+      };
+    },
+  },
+  {
+    type: "custom",
+    name: i18n.t("Custom"),
+    form: CustomBgForm,
+    getStyle({ color = "#ccc", img = {} } = {}) {
+      const style = {
+        backgroundColor: color,
+      };
+      if (img.type) {
+        const url = media2Url(img);
+        if (url) {
+          style.backgroundImage = `url(${url})`;
+        }
+      }
+      return style;
+    },
+  },
+];
+
+export default backgrounds;

+ 2 - 0
src/gameComponents/index.jsx

@@ -3,3 +3,5 @@ export { default as itemTemplates, itemLibrary } from "./itemTemplates";
 export { default as ItemForm } from "./ItemForm";
 
 export { default as premadeItems } from "./premadeItems";
+
+export { default as backgrounds } from "./boardBackgrounds";

+ 12 - 11
src/gameComponents/useGameItemActions.js

@@ -4,7 +4,12 @@ import { useTranslation } from "react-i18next";
 import { toast } from "react-toastify";
 import { useItemActions, useUsers, useSelectedItems } from "react-sync-board";
 
-import { shuffle as shuffleArray, randInt, uid } from "../utils";
+import {
+  shuffle as shuffleArray,
+  randInt,
+  uid,
+  getItemElement,
+} from "../utils";
 
 import RotateActionForm from "./forms/RotateActionForm";
 import RandomlyRotateActionForm from "./forms/RandomlyRotateActionForm";
@@ -102,16 +107,12 @@ export const useGameItemActions = () => {
       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)
+        ...items.map(({ x, id }) => x + getItemElement(id).clientWidth)
       );
       minMax.max.y = Math.max(
-        ...items.map(
-          ({ y, id }) => y + document.getElementById(id).clientHeight
-        )
-      );
-      const { clientWidth, clientHeight } = document.getElementById(
-        items[0].id
+        ...items.map(({ y, id }) => y + getItemElement(id).clientHeight)
       );
+      const { clientWidth, clientHeight } = getItemElement(items[0].id);
       let newX =
         minMax.min.x + (minMax.max.x - minMax.min.x) / 2 - clientWidth / 2;
       let newY =
@@ -174,7 +175,7 @@ export const useGameItemActions = () => {
       let { x: newX, y: newY } = items[0];
 
       batchUpdateItems(ids, (item) => {
-        const { clientWidth } = document.getElementById(item.id);
+        const { clientWidth } = getItemElement(item.id);
         const newItem = {
           ...item,
           x: newX,
@@ -202,7 +203,7 @@ export const useGameItemActions = () => {
       let currentColumn = 1;
 
       batchUpdateItems(ids, (item) => {
-        const { clientWidth, clientHeight } = document.getElementById(item.id);
+        const { clientWidth, clientHeight } = getItemElement(item.id);
         const newItem = {
           ...item,
           x: newX,
@@ -226,7 +227,7 @@ export const useGameItemActions = () => {
       const [ids] = await getItemListOrSelected(itemIds);
 
       ids.forEach((itemId) => {
-        const elem = document.getElementById(itemId);
+        const elem = getItemElement(itemId);
         elem.firstChild.className = "hvr-wobble-horizontal";
       });
       const shuffledItems = shuffleArray([...ids]);

+ 1 - 1
src/hooks/SubscribeSessionEvents.jsx

@@ -39,7 +39,7 @@ export const SubscribeSessionEvents = ({ getSession, setSession }) => {
     return () => {
       unsub.forEach((u) => u());
     };
-  }, [wire, setBoardConfig, setSession]);
+  }, [wire, setSession]);
 
   return null;
 };

+ 11 - 1
src/i18n/en.json

@@ -264,5 +264,15 @@
   "Or": "Or",
   "start a multi room session": "start a multi board session",
   "Let's go...": "Let's go...",
-  "Choose your board": "Choose your board"
+  "Choose your board": "Choose your board",
+  "Default": "Default",
+  "Dot": "Dot",
+  "Square": "Square",
+  "Horizontal Stripe": "Horizontal Stripe",
+  "Vertical Stripe": "Vertical Stripe",
+  "Crossed stripes": "Crossed stripes",
+  "Custom": "Custom",
+  "Background": "Background",
+  "Type": "Type",
+  "Main image": "Main image"
 }

+ 11 - 1
src/i18n/fr.json

@@ -264,5 +264,15 @@
   "Or": "Ou",
   "start a multi room session": "jouer sur plusieurs tables",
   "Let's go...": "C'est parti…",
-  "Choose your board": "Choisissez une table"
+  "Choose your board": "Choisissez une table",
+  "Default": "Par défaut",
+  "Dot": "Points",
+  "Square": "Carrés",
+  "Horizontal Stripe": "Rayures horizontales",
+  "Vertical Stripe": "Rayures verticales",
+  "Crossed stripes": "Rayures croisées",
+  "Custom": "Personnaliser",
+  "Background": "Fond du plateau",
+  "Type": "Type",
+  "Main image": "Image principale"
 }

+ 5 - 1
src/mediaLibrary/index.js

@@ -10,7 +10,11 @@ export const media2Url = (value) => {
   if (value && typeof value === "object") {
     switch (value.type) {
       case "local":
-        return `${API_BASE}/${value.content}`;
+        if (value.content) {
+          return `${API_BASE}/${value.content}`;
+        } else {
+          return "";
+        }
       case "external":
         return value.content;
       case "dataUrl":

+ 8 - 0
src/utils/index.js

@@ -64,3 +64,11 @@ export const uid = customAlphabet(alpha, 10);
 
 // Custom small uid generator
 export const smallUid = customAlphabet(alpha, 5);
+
+export const getItemElement = (id) => {
+  const elem = document.getElementsByClassName(`item ${id}`)[0];
+  if (!elem) {
+    console.warn(`Missing element for id ${id}`);
+  }
+  return elem;
+};

+ 35 - 10
src/views/BoardView/BoardForm.jsx

@@ -7,8 +7,11 @@ import { useBoardConfig } from "react-sync-board";
 import Hint from "../../ui/formUtils/Hint";
 import Label from "../../ui/formUtils/Label";
 import SliderRange from "../../ui/SliderRange";
+
 import { ImageField } from "../../mediaLibrary";
 
+import { backgrounds } from "../../gameComponents";
+
 const BoardConfigForm = () => {
   const { t } = useTranslation();
 
@@ -30,6 +33,10 @@ const BoardConfigForm = () => {
     }));
   };
 
+  const CurrentBgForm = backgrounds.find(
+    ({ type }) => type === (boardConfig.bgType || "default")
+  )?.form;
+
   return (
     <>
       <Label>
@@ -42,6 +49,33 @@ const BoardConfigForm = () => {
         />
         <Hint>{t("Check it to make your board publicly visible")}</Hint>
       </Label>
+      <fieldset style={{ marginBottom: "2em", paddingBottom: "1em" }}>
+        <legend>{t("Background")}</legend>
+
+        <Label>{t("Type")}</Label>
+        <Field
+          name="bgType"
+          component="select"
+          initialValue={boardConfig.bgType || "default"}
+        >
+          {backgrounds.map((bg) => {
+            return (
+              <option key={bg.type} value={bg.type}>
+                {bg.name}
+              </option>
+            );
+          })}
+        </Field>
+        <div style={{ paddingTop: "1em" }}>
+          {CurrentBgForm && (
+            <Field name="bgConf" initialValue={boardConfig.bgConf}>
+              {({ input: { onChange, value } }) => {
+                return <CurrentBgForm value={value} onChange={onChange} />;
+              }}
+            </Field>
+          )}
+        </div>
+      </fieldset>
       <Label>
         {t("Number of players")}
         <Field
@@ -96,15 +130,6 @@ const BoardConfigForm = () => {
         />
       </Label>
       <Label>
-        {t("Board size")}
-        <Field
-          name="size"
-          component="input"
-          initialValue={boardConfig.size}
-          style={{ width: "5em", textAlign: "right" }}
-        />
-      </Label>
-      <Label>
         {t("Magnetic Grid size")}
         <Field
           name="gridSize"
@@ -114,7 +139,7 @@ const BoardConfigForm = () => {
         />
       </Label>
       <Label>
-        {t("Image")}
+        {t("Main image")}
         <Field name="imageUrl" initialValue={boardConfig.imageUrl}>
           {({ input: { value, onChange } }) => {
             return <ImageField value={value} onChange={onChange} />;

+ 13 - 12
src/views/BoardView/BoardView.jsx

@@ -1,36 +1,37 @@
 import React from "react";
 
-import { Board, useWire } from "react-sync-board";
+import { Board, useWire, useBoardConfig } from "react-sync-board";
 
 import { SHOW_WELCOME } from "../../utils/settings";
 import WelcomeModal from "./WelcomeModal";
 import NavBar from "./NavBar";
 import BoardForm from "./BoardForm";
-import SelectedItemPane from "./SelectedItemsPane";
+import SelectedItemsPane from "./SelectedItemsPane";
 
-import { itemTemplates } from "../../gameComponents";
+import { itemTemplates, backgrounds } from "../../gameComponents";
 
 import ActionBar from "./ActionBar";
 
 import { MediaLibraryProvider, ImageDropNPaste } from "../../mediaLibrary";
 
-const style = {
-  background:
-    "radial-gradient(circle, hsla(218, 30%, 40%, 0.7), hsla(218, 40%, 40%, 0.05) 100%),  url(/board.png)",
-  border: "1px solid transparent",
-  borderRadius: "2px",
-  boxShadow: "0px 3px 6px #00000029",
-};
-
 export const BoardView = ({ mediaLibraries, edit, itemLibraries }) => {
   const { isMaster } = useWire("board");
   const [showWelcomeModal, setShowWelcomeModal] = React.useState(
     SHOW_WELCOME && !edit && isMaster
   );
 
+  const [boardConfig] = useBoardConfig();
+
   const [moveFirst, setMoveFirst] = React.useState(false);
   const [hideMenu, setHideMenu] = React.useState(false);
 
+  const style = React.useMemo(() => {
+    const currentBackground =
+      backgrounds.find(({ type }) => type === boardConfig.bgType) ||
+      backgrounds.find(({ type }) => type === "default");
+    return currentBackground.getStyle(boardConfig.bgConf);
+  }, [boardConfig.bgConf, boardConfig.bgType]);
+
   return (
     <MediaLibraryProvider libraries={mediaLibraries}>
       <ImageDropNPaste>
@@ -51,7 +52,7 @@ export const BoardView = ({ mediaLibraries, edit, itemLibraries }) => {
         />
         <WelcomeModal show={showWelcomeModal} setShow={setShowWelcomeModal} />
       </ImageDropNPaste>
-      <SelectedItemPane hideMenu={hideMenu} />
+      <SelectedItemsPane hideMenu={hideMenu} />
     </MediaLibraryProvider>
   );
 };

+ 2 - 2
src/views/BoardView/GameInformation.jsx

@@ -19,7 +19,7 @@ const GameInformation = () => {
 
   useAsyncEffect(
     async (isMounted) => {
-      const marked = (await import("marked")).default;
+      const { marked } = await import("marked");
       if (!isMounted()) return;
 
       const renderer = new marked.Renderer();
@@ -27,7 +27,7 @@ const GameInformation = () => {
         return `<a target="_blank" rel="noopener" href="${href}" title="${title}">${text}</a>`;
       };
       setInfo(
-        marked(translation.description || "", {
+        marked.parse(translation.description || "", {
           renderer: renderer,
         })
       );