[WIP] Update
This commit is contained in:
		@@ -28,19 +28,24 @@
 | 
			
		||||
    "prepare": "husky install"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@emotion/react": "^11.11.1",
 | 
			
		||||
    "@emotion/styled": "^11.11.0",
 | 
			
		||||
    "@excalidraw/excalidraw": "^0.15.2",
 | 
			
		||||
    "@fontsource/roboto": "^5.0.5",
 | 
			
		||||
    "@mui/icons-material": "^5.14.1",
 | 
			
		||||
    "@mui/material": "^5.14.1",
 | 
			
		||||
    "@mui/styled-engine-sc": "^5.12.0",
 | 
			
		||||
    "dexie": "^3.2.4",
 | 
			
		||||
    "dexie-react-hooks": "^1.1.6",
 | 
			
		||||
    "i18next": "^23.2.11",
 | 
			
		||||
    "i18next-browser-languagedetector": "^7.1.0",
 | 
			
		||||
    "i18next-http-backend": "^2.2.1",
 | 
			
		||||
    "jotai": "^2.2.2",
 | 
			
		||||
    "just-diff": "^6.0.2",
 | 
			
		||||
    "just-diff-apply": "^5.5.0",
 | 
			
		||||
    "qrcode.react": "^3.1.0",
 | 
			
		||||
    "react": "^18.2.0",
 | 
			
		||||
    "react-dom": "^18.2.0",
 | 
			
		||||
    "react-i18next": "^13.0.2",
 | 
			
		||||
    "react-router-dom": "^6.14.2",
 | 
			
		||||
    "styled-components": "^6.0.4",
 | 
			
		||||
    "ts-pattern": "^5.0.4",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										292
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										292
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							@@ -5,6 +5,12 @@ settings:
 | 
			
		||||
  excludeLinksFromLockfile: false
 | 
			
		||||
 | 
			
		||||
dependencies:
 | 
			
		||||
  '@emotion/react':
 | 
			
		||||
    specifier: ^11.11.1
 | 
			
		||||
    version: 11.11.1(@types/react@18.2.14)(react@18.2.0)
 | 
			
		||||
  '@emotion/styled':
 | 
			
		||||
    specifier: ^11.11.0
 | 
			
		||||
    version: 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.14)(react@18.2.0)
 | 
			
		||||
  '@excalidraw/excalidraw':
 | 
			
		||||
    specifier: ^0.15.2
 | 
			
		||||
    version: 0.15.2(react-dom@18.2.0)(react@18.2.0)
 | 
			
		||||
@@ -16,16 +22,22 @@ dependencies:
 | 
			
		||||
    version: 5.14.1(@mui/material@5.14.1)(@types/react@18.2.14)(react@18.2.0)
 | 
			
		||||
  '@mui/material':
 | 
			
		||||
    specifier: ^5.14.1
 | 
			
		||||
    version: 5.14.1(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
 | 
			
		||||
  '@mui/styled-engine-sc':
 | 
			
		||||
    specifier: ^5.12.0
 | 
			
		||||
    version: 5.12.0(styled-components@6.0.4)
 | 
			
		||||
    version: 5.14.1(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
 | 
			
		||||
  dexie:
 | 
			
		||||
    specifier: ^3.2.4
 | 
			
		||||
    version: 3.2.4
 | 
			
		||||
  dexie-react-hooks:
 | 
			
		||||
    specifier: ^1.1.6
 | 
			
		||||
    version: 1.1.6(@types/react@18.2.14)(dexie@3.2.4)(react@18.2.0)
 | 
			
		||||
  i18next:
 | 
			
		||||
    specifier: ^23.2.11
 | 
			
		||||
    version: 23.2.11
 | 
			
		||||
  i18next-browser-languagedetector:
 | 
			
		||||
    specifier: ^7.1.0
 | 
			
		||||
    version: 7.1.0
 | 
			
		||||
  i18next-http-backend:
 | 
			
		||||
    specifier: ^2.2.1
 | 
			
		||||
    version: 2.2.1
 | 
			
		||||
  jotai:
 | 
			
		||||
    specifier: ^2.2.2
 | 
			
		||||
    version: 2.2.2(react@18.2.0)
 | 
			
		||||
@@ -44,6 +56,9 @@ dependencies:
 | 
			
		||||
  react-dom:
 | 
			
		||||
    specifier: ^18.2.0
 | 
			
		||||
    version: 18.2.0(react@18.2.0)
 | 
			
		||||
  react-i18next:
 | 
			
		||||
    specifier: ^13.0.2
 | 
			
		||||
    version: 13.0.2(i18next@23.2.11)(react-dom@18.2.0)(react@18.2.0)
 | 
			
		||||
  react-router-dom:
 | 
			
		||||
    specifier: ^6.14.2
 | 
			
		||||
    version: 6.14.2(react-dom@18.2.0)(react@18.2.0)
 | 
			
		||||
@@ -1456,6 +1471,22 @@ packages:
 | 
			
		||||
      to-fast-properties: 2.0.0
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /@emotion/babel-plugin@11.11.0:
 | 
			
		||||
    resolution: {integrity: sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==}
 | 
			
		||||
    dependencies:
 | 
			
		||||
      '@babel/helper-module-imports': 7.22.5
 | 
			
		||||
      '@babel/runtime': 7.22.6
 | 
			
		||||
      '@emotion/hash': 0.9.1
 | 
			
		||||
      '@emotion/memoize': 0.8.1
 | 
			
		||||
      '@emotion/serialize': 1.1.2
 | 
			
		||||
      babel-plugin-macros: 3.1.0
 | 
			
		||||
      convert-source-map: 1.9.0
 | 
			
		||||
      escape-string-regexp: 4.0.0
 | 
			
		||||
      find-root: 1.1.0
 | 
			
		||||
      source-map: 0.5.7
 | 
			
		||||
      stylis: 4.2.0
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /@emotion/cache@11.11.0:
 | 
			
		||||
    resolution: {integrity: sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==}
 | 
			
		||||
    dependencies:
 | 
			
		||||
@@ -1466,6 +1497,10 @@ packages:
 | 
			
		||||
      stylis: 4.2.0
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /@emotion/hash@0.9.1:
 | 
			
		||||
    resolution: {integrity: sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==}
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /@emotion/is-prop-valid@1.2.1:
 | 
			
		||||
    resolution: {integrity: sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==}
 | 
			
		||||
    dependencies:
 | 
			
		||||
@@ -1476,14 +1511,74 @@ packages:
 | 
			
		||||
    resolution: {integrity: sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==}
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /@emotion/react@11.11.1(@types/react@18.2.14)(react@18.2.0):
 | 
			
		||||
    resolution: {integrity: sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA==}
 | 
			
		||||
    peerDependencies:
 | 
			
		||||
      '@types/react': '*'
 | 
			
		||||
      react: '>=16.8.0'
 | 
			
		||||
    peerDependenciesMeta:
 | 
			
		||||
      '@types/react':
 | 
			
		||||
        optional: true
 | 
			
		||||
    dependencies:
 | 
			
		||||
      '@babel/runtime': 7.22.6
 | 
			
		||||
      '@emotion/babel-plugin': 11.11.0
 | 
			
		||||
      '@emotion/cache': 11.11.0
 | 
			
		||||
      '@emotion/serialize': 1.1.2
 | 
			
		||||
      '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.2.0)
 | 
			
		||||
      '@emotion/utils': 1.2.1
 | 
			
		||||
      '@emotion/weak-memoize': 0.3.1
 | 
			
		||||
      '@types/react': 18.2.14
 | 
			
		||||
      hoist-non-react-statics: 3.3.2
 | 
			
		||||
      react: 18.2.0
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /@emotion/serialize@1.1.2:
 | 
			
		||||
    resolution: {integrity: sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==}
 | 
			
		||||
    dependencies:
 | 
			
		||||
      '@emotion/hash': 0.9.1
 | 
			
		||||
      '@emotion/memoize': 0.8.1
 | 
			
		||||
      '@emotion/unitless': 0.8.1
 | 
			
		||||
      '@emotion/utils': 1.2.1
 | 
			
		||||
      csstype: 3.1.2
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /@emotion/sheet@1.2.2:
 | 
			
		||||
    resolution: {integrity: sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==}
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /@emotion/styled@11.11.0(@emotion/react@11.11.1)(@types/react@18.2.14)(react@18.2.0):
 | 
			
		||||
    resolution: {integrity: sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==}
 | 
			
		||||
    peerDependencies:
 | 
			
		||||
      '@emotion/react': ^11.0.0-rc.0
 | 
			
		||||
      '@types/react': '*'
 | 
			
		||||
      react: '>=16.8.0'
 | 
			
		||||
    peerDependenciesMeta:
 | 
			
		||||
      '@types/react':
 | 
			
		||||
        optional: true
 | 
			
		||||
    dependencies:
 | 
			
		||||
      '@babel/runtime': 7.22.6
 | 
			
		||||
      '@emotion/babel-plugin': 11.11.0
 | 
			
		||||
      '@emotion/is-prop-valid': 1.2.1
 | 
			
		||||
      '@emotion/react': 11.11.1(@types/react@18.2.14)(react@18.2.0)
 | 
			
		||||
      '@emotion/serialize': 1.1.2
 | 
			
		||||
      '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.2.0)
 | 
			
		||||
      '@emotion/utils': 1.2.1
 | 
			
		||||
      '@types/react': 18.2.14
 | 
			
		||||
      react: 18.2.0
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /@emotion/unitless@0.8.1:
 | 
			
		||||
    resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==}
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /@emotion/use-insertion-effect-with-fallbacks@1.0.1(react@18.2.0):
 | 
			
		||||
    resolution: {integrity: sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==}
 | 
			
		||||
    peerDependencies:
 | 
			
		||||
      react: '>=16.8.0'
 | 
			
		||||
    dependencies:
 | 
			
		||||
      react: 18.2.0
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /@emotion/utils@1.2.1:
 | 
			
		||||
    resolution: {integrity: sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==}
 | 
			
		||||
    dev: false
 | 
			
		||||
@@ -1839,12 +1934,12 @@ packages:
 | 
			
		||||
        optional: true
 | 
			
		||||
    dependencies:
 | 
			
		||||
      '@babel/runtime': 7.22.6
 | 
			
		||||
      '@mui/material': 5.14.1(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
 | 
			
		||||
      '@mui/material': 5.14.1(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
 | 
			
		||||
      '@types/react': 18.2.14
 | 
			
		||||
      react: 18.2.0
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /@mui/material@5.14.1(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0):
 | 
			
		||||
  /@mui/material@5.14.1(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0):
 | 
			
		||||
    resolution: {integrity: sha512-WtsgYuageTunLfxH3Ri+o1RuQTFImtRHxMcVNyD0Hhd2/znjW6KODNz0XfjvLRnNCAynBxZNiflcoIBW40h9PQ==}
 | 
			
		||||
    engines: {node: '>=12.0.0'}
 | 
			
		||||
    peerDependencies:
 | 
			
		||||
@@ -1862,9 +1957,11 @@ packages:
 | 
			
		||||
        optional: true
 | 
			
		||||
    dependencies:
 | 
			
		||||
      '@babel/runtime': 7.22.6
 | 
			
		||||
      '@emotion/react': 11.11.1(@types/react@18.2.14)(react@18.2.0)
 | 
			
		||||
      '@emotion/styled': 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.14)(react@18.2.0)
 | 
			
		||||
      '@mui/base': 5.0.0-beta.8(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
 | 
			
		||||
      '@mui/core-downloads-tracker': 5.14.1
 | 
			
		||||
      '@mui/system': 5.14.1(@types/react@18.2.14)(react@18.2.0)
 | 
			
		||||
      '@mui/system': 5.14.1(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.14)(react@18.2.0)
 | 
			
		||||
      '@mui/types': 7.2.4(@types/react@18.2.14)
 | 
			
		||||
      '@mui/utils': 5.14.1(react@18.2.0)
 | 
			
		||||
      '@types/react': 18.2.14
 | 
			
		||||
@@ -1895,22 +1992,7 @@ packages:
 | 
			
		||||
      react: 18.2.0
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /@mui/styled-engine-sc@5.12.0(styled-components@6.0.4):
 | 
			
		||||
    resolution: {integrity: sha512-3MgYoY2YG5tx0E5oKqvCv94oL0ABVBr+qpcyvciXW/v0wzPG6bXvuZV80GHYlJfasgnnRa1AbRWf5a9FcX8v6g==}
 | 
			
		||||
    engines: {node: '>=12.0.0'}
 | 
			
		||||
    peerDependencies:
 | 
			
		||||
      '@types/styled-components': ^5.1.14
 | 
			
		||||
      styled-components: ^5.3.1
 | 
			
		||||
    peerDependenciesMeta:
 | 
			
		||||
      '@types/styled-components':
 | 
			
		||||
        optional: true
 | 
			
		||||
    dependencies:
 | 
			
		||||
      '@babel/runtime': 7.22.6
 | 
			
		||||
      prop-types: 15.8.1
 | 
			
		||||
      styled-components: 6.0.4(react-dom@18.2.0)(react@18.2.0)
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /@mui/styled-engine@5.13.2(react@18.2.0):
 | 
			
		||||
  /@mui/styled-engine@5.13.2(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0):
 | 
			
		||||
    resolution: {integrity: sha512-VCYCU6xVtXOrIN8lcbuPmoG+u7FYuOERG++fpY74hPpEWkyFQG97F+/XfTQVYzlR2m7nPjnwVUgATcTCMEaMvw==}
 | 
			
		||||
    engines: {node: '>=12.0.0'}
 | 
			
		||||
    peerDependencies:
 | 
			
		||||
@@ -1925,12 +2007,14 @@ packages:
 | 
			
		||||
    dependencies:
 | 
			
		||||
      '@babel/runtime': 7.22.6
 | 
			
		||||
      '@emotion/cache': 11.11.0
 | 
			
		||||
      '@emotion/react': 11.11.1(@types/react@18.2.14)(react@18.2.0)
 | 
			
		||||
      '@emotion/styled': 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.14)(react@18.2.0)
 | 
			
		||||
      csstype: 3.1.2
 | 
			
		||||
      prop-types: 15.8.1
 | 
			
		||||
      react: 18.2.0
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /@mui/system@5.14.1(@types/react@18.2.14)(react@18.2.0):
 | 
			
		||||
  /@mui/system@5.14.1(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.14)(react@18.2.0):
 | 
			
		||||
    resolution: {integrity: sha512-u+xlsU34Jdkgx1CxmBnIC4Y08uPdVX5iEd3S/1dggDFtOGp+Lj8xmKRJAQ8PJOOJLOh8pDwaZx4AwXikL4l1QA==}
 | 
			
		||||
    engines: {node: '>=12.0.0'}
 | 
			
		||||
    peerDependencies:
 | 
			
		||||
@@ -1947,8 +2031,10 @@ packages:
 | 
			
		||||
        optional: true
 | 
			
		||||
    dependencies:
 | 
			
		||||
      '@babel/runtime': 7.22.6
 | 
			
		||||
      '@emotion/react': 11.11.1(@types/react@18.2.14)(react@18.2.0)
 | 
			
		||||
      '@emotion/styled': 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.14)(react@18.2.0)
 | 
			
		||||
      '@mui/private-theming': 5.13.7(@types/react@18.2.14)(react@18.2.0)
 | 
			
		||||
      '@mui/styled-engine': 5.13.2(react@18.2.0)
 | 
			
		||||
      '@mui/styled-engine': 5.13.2(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0)
 | 
			
		||||
      '@mui/types': 7.2.4(@types/react@18.2.14)
 | 
			
		||||
      '@mui/utils': 5.14.1(react@18.2.0)
 | 
			
		||||
      '@types/react': 18.2.14
 | 
			
		||||
@@ -2156,6 +2242,10 @@ packages:
 | 
			
		||||
    resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==}
 | 
			
		||||
    dev: true
 | 
			
		||||
 | 
			
		||||
  /@types/parse-json@4.0.0:
 | 
			
		||||
    resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==}
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /@types/prop-types@15.7.5:
 | 
			
		||||
    resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==}
 | 
			
		||||
 | 
			
		||||
@@ -2449,6 +2539,15 @@ packages:
 | 
			
		||||
    engines: {node: '>= 0.4'}
 | 
			
		||||
    dev: true
 | 
			
		||||
 | 
			
		||||
  /babel-plugin-macros@3.1.0:
 | 
			
		||||
    resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==}
 | 
			
		||||
    engines: {node: '>=10', npm: '>=6'}
 | 
			
		||||
    dependencies:
 | 
			
		||||
      '@babel/runtime': 7.22.6
 | 
			
		||||
      cosmiconfig: 7.1.0
 | 
			
		||||
      resolve: 1.22.2
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /babel-plugin-polyfill-corejs2@0.4.4(@babel/core@7.22.9):
 | 
			
		||||
    resolution: {integrity: sha512-9WeK9snM1BfxB38goUEv2FLnA6ja07UMfazFHzCXUb3NyDZAwfXvQiURQ6guTTMeHcOsdknULm1PDhs4uWtKyA==}
 | 
			
		||||
    peerDependencies:
 | 
			
		||||
@@ -2545,7 +2644,6 @@ packages:
 | 
			
		||||
  /callsites@3.1.0:
 | 
			
		||||
    resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
 | 
			
		||||
    engines: {node: '>=6'}
 | 
			
		||||
    dev: true
 | 
			
		||||
 | 
			
		||||
  /camelize@1.0.1:
 | 
			
		||||
    resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==}
 | 
			
		||||
@@ -2672,6 +2770,25 @@ packages:
 | 
			
		||||
      browserslist: 4.21.9
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /cosmiconfig@7.1.0:
 | 
			
		||||
    resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==}
 | 
			
		||||
    engines: {node: '>=10'}
 | 
			
		||||
    dependencies:
 | 
			
		||||
      '@types/parse-json': 4.0.0
 | 
			
		||||
      import-fresh: 3.3.0
 | 
			
		||||
      parse-json: 5.2.0
 | 
			
		||||
      path-type: 4.0.0
 | 
			
		||||
      yaml: 1.10.2
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /cross-fetch@3.1.6:
 | 
			
		||||
    resolution: {integrity: sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==}
 | 
			
		||||
    dependencies:
 | 
			
		||||
      node-fetch: 2.6.12
 | 
			
		||||
    transitivePeerDependencies:
 | 
			
		||||
      - encoding
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /cross-spawn@6.0.5:
 | 
			
		||||
    resolution: {integrity: sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==}
 | 
			
		||||
    engines: {node: '>=4.8'}
 | 
			
		||||
@@ -2810,7 +2927,6 @@ packages:
 | 
			
		||||
    resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
 | 
			
		||||
    dependencies:
 | 
			
		||||
      is-arrayish: 0.2.1
 | 
			
		||||
    dev: true
 | 
			
		||||
 | 
			
		||||
  /es-abstract@1.22.1:
 | 
			
		||||
    resolution: {integrity: sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==}
 | 
			
		||||
@@ -2917,7 +3033,6 @@ packages:
 | 
			
		||||
  /escape-string-regexp@4.0.0:
 | 
			
		||||
    resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
 | 
			
		||||
    engines: {node: '>=10'}
 | 
			
		||||
    dev: true
 | 
			
		||||
 | 
			
		||||
  /eslint-plugin-react-hooks@4.6.0(eslint@8.44.0):
 | 
			
		||||
    resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==}
 | 
			
		||||
@@ -3099,6 +3214,10 @@ packages:
 | 
			
		||||
    dependencies:
 | 
			
		||||
      to-regex-range: 5.0.1
 | 
			
		||||
 | 
			
		||||
  /find-root@1.1.0:
 | 
			
		||||
    resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==}
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /find-up@5.0.0:
 | 
			
		||||
    resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
 | 
			
		||||
    engines: {node: '>=10'}
 | 
			
		||||
@@ -3333,10 +3452,22 @@ packages:
 | 
			
		||||
    dependencies:
 | 
			
		||||
      function-bind: 1.1.1
 | 
			
		||||
 | 
			
		||||
  /hoist-non-react-statics@3.3.2:
 | 
			
		||||
    resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
 | 
			
		||||
    dependencies:
 | 
			
		||||
      react-is: 16.13.1
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /hosted-git-info@2.8.9:
 | 
			
		||||
    resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
 | 
			
		||||
    dev: true
 | 
			
		||||
 | 
			
		||||
  /html-parse-stringify@3.0.1:
 | 
			
		||||
    resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==}
 | 
			
		||||
    dependencies:
 | 
			
		||||
      void-elements: 3.1.0
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /http-cache-semantics@4.1.1:
 | 
			
		||||
    resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==}
 | 
			
		||||
    dev: true
 | 
			
		||||
@@ -3360,6 +3491,26 @@ packages:
 | 
			
		||||
    hasBin: true
 | 
			
		||||
    dev: true
 | 
			
		||||
 | 
			
		||||
  /i18next-browser-languagedetector@7.1.0:
 | 
			
		||||
    resolution: {integrity: sha512-cr2k7u1XJJ4HTOjM9GyOMtbOA47RtUoWRAtt52z43r3AoMs2StYKyjS3URPhzHaf+mn10hY9dZWamga5WPQjhA==}
 | 
			
		||||
    dependencies:
 | 
			
		||||
      '@babel/runtime': 7.22.6
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /i18next-http-backend@2.2.1:
 | 
			
		||||
    resolution: {integrity: sha512-ZXIdn/8NJIBJ0X4hzXfc3STYxKrCKh1fYjji9HPyIpEJfvTvy8/ZlTl8RuTizzCPj2ZcWrfaecyOMKs6bQ7u5A==}
 | 
			
		||||
    dependencies:
 | 
			
		||||
      cross-fetch: 3.1.6
 | 
			
		||||
    transitivePeerDependencies:
 | 
			
		||||
      - encoding
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /i18next@23.2.11:
 | 
			
		||||
    resolution: {integrity: sha512-MA4FsxOjyCaOZtRDB4yuwjCvqYEioD4G4LlXOn7SO3rnQUlxTufyLsOqfL9MKakeLRBkefe8bqcs0D6Z/xFk1w==}
 | 
			
		||||
    dependencies:
 | 
			
		||||
      '@babel/runtime': 7.22.6
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /ignore@5.2.4:
 | 
			
		||||
    resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==}
 | 
			
		||||
    engines: {node: '>= 4'}
 | 
			
		||||
@@ -3371,7 +3522,6 @@ packages:
 | 
			
		||||
    dependencies:
 | 
			
		||||
      parent-module: 1.0.1
 | 
			
		||||
      resolve-from: 4.0.0
 | 
			
		||||
    dev: true
 | 
			
		||||
 | 
			
		||||
  /imurmurhash@0.1.4:
 | 
			
		||||
    resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
 | 
			
		||||
@@ -3415,7 +3565,6 @@ packages:
 | 
			
		||||
 | 
			
		||||
  /is-arrayish@0.2.1:
 | 
			
		||||
    resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
 | 
			
		||||
    dev: true
 | 
			
		||||
 | 
			
		||||
  /is-bigint@1.0.4:
 | 
			
		||||
    resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==}
 | 
			
		||||
@@ -3593,6 +3742,10 @@ packages:
 | 
			
		||||
    resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==}
 | 
			
		||||
    dev: true
 | 
			
		||||
 | 
			
		||||
  /json-parse-even-better-errors@2.3.1:
 | 
			
		||||
    resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /json-schema-traverse@0.4.1:
 | 
			
		||||
    resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
 | 
			
		||||
    dev: true
 | 
			
		||||
@@ -3660,6 +3813,10 @@ packages:
 | 
			
		||||
    engines: {node: '>=10'}
 | 
			
		||||
    dev: true
 | 
			
		||||
 | 
			
		||||
  /lines-and-columns@1.2.4:
 | 
			
		||||
    resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /lint-staged@13.2.3:
 | 
			
		||||
    resolution: {integrity: sha512-zVVEXLuQIhr1Y7R7YAWx4TZLdvuzk7DnmrsTNL0fax6Z3jrpFcas+vKbzxhhvp6TA55m1SQuWkpzI1qbfDZbAg==}
 | 
			
		||||
    engines: {node: ^14.13.1 || >=16.0.0}
 | 
			
		||||
@@ -3854,6 +4011,18 @@ packages:
 | 
			
		||||
      tslib: 2.6.0
 | 
			
		||||
    dev: true
 | 
			
		||||
 | 
			
		||||
  /node-fetch@2.6.12:
 | 
			
		||||
    resolution: {integrity: sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==}
 | 
			
		||||
    engines: {node: 4.x || >=6.0.0}
 | 
			
		||||
    peerDependencies:
 | 
			
		||||
      encoding: ^0.1.0
 | 
			
		||||
    peerDependenciesMeta:
 | 
			
		||||
      encoding:
 | 
			
		||||
        optional: true
 | 
			
		||||
    dependencies:
 | 
			
		||||
      whatwg-url: 5.0.0
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /node-releases@2.0.13:
 | 
			
		||||
    resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==}
 | 
			
		||||
    dev: false
 | 
			
		||||
@@ -3985,7 +4154,6 @@ packages:
 | 
			
		||||
    engines: {node: '>=6'}
 | 
			
		||||
    dependencies:
 | 
			
		||||
      callsites: 3.1.0
 | 
			
		||||
    dev: true
 | 
			
		||||
 | 
			
		||||
  /parse-json@4.0.0:
 | 
			
		||||
    resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==}
 | 
			
		||||
@@ -3995,6 +4163,16 @@ packages:
 | 
			
		||||
      json-parse-better-errors: 1.0.2
 | 
			
		||||
    dev: true
 | 
			
		||||
 | 
			
		||||
  /parse-json@5.2.0:
 | 
			
		||||
    resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
 | 
			
		||||
    engines: {node: '>=8'}
 | 
			
		||||
    dependencies:
 | 
			
		||||
      '@babel/code-frame': 7.22.5
 | 
			
		||||
      error-ex: 1.3.2
 | 
			
		||||
      json-parse-even-better-errors: 2.3.1
 | 
			
		||||
      lines-and-columns: 1.2.4
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /path-exists@4.0.0:
 | 
			
		||||
    resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
 | 
			
		||||
    engines: {node: '>=8'}
 | 
			
		||||
@@ -4032,7 +4210,6 @@ packages:
 | 
			
		||||
  /path-type@4.0.0:
 | 
			
		||||
    resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
 | 
			
		||||
    engines: {node: '>=8'}
 | 
			
		||||
    dev: true
 | 
			
		||||
 | 
			
		||||
  /picocolors@1.0.0:
 | 
			
		||||
    resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
 | 
			
		||||
@@ -4136,6 +4313,26 @@ packages:
 | 
			
		||||
      scheduler: 0.23.0
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /react-i18next@13.0.2(i18next@23.2.11)(react-dom@18.2.0)(react@18.2.0):
 | 
			
		||||
    resolution: {integrity: sha512-NEVxC32v0oR4egwYM0QM0WE93AiJG5r0NTXTL8mhQfAhsMfDS2fSO6jpluyfsfypP988KzUQrAXncspcJ7+GHA==}
 | 
			
		||||
    peerDependencies:
 | 
			
		||||
      i18next: '>= 23.2.3'
 | 
			
		||||
      react: '>= 16.8.0'
 | 
			
		||||
      react-dom: '*'
 | 
			
		||||
      react-native: '*'
 | 
			
		||||
    peerDependenciesMeta:
 | 
			
		||||
      react-dom:
 | 
			
		||||
        optional: true
 | 
			
		||||
      react-native:
 | 
			
		||||
        optional: true
 | 
			
		||||
    dependencies:
 | 
			
		||||
      '@babel/runtime': 7.22.6
 | 
			
		||||
      html-parse-stringify: 3.0.1
 | 
			
		||||
      i18next: 23.2.11
 | 
			
		||||
      react: 18.2.0
 | 
			
		||||
      react-dom: 18.2.0(react@18.2.0)
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /react-is@16.13.1:
 | 
			
		||||
    resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
 | 
			
		||||
    dev: false
 | 
			
		||||
@@ -4261,7 +4458,6 @@ packages:
 | 
			
		||||
  /resolve-from@4.0.0:
 | 
			
		||||
    resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
 | 
			
		||||
    engines: {node: '>=4'}
 | 
			
		||||
    dev: true
 | 
			
		||||
 | 
			
		||||
  /resolve@1.22.2:
 | 
			
		||||
    resolution: {integrity: sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==}
 | 
			
		||||
@@ -4455,6 +4651,11 @@ packages:
 | 
			
		||||
    resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
 | 
			
		||||
    engines: {node: '>=0.10.0'}
 | 
			
		||||
 | 
			
		||||
  /source-map@0.5.7:
 | 
			
		||||
    resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==}
 | 
			
		||||
    engines: {node: '>=0.10.0'}
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /spdx-correct@3.2.0:
 | 
			
		||||
    resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==}
 | 
			
		||||
    dependencies:
 | 
			
		||||
@@ -4664,6 +4865,10 @@ packages:
 | 
			
		||||
    dependencies:
 | 
			
		||||
      is-number: 7.0.0
 | 
			
		||||
 | 
			
		||||
  /tr46@0.0.3:
 | 
			
		||||
    resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /ts-pattern@5.0.4:
 | 
			
		||||
    resolution: {integrity: sha512-D5iVliqugv2C9541W2CNXFYNEZxr4TiHuLPuf49tKEdQFp/8y8fR0v1RExUvXkiWozKCwE7zv07C6EKxf0lKuQ==}
 | 
			
		||||
    dev: false
 | 
			
		||||
@@ -4882,6 +5087,22 @@ packages:
 | 
			
		||||
      fsevents: 2.3.2
 | 
			
		||||
    dev: true
 | 
			
		||||
 | 
			
		||||
  /void-elements@3.1.0:
 | 
			
		||||
    resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
 | 
			
		||||
    engines: {node: '>=0.10.0'}
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /webidl-conversions@3.0.1:
 | 
			
		||||
    resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /whatwg-url@5.0.0:
 | 
			
		||||
    resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
 | 
			
		||||
    dependencies:
 | 
			
		||||
      tr46: 0.0.3
 | 
			
		||||
      webidl-conversions: 3.0.1
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /which-boxed-primitive@1.0.2:
 | 
			
		||||
    resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==}
 | 
			
		||||
    dependencies:
 | 
			
		||||
@@ -4947,6 +5168,11 @@ packages:
 | 
			
		||||
    resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
 | 
			
		||||
    dev: true
 | 
			
		||||
 | 
			
		||||
  /yaml@1.10.2:
 | 
			
		||||
    resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
 | 
			
		||||
    engines: {node: '>= 6'}
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /yaml@2.3.1:
 | 
			
		||||
    resolution: {integrity: sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==}
 | 
			
		||||
    engines: {node: '>= 14'}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@ import ExcalidrawContainer from './Excalidraw/Container';
 | 
			
		||||
import { useCallback, useState } from 'react';
 | 
			
		||||
import Sidebar, { SidebarVariant } from './Excalidraw/Sidebar';
 | 
			
		||||
import RightTopUI from './Excalidraw/RightTopUI';
 | 
			
		||||
import MainMenu from './Excalidraw/MainMenu';
 | 
			
		||||
import MainMenuBase from './Excalidraw/MainMenu';
 | 
			
		||||
 | 
			
		||||
export const ExcalidrawMain = () => {
 | 
			
		||||
  const [excalidrawAPI, setExcalidrawAPI] = useState<ExcalidrawImperativeAPI>();
 | 
			
		||||
@@ -32,7 +32,7 @@ export const ExcalidrawMain = () => {
 | 
			
		||||
        return <Sidebar variant={sidebarVariant} variantProps={{ onClose }} />;
 | 
			
		||||
      }}
 | 
			
		||||
    >
 | 
			
		||||
      <MainMenu />
 | 
			
		||||
      <MainMenuBase />
 | 
			
		||||
    </ExcalidrawContainer>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,13 @@
 | 
			
		||||
import { MainMenuLink, MainMenuProvider } from '@/Components/Utilities';
 | 
			
		||||
import { MainMenu as MainMenuBase } from '@excalidraw/excalidraw';
 | 
			
		||||
 | 
			
		||||
const MainMenu = () => {
 | 
			
		||||
  return (
 | 
			
		||||
    <MainMenuProvider>
 | 
			
		||||
    <MainMenuBase>
 | 
			
		||||
      <MainMenuBase.DefaultItems.ToggleTheme />
 | 
			
		||||
      <MainMenuBase.DefaultItems.Export />
 | 
			
		||||
      <MainMenuBase.DefaultItems.SaveAsImage />
 | 
			
		||||
      <MainMenuLink href="/license">License</MainMenuLink>
 | 
			
		||||
    </MainMenuProvider>
 | 
			
		||||
      <MainMenuBase.ItemLink href="/license">License</MainMenuBase.ItemLink>
 | 
			
		||||
    </MainMenuBase>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import { ExcalidrawImperativeAPI } from '@excalidraw/excalidraw/types/types';
 | 
			
		||||
import { FC, useCallback } from 'react';
 | 
			
		||||
import { ToggleButton, ToggleButtonGroup } from '@/Components/Utilities';
 | 
			
		||||
import { ToggleButton, ToggleButtonGroup } from '@mui/material';
 | 
			
		||||
import { SidebarVariant, SidebarVariantSchema } from './Sidebar';
 | 
			
		||||
import { z } from 'zod';
 | 
			
		||||
import { LiveCollaborationTrigger } from '@excalidraw/excalidraw';
 | 
			
		||||
@@ -33,7 +33,7 @@ const RightTopUI: FC<RightTopUIProps> = (props) => {
 | 
			
		||||
    <>
 | 
			
		||||
      <LiveCollaborationTrigger
 | 
			
		||||
        isCollaborating={false}
 | 
			
		||||
        onSelect={() => console.log('nyya')}
 | 
			
		||||
        onSelect={() => devlog.log('nyya')}
 | 
			
		||||
      />
 | 
			
		||||
      <ToggleButtonGroup
 | 
			
		||||
        exclusive
 | 
			
		||||
 
 | 
			
		||||
@@ -1,22 +1,23 @@
 | 
			
		||||
import { SidebarProvider } from '@/Components/Utilities';
 | 
			
		||||
import { Sidebar } from '@excalidraw/excalidraw';
 | 
			
		||||
import { FC } from 'react';
 | 
			
		||||
import { z } from 'zod';
 | 
			
		||||
import type { ReactNode } from 'react';
 | 
			
		||||
import { Box } from '@mui/material';
 | 
			
		||||
 | 
			
		||||
export const SidebarBasePropsSchema = z.object({
 | 
			
		||||
  onClose: z.function().returns(z.void()),
 | 
			
		||||
  onClose: z.function().args().returns(z.void()),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export type SidebarBaseProps = z.infer<typeof SidebarBasePropsSchema> & {
 | 
			
		||||
  children?: ReactNode;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const SidebarProviderBase: FC<SidebarBaseProps> = (props) => {
 | 
			
		||||
const SidebarBase: FC<SidebarBaseProps> = (props) => {
 | 
			
		||||
  return (
 | 
			
		||||
    <SidebarProvider dockable={true} onClose={props.onClose}>
 | 
			
		||||
      {props.children}
 | 
			
		||||
    </SidebarProvider>
 | 
			
		||||
    <Sidebar dockable={true} onClose={props.onClose}>
 | 
			
		||||
      <Box sx={{ p: 2 }}>{props.children}</Box>
 | 
			
		||||
    </Sidebar>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default SidebarProviderBase;
 | 
			
		||||
export default SidebarBase;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,16 +1,11 @@
 | 
			
		||||
import { FC, useCallback, useEffect, useState } from 'react';
 | 
			
		||||
import { Button, SidebarHeader } from '@/Components/Utilities';
 | 
			
		||||
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
 | 
			
		||||
import { Sidebar } from '@excalidraw/excalidraw';
 | 
			
		||||
import { z } from 'zod';
 | 
			
		||||
import SidebarProviderBase, { SidebarBasePropsSchema } from './Base';
 | 
			
		||||
import { startCollaboration } from '@/webrtc/collaboration';
 | 
			
		||||
import {
 | 
			
		||||
  SignalingData,
 | 
			
		||||
  decodeSignalingData,
 | 
			
		||||
  encodeSignalingData,
 | 
			
		||||
} from '@/webrtc/utilities';
 | 
			
		||||
import { Input, Link } from '@mui/material';
 | 
			
		||||
import { fromUrl, toUrl } from '@/utilities/url';
 | 
			
		||||
import SidebarBase, { SidebarBasePropsSchema } from './Base';
 | 
			
		||||
import { Button, Input, Link } from '@mui/material';
 | 
			
		||||
import { copyToClipboard } from '@/utilities/clipboard';
 | 
			
		||||
import { useAtom } from 'jotai';
 | 
			
		||||
import { PeerConfigurationAtom, PeerConnectionAtom } from '@/atoms/webrtc';
 | 
			
		||||
 | 
			
		||||
export type CollaborationSidebarProps = z.infer<
 | 
			
		||||
  typeof CollaborationSidebarPropsSchema
 | 
			
		||||
@@ -21,14 +16,18 @@ export const CollaborationSidebarPropsSchema = z
 | 
			
		||||
  .merge(SidebarBasePropsSchema);
 | 
			
		||||
 | 
			
		||||
const CollaborationSidebar: FC<CollaborationSidebarProps> = (props) => {
 | 
			
		||||
  const signalingDataCallback = useCallback((data: SignalingData) => {
 | 
			
		||||
    const encodedData = encodeSignalingData(data);
 | 
			
		||||
    const url = toUrl({ signalingData: encodedData });
 | 
			
		||||
  const [peerConnection, setPeerConnection] = useAtom(PeerConnectionAtom);
 | 
			
		||||
 | 
			
		||||
    setCollaborationUrl(url);
 | 
			
		||||
  const [peerConfiguration] = useAtom(PeerConfigurationAtom);
 | 
			
		||||
 | 
			
		||||
    console.log(data);
 | 
			
		||||
  }, []);
 | 
			
		||||
  const initializePeerConnection = useCallback(() => {
 | 
			
		||||
    if (peerConnection) {
 | 
			
		||||
      devlog.warn('Peer connection already initialized');
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setPeerConnection(new RTCPeerConnection(peerConfiguration));
 | 
			
		||||
  }, [peerConfiguration, peerConnection, setPeerConnection]);
 | 
			
		||||
 | 
			
		||||
  const [collaborationUrl, setCollaborationUrl] = useState<URL | null>(null);
 | 
			
		||||
  const [isCollaborationStarted, setIsCollaborationStarted] = useState(false);
 | 
			
		||||
@@ -36,58 +35,35 @@ const CollaborationSidebar: FC<CollaborationSidebarProps> = (props) => {
 | 
			
		||||
    useState<URL | null>(null);
 | 
			
		||||
 | 
			
		||||
  const onMessage = useCallback((event: MessageEvent) => {
 | 
			
		||||
    console.log(event);
 | 
			
		||||
    devlog.log(event);
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const startCollaborationWrapper = useCallback(async () => {
 | 
			
		||||
    console.log('start collaboration');
 | 
			
		||||
  const copyCollaborationUrl = useCallback(
 | 
			
		||||
    () => copyToClipboard(collaborationUrl?.toString(), undefined),
 | 
			
		||||
    [collaborationUrl]
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
    const signalingData = providedCollaborationUrl
 | 
			
		||||
      ? decodeSignalingData(fromUrl(providedCollaborationUrl).signalingData!)
 | 
			
		||||
      : undefined;
 | 
			
		||||
 | 
			
		||||
    console.log(signalingData);
 | 
			
		||||
 | 
			
		||||
    await startCollaboration(signalingData, signalingDataCallback, {
 | 
			
		||||
      onMessage,
 | 
			
		||||
    });
 | 
			
		||||
  }, [signalingDataCallback, onMessage, providedCollaborationUrl]);
 | 
			
		||||
 | 
			
		||||
  const copyCollaborationUrl = useCallback(async () => {
 | 
			
		||||
    console.log('copy collaboration url');
 | 
			
		||||
    console.log(collaborationUrl?.toString());
 | 
			
		||||
    await copyToClipboard(collaborationUrl ? collaborationUrl?.toString() : '');
 | 
			
		||||
  }, [collaborationUrl]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (isCollaborationStarted || providedCollaborationUrl) {
 | 
			
		||||
      startCollaborationWrapper().catch((error) => console.error(error));
 | 
			
		||||
  // TODO: Check this !!
 | 
			
		||||
  const offerWerapper = useCallback(async () => {
 | 
			
		||||
    if (!peerConnection) {
 | 
			
		||||
      devlog.error('Peer connection not initialized');
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
  }, [
 | 
			
		||||
    isCollaborationStarted,
 | 
			
		||||
    startCollaborationWrapper,
 | 
			
		||||
    providedCollaborationUrl,
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
    const offer = await peerConnection.createOffer();
 | 
			
		||||
    await peerConnection.setLocalDescription(offer);
 | 
			
		||||
 | 
			
		||||
    devlog.log(offer);
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <SidebarProviderBase onClose={props.onClose}>
 | 
			
		||||
      <SidebarHeader>Collaboration</SidebarHeader>
 | 
			
		||||
      <Button onClick={() => setIsCollaborationStarted(true)}>
 | 
			
		||||
        Start Collaboration
 | 
			
		||||
    <SidebarBase onClose={props.onClose}>
 | 
			
		||||
      <Sidebar.Header>Collaboration</Sidebar.Header>
 | 
			
		||||
      <Button onClick={initializePeerConnection}>
 | 
			
		||||
        Initialize a local peer.
 | 
			
		||||
      </Button>
 | 
			
		||||
      <Link>{collaborationUrl?.toString()}</Link>
 | 
			
		||||
      <Button
 | 
			
		||||
        onClick={() => {
 | 
			
		||||
          copyCollaborationUrl().catch((e) => console.error(e));
 | 
			
		||||
        }}
 | 
			
		||||
      >
 | 
			
		||||
        Copy
 | 
			
		||||
      </Button>
 | 
			
		||||
      <Input
 | 
			
		||||
        value={providedCollaborationUrl ?? ''}
 | 
			
		||||
        onChange={(e) => setProvidedCollaborationUrl(new URL(e.target.value))}
 | 
			
		||||
      ></Input>
 | 
			
		||||
    </SidebarProviderBase>
 | 
			
		||||
      <Button> Create a new collaboration session. (offer)</Button>
 | 
			
		||||
    </SidebarBase>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,11 @@
 | 
			
		||||
import { FC } from 'react';
 | 
			
		||||
import { Button, SidebarHeader } from '@/Components/Utilities';
 | 
			
		||||
import { Sidebar } from '@excalidraw/excalidraw';
 | 
			
		||||
import { z } from 'zod';
 | 
			
		||||
import SidebarProviderBase, { SidebarBasePropsSchema } from './Base';
 | 
			
		||||
import SidebarBase, { SidebarBasePropsSchema } from './Base';
 | 
			
		||||
import { Switch, FormControlLabel, FormGroup } from '@mui/material';
 | 
			
		||||
import { useAtom } from 'jotai';
 | 
			
		||||
import { LoggerStateAtom } from '@/atoms/debug';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
 | 
			
		||||
export type GeneralSidebarProps = z.infer<typeof GeneralSidebarPropsSchema>;
 | 
			
		||||
 | 
			
		||||
@@ -10,11 +14,24 @@ export const GeneralSidebarPropsSchema = z
 | 
			
		||||
  .merge(SidebarBasePropsSchema);
 | 
			
		||||
 | 
			
		||||
const GeneralSidebar: FC<GeneralSidebarProps> = (props) => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const [loggerState, setLoggerState] = useAtom(LoggerStateAtom);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <SidebarProviderBase onClose={props.onClose}>
 | 
			
		||||
      <SidebarHeader>General Settings</SidebarHeader>
 | 
			
		||||
      <Button>Button</Button>
 | 
			
		||||
    </SidebarProviderBase>
 | 
			
		||||
    <SidebarBase onClose={props.onClose}>
 | 
			
		||||
      <Sidebar.Header>{t('general-settings')}</Sidebar.Header>
 | 
			
		||||
      <FormGroup>
 | 
			
		||||
        <FormControlLabel
 | 
			
		||||
          control={
 | 
			
		||||
            <Switch
 | 
			
		||||
              checked={loggerState}
 | 
			
		||||
              onChange={(e) => setLoggerState?.(e.target.checked)}
 | 
			
		||||
            />
 | 
			
		||||
          }
 | 
			
		||||
          label={t('toggle-debug-mode')}
 | 
			
		||||
        />
 | 
			
		||||
      </FormGroup>
 | 
			
		||||
    </SidebarBase>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +0,0 @@
 | 
			
		||||
import styled from 'styled-components';
 | 
			
		||||
import {
 | 
			
		||||
  Button as ButtonBase,
 | 
			
		||||
  ToggleButtonGroup as ToggleButtonGroupBase,
 | 
			
		||||
  ToggleButton as ToggleButtonBase,
 | 
			
		||||
} from '@mui/material';
 | 
			
		||||
import { MainMenu, Sidebar } from '@excalidraw/excalidraw';
 | 
			
		||||
 | 
			
		||||
export const SidebarProvider = styled(Sidebar)``;
 | 
			
		||||
export const SidebarHeader = styled(Sidebar.Header)``;
 | 
			
		||||
 | 
			
		||||
export const MainMenuProvider = styled(MainMenu)``;
 | 
			
		||||
export const MainMenuLink = styled(MainMenu.ItemLink)``;
 | 
			
		||||
 | 
			
		||||
export const Button = styled(ButtonBase)``;
 | 
			
		||||
 | 
			
		||||
export const ToggleButtonGroup = styled(ToggleButtonGroupBase)``;
 | 
			
		||||
 | 
			
		||||
export const ToggleButton = styled(ToggleButtonBase)``;
 | 
			
		||||
							
								
								
									
										3
									
								
								src/atoms/debug.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/atoms/debug.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
import { atom } from 'jotai';
 | 
			
		||||
 | 
			
		||||
export const LoggerStateAtom = atom(import.meta.env.DEV);
 | 
			
		||||
							
								
								
									
										3
									
								
								src/atoms/ui.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/atoms/ui.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
import { atom } from 'jotai';
 | 
			
		||||
 | 
			
		||||
export const LangCodeAtom = atom('en');
 | 
			
		||||
							
								
								
									
										10
									
								
								src/atoms/webrtc.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/atoms/webrtc.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
import { DefaultPeerConfiguration } from '@/webrtc/connection';
 | 
			
		||||
import { atom } from 'jotai';
 | 
			
		||||
 | 
			
		||||
export const PeerConnectionAtom = atom<RTCPeerConnection | undefined>(
 | 
			
		||||
  undefined
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
export const PeerConfigurationAtom = atom<RTCConfiguration>(
 | 
			
		||||
  DefaultPeerConfiguration
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										8
									
								
								src/globals.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/globals.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
import type { DevLog } from '@/types/devlog';
 | 
			
		||||
 | 
			
		||||
declare global {
 | 
			
		||||
  interface Window {
 | 
			
		||||
    devlog: DevLog;
 | 
			
		||||
  }
 | 
			
		||||
  let devlog: DevLog;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										36
									
								
								src/i18n.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/i18n.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
			
		||||
import i18n from 'i18next';
 | 
			
		||||
import { initReactI18next } from 'react-i18next';
 | 
			
		||||
 | 
			
		||||
import Backend from 'i18next-http-backend';
 | 
			
		||||
import LanguageDetector from 'i18next-browser-languagedetector';
 | 
			
		||||
 | 
			
		||||
import en from '@/i18n/en/translation.json';
 | 
			
		||||
import ja_JP from '@/i18n/ja_JP/translation.json';
 | 
			
		||||
 | 
			
		||||
i18n
 | 
			
		||||
  .use(Backend)
 | 
			
		||||
  .use(LanguageDetector)
 | 
			
		||||
  .use(initReactI18next)
 | 
			
		||||
  .init({
 | 
			
		||||
    fallbackLng: 'en',
 | 
			
		||||
    returnEmptyString: false,
 | 
			
		||||
    debug: true,
 | 
			
		||||
 | 
			
		||||
    interpolation: {
 | 
			
		||||
      escapeValue: false, // not needed for react as it escapes by default
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    resources: {
 | 
			
		||||
      en: {
 | 
			
		||||
        translation: en,
 | 
			
		||||
      },
 | 
			
		||||
      ja_JP: {
 | 
			
		||||
        translation: ja_JP,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
  .catch((e) => {
 | 
			
		||||
    devlog.error(e);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
export default i18n;
 | 
			
		||||
							
								
								
									
										4
									
								
								src/i18n/en/translation.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/i18n/en/translation.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
{
 | 
			
		||||
  "toggle-debug-mode": "Toggle Debug Mode",
 | 
			
		||||
  "general-settings": "General Settings"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								src/i18n/ja_JP/translation.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/i18n/ja_JP/translation.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
{}
 | 
			
		||||
@@ -1,11 +1,18 @@
 | 
			
		||||
import '@/globals';
 | 
			
		||||
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import ReactDOM from 'react-dom/client';
 | 
			
		||||
import './index.css';
 | 
			
		||||
import { RouterProvider } from 'react-router-dom';
 | 
			
		||||
import router from './routes';
 | 
			
		||||
import DevLogProvider from '@/utilities/DevLogProvider';
 | 
			
		||||
 | 
			
		||||
import '@/i18n';
 | 
			
		||||
 | 
			
		||||
ReactDOM.createRoot(document.getElementById('root')!).render(
 | 
			
		||||
  <React.StrictMode>
 | 
			
		||||
    <DevLogProvider>
 | 
			
		||||
      <RouterProvider router={router} />
 | 
			
		||||
    </DevLogProvider>
 | 
			
		||||
  </React.StrictMode>
 | 
			
		||||
);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								src/types/devlog.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/types/devlog.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
export type Logger = <T>(...args: T[]) => void;
 | 
			
		||||
 | 
			
		||||
export type DevLog = {
 | 
			
		||||
  info: Logger;
 | 
			
		||||
  log: Logger;
 | 
			
		||||
  error: Logger;
 | 
			
		||||
  warn: Logger;
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										65
									
								
								src/types/language.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								src/types/language.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,65 @@
 | 
			
		||||
import { languages } from '@excalidraw/excalidraw';
 | 
			
		||||
import { Language } from '@excalidraw/excalidraw/types/i18n';
 | 
			
		||||
 | 
			
		||||
// Reference:
 | 
			
		||||
// https://github.com/excalidraw/excalidraw/blob/e57dc405fa575a330110699ee762dce5918e9e04/src/i18n.ts#L20
 | 
			
		||||
const LangCodes = [
 | 
			
		||||
  'en',
 | 
			
		||||
  'ar-SA',
 | 
			
		||||
  'bg-BG',
 | 
			
		||||
  'ca-ES',
 | 
			
		||||
  'cs-CZ',
 | 
			
		||||
  'de-DE',
 | 
			
		||||
  'el-GR',
 | 
			
		||||
  'es-ES',
 | 
			
		||||
  'eu-ES',
 | 
			
		||||
  'fa-IR',
 | 
			
		||||
  'fi-FI',
 | 
			
		||||
  'fr-FR',
 | 
			
		||||
  'gl-ES',
 | 
			
		||||
  'he-IL',
 | 
			
		||||
  'hi-IN',
 | 
			
		||||
  'hu-HU',
 | 
			
		||||
  'id-ID',
 | 
			
		||||
  'it-IT',
 | 
			
		||||
  'ja-JP',
 | 
			
		||||
  'kab-KA',
 | 
			
		||||
  'kk-KZ',
 | 
			
		||||
  'ko-KR',
 | 
			
		||||
  'ku-TR',
 | 
			
		||||
  'lt-LT',
 | 
			
		||||
  'lv-LV',
 | 
			
		||||
  'my-MM',
 | 
			
		||||
  'nb-NO',
 | 
			
		||||
  'nl-NL',
 | 
			
		||||
  'nn-NO',
 | 
			
		||||
  'oc-FR',
 | 
			
		||||
  'pa-IN',
 | 
			
		||||
  'pl-PL',
 | 
			
		||||
  'pt-BR',
 | 
			
		||||
  'pt-PT',
 | 
			
		||||
  'ro-RO',
 | 
			
		||||
  'ru-RU',
 | 
			
		||||
  'sk-SK',
 | 
			
		||||
  'sv-SE',
 | 
			
		||||
  'sl-SI',
 | 
			
		||||
  'tr-TR',
 | 
			
		||||
  'uk-UA',
 | 
			
		||||
  'zh-CN',
 | 
			
		||||
  'zh-TW',
 | 
			
		||||
  'vi-VN',
 | 
			
		||||
  'mr-IN',
 | 
			
		||||
] as const;
 | 
			
		||||
 | 
			
		||||
export type LangCode = (typeof LangCodes)[number];
 | 
			
		||||
 | 
			
		||||
export type Languages = {
 | 
			
		||||
  [key in LangCode]: Language;
 | 
			
		||||
}[];
 | 
			
		||||
 | 
			
		||||
export const Languages = languages
 | 
			
		||||
  .map((lang) => (LangCodes.includes(lang.code as LangCode) ? lang : undefined))
 | 
			
		||||
  .filter((item) => item !== undefined)
 | 
			
		||||
  .map((langCode) => ({
 | 
			
		||||
    langCode: languages.find((lang) => lang.code === langCode?.code),
 | 
			
		||||
  }));
 | 
			
		||||
							
								
								
									
										30
									
								
								src/types/utilities.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/types/utilities.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
export type Result<T, E> = Ok<T> | Err<E>;
 | 
			
		||||
 | 
			
		||||
export type Ok<T> = {
 | 
			
		||||
  v: T;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type Err<E> = {
 | 
			
		||||
  e: E;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const ok = <T>(v: T): Ok<T> => ({ v });
 | 
			
		||||
 | 
			
		||||
export const err = <E>(e: E): Err<E> => ({ e });
 | 
			
		||||
 | 
			
		||||
export const isOk = <T, E>(r: Result<T, E>): r is Ok<T> => 'v' in r;
 | 
			
		||||
 | 
			
		||||
export const isErr = <T, E>(r: Result<T, E>): r is Err<E> => 'e' in r;
 | 
			
		||||
 | 
			
		||||
export const unwrap = <T, E>(r: Result<T, E>): T => {
 | 
			
		||||
  if (isOk(r)) return r.v;
 | 
			
		||||
  throw r.e;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const re = <T, E>(r: Result<T, E>) => ({
 | 
			
		||||
  ok: ok(r),
 | 
			
		||||
  err: err(r),
 | 
			
		||||
  isOk: isOk(r),
 | 
			
		||||
  isErr: isErr(r),
 | 
			
		||||
  unwrap: unwrap(r),
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										55
									
								
								src/utilities/DevLogProvider.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/utilities/DevLogProvider.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
			
		||||
import { LoggerStateAtom } from '@/atoms/debug';
 | 
			
		||||
import { useAtom } from 'jotai';
 | 
			
		||||
import { FC, ReactNode, useCallback, useEffect } from 'react';
 | 
			
		||||
import type { Logger } from '@/types/devlog';
 | 
			
		||||
 | 
			
		||||
export type DevLogProviderProps = {
 | 
			
		||||
  children: ReactNode;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const DevLogProvider: FC<DevLogProviderProps> = (props) => {
 | 
			
		||||
  const [loggerState] = useAtom(LoggerStateAtom);
 | 
			
		||||
 | 
			
		||||
  const info: Logger = useCallback(
 | 
			
		||||
    (...args) => {
 | 
			
		||||
      loggerState && console.info(args);
 | 
			
		||||
    },
 | 
			
		||||
    [loggerState]
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const log: Logger = useCallback(
 | 
			
		||||
    (...args) => {
 | 
			
		||||
      loggerState && console.log(args);
 | 
			
		||||
    },
 | 
			
		||||
    [loggerState]
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const error: Logger = useCallback(
 | 
			
		||||
    (...args) => {
 | 
			
		||||
      loggerState && console.error(args);
 | 
			
		||||
    },
 | 
			
		||||
    [loggerState]
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const warn: Logger = useCallback(
 | 
			
		||||
    (...args) => {
 | 
			
		||||
      loggerState && console.warn(args);
 | 
			
		||||
    },
 | 
			
		||||
    [loggerState]
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    window.devlog = {
 | 
			
		||||
      info,
 | 
			
		||||
      log,
 | 
			
		||||
      error,
 | 
			
		||||
      warn,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    devlog = window.devlog;
 | 
			
		||||
  }, [info, log, error, warn]);
 | 
			
		||||
 | 
			
		||||
  return <>{props.children}</>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default DevLogProvider;
 | 
			
		||||
@@ -1,3 +1,23 @@
 | 
			
		||||
export const copyToClipboard = async (text: string) => {
 | 
			
		||||
  await navigator.clipboard.writeText(text);
 | 
			
		||||
export const copyToClipboard = (
 | 
			
		||||
  text: string | undefined | null,
 | 
			
		||||
  callback: (() => void) | undefined
 | 
			
		||||
) => {
 | 
			
		||||
  if (!text) {
 | 
			
		||||
    devlog.log('no text to copy');
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  devlog.log('copy collaboration url');
 | 
			
		||||
  devlog.log(text);
 | 
			
		||||
 | 
			
		||||
  navigator.clipboard
 | 
			
		||||
    .writeText(text)
 | 
			
		||||
    .then(() => {
 | 
			
		||||
      if (callback) {
 | 
			
		||||
        callback();
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
    .catch((err: DOMException) => {
 | 
			
		||||
      devlog.error(`Could not copy text why: ${err.toString()}`);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,28 +0,0 @@
 | 
			
		||||
import establishConnection from './connection';
 | 
			
		||||
import type { Connection, DataChannelCallbacks } from './connection';
 | 
			
		||||
import type { SignalingData } from './utilities';
 | 
			
		||||
 | 
			
		||||
export type SignalingDataCallback = (signalingData: SignalingData) => void;
 | 
			
		||||
 | 
			
		||||
export type OnAnswerCallback = (answer: RTCSessionDescription) => void;
 | 
			
		||||
export type OnOfferCallback = (offer: RTCSessionDescription) => void;
 | 
			
		||||
 | 
			
		||||
export const startCollaboration = async (
 | 
			
		||||
  signalingData: SignalingData | undefined,
 | 
			
		||||
  onSignalingData: SignalingDataCallback,
 | 
			
		||||
  dataChannelCallbacks: DataChannelCallbacks
 | 
			
		||||
): Promise<void> => {
 | 
			
		||||
  let connection: Connection = {
 | 
			
		||||
    connectionState: 'noConnection',
 | 
			
		||||
    dataChannelCallbacks,
 | 
			
		||||
    signalingData,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  while (connection?.connectionState !== 'established') {
 | 
			
		||||
    connection = await establishConnection(connection);
 | 
			
		||||
 | 
			
		||||
    if (connection.signalingData) {
 | 
			
		||||
      onSignalingData(connection.signalingData);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
@@ -1,96 +1,10 @@
 | 
			
		||||
import { match } from 'ts-pattern';
 | 
			
		||||
import { SignalingDataType, SignalingData } from './utilities';
 | 
			
		||||
import { z } from 'zod';
 | 
			
		||||
 | 
			
		||||
export const PeerConfiguration: RTCConfiguration = {
 | 
			
		||||
export const DefaultPeerConfiguration: RTCConfiguration = {
 | 
			
		||||
  iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const EstablishingConnectionStateSchema = z.enum([
 | 
			
		||||
  'noConnection',
 | 
			
		||||
  'waiting',
 | 
			
		||||
  'established',
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
export type EstablishingConnectionState = z.infer<
 | 
			
		||||
  typeof EstablishingConnectionStateSchema
 | 
			
		||||
>;
 | 
			
		||||
 | 
			
		||||
export type Connection = {
 | 
			
		||||
  connectionState: EstablishingConnectionState;
 | 
			
		||||
  signalingData?: SignalingData;
 | 
			
		||||
  dataChannelCallbacks?: DataChannelCallbacks;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const establishConnection = async ({
 | 
			
		||||
  connectionState,
 | 
			
		||||
  signalingData,
 | 
			
		||||
  dataChannelCallbacks,
 | 
			
		||||
}: Connection): Promise<Connection> => {
 | 
			
		||||
  const peerConnection = createPeerConnection();
 | 
			
		||||
 | 
			
		||||
  return await match(connectionState)
 | 
			
		||||
    .with('noConnection', async () => {
 | 
			
		||||
      const _signalingData = await createOffer(peerConnection);
 | 
			
		||||
 | 
			
		||||
      return {
 | 
			
		||||
        connectionState: 'waiting',
 | 
			
		||||
        signalingData: _signalingData,
 | 
			
		||||
        dataChannelCallbacks,
 | 
			
		||||
      } as Connection;
 | 
			
		||||
    })
 | 
			
		||||
    .with('waiting', async () => {
 | 
			
		||||
      if (!dataChannelCallbacks) {
 | 
			
		||||
        throw new Error('Data channel callbacks are not set');
 | 
			
		||||
      }
 | 
			
		||||
      if (!signalingData) {
 | 
			
		||||
        throw new Error('Signaling data is not set');
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      await peerConnection.setRemoteDescription(signalingData.sdp);
 | 
			
		||||
 | 
			
		||||
      const connection: Connection = await match(signalingData.type)
 | 
			
		||||
        .with('offer', async () => {
 | 
			
		||||
          const answer = await createAnswer(peerConnection);
 | 
			
		||||
          return {
 | 
			
		||||
            connectionState: 'waiting',
 | 
			
		||||
            signalingData: answer,
 | 
			
		||||
            dataChannelCallbacks,
 | 
			
		||||
          } as Connection;
 | 
			
		||||
        })
 | 
			
		||||
        .with('answer', () => {
 | 
			
		||||
          return {
 | 
			
		||||
            connectionState: 'established',
 | 
			
		||||
            signalingData: undefined,
 | 
			
		||||
            dataChannelCallbacks,
 | 
			
		||||
          } as Connection;
 | 
			
		||||
        })
 | 
			
		||||
        .exhaustive();
 | 
			
		||||
 | 
			
		||||
      dataChannelHandler(
 | 
			
		||||
        signalingData.type,
 | 
			
		||||
        peerConnection,
 | 
			
		||||
        dataChannelCallbacks
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      return connection;
 | 
			
		||||
    })
 | 
			
		||||
    .with('established', () => {
 | 
			
		||||
      throw new Error(
 | 
			
		||||
        "Connection can't be established because it is already established"
 | 
			
		||||
      );
 | 
			
		||||
    })
 | 
			
		||||
    .exhaustive();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default establishConnection;
 | 
			
		||||
 | 
			
		||||
export const createPeerConnection = () => {
 | 
			
		||||
  const configuration = PeerConfiguration;
 | 
			
		||||
 | 
			
		||||
  return new RTCPeerConnection(configuration);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const createOffer = async (peerConnection: RTCPeerConnection) => {
 | 
			
		||||
  const offer = await peerConnection.createOffer();
 | 
			
		||||
  await peerConnection.setLocalDescription(offer);
 | 
			
		||||
@@ -103,7 +17,7 @@ export const createOffer = async (peerConnection: RTCPeerConnection) => {
 | 
			
		||||
 | 
			
		||||
    return signalingData;
 | 
			
		||||
  } else {
 | 
			
		||||
    throw new Error('Local description is not set');
 | 
			
		||||
    devlog.error('Local description is not set');
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -143,6 +57,6 @@ export const createAnswer = async (peerConnection: RTCPeerConnection) => {
 | 
			
		||||
 | 
			
		||||
    return signalingData;
 | 
			
		||||
  } else {
 | 
			
		||||
    throw new Error('Local description is not set');
 | 
			
		||||
    devlog.error('Local description is not set');
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -4,10 +4,9 @@ import tsconfigPaths from 'vite-tsconfig-paths';
 | 
			
		||||
 | 
			
		||||
// https://vitejs.dev/config/
 | 
			
		||||
export default defineConfig({
 | 
			
		||||
  resolve: {
 | 
			
		||||
    alias: {
 | 
			
		||||
      '@mui/styled-engine': '@mui/styled-engine-sc',
 | 
			
		||||
    },
 | 
			
		||||
  define: {
 | 
			
		||||
    devlog: 'window.devlog',
 | 
			
		||||
    'process.env': {},
 | 
			
		||||
  },
 | 
			
		||||
  optimizeDeps: {
 | 
			
		||||
    include: [
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user