From 3afcb40ae965c55caaeb4b699d0da46a4240ba0a Mon Sep 17 00:00:00 2001 From: Satsuki Akiba Date: Thu, 25 Jan 2024 14:32:12 +0900 Subject: [PATCH] [WIP] Update --- package.json | 7 +- pnpm-lock.yaml | 292 ++++++++++++++++-- src/Components/Excalidraw.tsx | 4 +- src/Components/Excalidraw/MainMenu.tsx | 7 +- src/Components/Excalidraw/RightTopUI.tsx | 4 +- src/Components/Excalidraw/Sidebar/Base.tsx | 15 +- .../Excalidraw/Sidebar/Collaboration.tsx | 100 +++--- src/Components/Excalidraw/Sidebar/General.tsx | 29 +- src/Components/Utilities.tsx | 19 -- src/atoms/debug.ts | 3 + src/atoms/ui.ts | 3 + src/atoms/webrtc.ts | 10 + src/globals.ts | 8 + src/i18n.ts | 36 +++ src/i18n/en/translation.json | 4 + src/i18n/ja_JP/translation.json | 1 + src/main.tsx | 9 +- src/types/devlog.ts | 8 + src/types/language.ts | 65 ++++ src/types/utilities.ts | 30 ++ src/utilities/DevLogProvider.tsx | 55 ++++ src/utilities/clipboard.ts | 24 +- src/webrtc/collaboration.ts | 28 -- src/webrtc/connection.ts | 92 +----- vite.config.ts | 7 +- 25 files changed, 600 insertions(+), 260 deletions(-) delete mode 100644 src/Components/Utilities.tsx create mode 100644 src/atoms/debug.ts create mode 100644 src/atoms/ui.ts create mode 100644 src/atoms/webrtc.ts create mode 100644 src/globals.ts create mode 100644 src/i18n.ts create mode 100644 src/i18n/en/translation.json create mode 100644 src/i18n/ja_JP/translation.json create mode 100644 src/types/devlog.ts create mode 100644 src/types/language.ts create mode 100644 src/types/utilities.ts create mode 100644 src/utilities/DevLogProvider.tsx delete mode 100644 src/webrtc/collaboration.ts diff --git a/package.json b/package.json index a776f45..0b0f796 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5c78dd9..815eede 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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'} diff --git a/src/Components/Excalidraw.tsx b/src/Components/Excalidraw.tsx index d9138c3..c9b470d 100644 --- a/src/Components/Excalidraw.tsx +++ b/src/Components/Excalidraw.tsx @@ -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(); @@ -32,7 +32,7 @@ export const ExcalidrawMain = () => { return ; }} > - + ); }; diff --git a/src/Components/Excalidraw/MainMenu.tsx b/src/Components/Excalidraw/MainMenu.tsx index b93becb..7bb8041 100644 --- a/src/Components/Excalidraw/MainMenu.tsx +++ b/src/Components/Excalidraw/MainMenu.tsx @@ -1,14 +1,13 @@ -import { MainMenuLink, MainMenuProvider } from '@/Components/Utilities'; import { MainMenu as MainMenuBase } from '@excalidraw/excalidraw'; const MainMenu = () => { return ( - + - License - + License + ); }; diff --git a/src/Components/Excalidraw/RightTopUI.tsx b/src/Components/Excalidraw/RightTopUI.tsx index a0b5e62..de1a5bb 100644 --- a/src/Components/Excalidraw/RightTopUI.tsx +++ b/src/Components/Excalidraw/RightTopUI.tsx @@ -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 = (props) => { <> console.log('nyya')} + onSelect={() => devlog.log('nyya')} /> & { children?: ReactNode; }; -const SidebarProviderBase: FC = (props) => { +const SidebarBase: FC = (props) => { return ( - - {props.children} - + + {props.children} + ); }; -export default SidebarProviderBase; +export default SidebarBase; diff --git a/src/Components/Excalidraw/Sidebar/Collaboration.tsx b/src/Components/Excalidraw/Sidebar/Collaboration.tsx index 2e75b94..dd9f0fb 100644 --- a/src/Components/Excalidraw/Sidebar/Collaboration.tsx +++ b/src/Components/Excalidraw/Sidebar/Collaboration.tsx @@ -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 = (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(null); const [isCollaborationStarted, setIsCollaborationStarted] = useState(false); @@ -36,58 +35,35 @@ const CollaborationSidebar: FC = (props) => { useState(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 ( - - Collaboration - - {collaborationUrl?.toString()} - - setProvidedCollaborationUrl(new URL(e.target.value))} - > - + + ); }; diff --git a/src/Components/Excalidraw/Sidebar/General.tsx b/src/Components/Excalidraw/Sidebar/General.tsx index d613889..747fb0e 100644 --- a/src/Components/Excalidraw/Sidebar/General.tsx +++ b/src/Components/Excalidraw/Sidebar/General.tsx @@ -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; @@ -10,11 +14,24 @@ export const GeneralSidebarPropsSchema = z .merge(SidebarBasePropsSchema); const GeneralSidebar: FC = (props) => { + const { t } = useTranslation(); + const [loggerState, setLoggerState] = useAtom(LoggerStateAtom); + return ( - - General Settings - - + + {t('general-settings')} + + setLoggerState?.(e.target.checked)} + /> + } + label={t('toggle-debug-mode')} + /> + + ); }; diff --git a/src/Components/Utilities.tsx b/src/Components/Utilities.tsx deleted file mode 100644 index b7901e4..0000000 --- a/src/Components/Utilities.tsx +++ /dev/null @@ -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)``; diff --git a/src/atoms/debug.ts b/src/atoms/debug.ts new file mode 100644 index 0000000..5727980 --- /dev/null +++ b/src/atoms/debug.ts @@ -0,0 +1,3 @@ +import { atom } from 'jotai'; + +export const LoggerStateAtom = atom(import.meta.env.DEV); diff --git a/src/atoms/ui.ts b/src/atoms/ui.ts new file mode 100644 index 0000000..9c6590b --- /dev/null +++ b/src/atoms/ui.ts @@ -0,0 +1,3 @@ +import { atom } from 'jotai'; + +export const LangCodeAtom = atom('en'); diff --git a/src/atoms/webrtc.ts b/src/atoms/webrtc.ts new file mode 100644 index 0000000..eb4d96b --- /dev/null +++ b/src/atoms/webrtc.ts @@ -0,0 +1,10 @@ +import { DefaultPeerConfiguration } from '@/webrtc/connection'; +import { atom } from 'jotai'; + +export const PeerConnectionAtom = atom( + undefined +); + +export const PeerConfigurationAtom = atom( + DefaultPeerConfiguration +); diff --git a/src/globals.ts b/src/globals.ts new file mode 100644 index 0000000..c106e3d --- /dev/null +++ b/src/globals.ts @@ -0,0 +1,8 @@ +import type { DevLog } from '@/types/devlog'; + +declare global { + interface Window { + devlog: DevLog; + } + let devlog: DevLog; +} diff --git a/src/i18n.ts b/src/i18n.ts new file mode 100644 index 0000000..23808bb --- /dev/null +++ b/src/i18n.ts @@ -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; diff --git a/src/i18n/en/translation.json b/src/i18n/en/translation.json new file mode 100644 index 0000000..13ea2ef --- /dev/null +++ b/src/i18n/en/translation.json @@ -0,0 +1,4 @@ +{ + "toggle-debug-mode": "Toggle Debug Mode", + "general-settings": "General Settings" +} diff --git a/src/i18n/ja_JP/translation.json b/src/i18n/ja_JP/translation.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n/ja_JP/translation.json @@ -0,0 +1 @@ +{} diff --git a/src/main.tsx b/src/main.tsx index f5fc816..39f780d 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -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( - + + + ); diff --git a/src/types/devlog.ts b/src/types/devlog.ts new file mode 100644 index 0000000..2ba9106 --- /dev/null +++ b/src/types/devlog.ts @@ -0,0 +1,8 @@ +export type Logger = (...args: T[]) => void; + +export type DevLog = { + info: Logger; + log: Logger; + error: Logger; + warn: Logger; +}; diff --git a/src/types/language.ts b/src/types/language.ts new file mode 100644 index 0000000..359b96a --- /dev/null +++ b/src/types/language.ts @@ -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), + })); diff --git a/src/types/utilities.ts b/src/types/utilities.ts new file mode 100644 index 0000000..7869007 --- /dev/null +++ b/src/types/utilities.ts @@ -0,0 +1,30 @@ +export type Result = Ok | Err; + +export type Ok = { + v: T; +}; + +export type Err = { + e: E; +}; + +export const ok = (v: T): Ok => ({ v }); + +export const err = (e: E): Err => ({ e }); + +export const isOk = (r: Result): r is Ok => 'v' in r; + +export const isErr = (r: Result): r is Err => 'e' in r; + +export const unwrap = (r: Result): T => { + if (isOk(r)) return r.v; + throw r.e; +}; + +export const re = (r: Result) => ({ + ok: ok(r), + err: err(r), + isOk: isOk(r), + isErr: isErr(r), + unwrap: unwrap(r), +}); diff --git a/src/utilities/DevLogProvider.tsx b/src/utilities/DevLogProvider.tsx new file mode 100644 index 0000000..1d79ff8 --- /dev/null +++ b/src/utilities/DevLogProvider.tsx @@ -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 = (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; diff --git a/src/utilities/clipboard.ts b/src/utilities/clipboard.ts index 7225f31..573acf2 100644 --- a/src/utilities/clipboard.ts +++ b/src/utilities/clipboard.ts @@ -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()}`); + }); }; diff --git a/src/webrtc/collaboration.ts b/src/webrtc/collaboration.ts deleted file mode 100644 index 32a782d..0000000 --- a/src/webrtc/collaboration.ts +++ /dev/null @@ -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 => { - let connection: Connection = { - connectionState: 'noConnection', - dataChannelCallbacks, - signalingData, - }; - - while (connection?.connectionState !== 'established') { - connection = await establishConnection(connection); - - if (connection.signalingData) { - onSignalingData(connection.signalingData); - } - } -}; diff --git a/src/webrtc/connection.ts b/src/webrtc/connection.ts index 4b2a89e..f077fac 100644 --- a/src/webrtc/connection.ts +++ b/src/webrtc/connection.ts @@ -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 => { - 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'); } }; diff --git a/vite.config.ts b/vite.config.ts index dbaa08e..bdfb9f1 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -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: [