From ca45288b5d449e630f6cec95b9ba65ebe2f73009 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 22 Mar 2024 07:18:29 +0300 Subject: [PATCH 1/3] ci: update preview message --- .github/workflows/preview.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index 1db1bb9e4..8fcfbdc28 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -45,3 +45,5 @@ jobs: with: message: | Deployed to Vercel Preview: ${{ steps.deploy.outputs.stdout }} + [Playground](${{ steps.deploy.outputs.stdout }}/playground.html) + [Storybook](${{ steps.deploy.outputs.stdout }}/storybook/) From 85c0eb8c5b74359c39903f8a4bb0b221fb0756fd Mon Sep 17 00:00:00 2001 From: gguio <109200692+gguio@users.noreply.github.com> Date: Sat, 23 Mar 2024 17:21:36 +0400 Subject: [PATCH 2/3] feat: Sign editor with formatting support! (+2 settings) (#86) Co-authored-by: gguio Co-authored-by: Vitaly Turovsky --- .storybook/preview.tsx | 9 +- package.json | 5 + pnpm-lock.yaml | 541 +++++++++++++++++++++++++++- src/controls.ts | 9 + src/markdownToFormattedText.test.ts | 39 ++ src/markdownToFormattedText.ts | 58 +++ src/optionsGuiScheme.tsx | 16 + src/optionsStorage.ts | 3 + src/react/SignEditor.css | 115 ++++++ src/react/SignEditor.stories.tsx | 25 ++ src/react/SignEditor.tsx | 81 +++++ src/react/SignEditorProvider.tsx | 84 +++++ src/react/prosemirror-markdown.ts | 77 ++++ src/reactUi.tsx | 6 +- vitest.config.ts | 1 + 15 files changed, 1060 insertions(+), 9 deletions(-) create mode 100644 src/markdownToFormattedText.test.ts create mode 100644 src/markdownToFormattedText.ts create mode 100644 src/react/SignEditor.css create mode 100644 src/react/SignEditor.stories.tsx create mode 100644 src/react/SignEditor.tsx create mode 100644 src/react/SignEditorProvider.tsx create mode 100644 src/react/prosemirror-markdown.ts diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 1241b47ce..a8a219f14 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -7,11 +7,12 @@ import './storybook.css' const preview: Preview = { decorators: [ - (Story) => ( -
+ (Story, c) => { + const noScaling = c.parameters.noScaling + return
-
- ), +
; + }, ], parameters: { actions: { argTypesRegex: "^on[A-Z].*" }, diff --git a/package.json b/package.json index 6bf9a456c..8d6bfbf6c 100644 --- a/package.json +++ b/package.json @@ -62,11 +62,16 @@ "node-gzip": "^1.1.2", "peerjs": "^1.5.0", "pretty-bytes": "^6.1.1", + "prosemirror-example-setup": "^1.2.2", + "prosemirror-markdown": "^1.12.0", + "prosemirror-state": "^1.4.3", + "prosemirror-view": "^1.33.1", "prismarine-provider-anvil": "github:zardoy/prismarine-provider-anvil#everything", "qrcode.react": "^3.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-transition-group": "^4.4.5", + "remark": "^15.0.1", "sanitize-filename": "^1.6.3", "skinview3d": "^3.0.1", "source-map-js": "^1.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 33f87a073..759690ffe 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -126,6 +126,18 @@ importers: prismarine-provider-anvil: specifier: github:zardoy/prismarine-provider-anvil#everything version: github.com/zardoy/prismarine-provider-anvil/0ddcd9d48574113308e1fbebef60816aced0846f(minecraft-data@3.62.0) + prosemirror-example-setup: + specifier: ^1.2.2 + version: 1.2.2 + prosemirror-markdown: + specifier: ^1.12.0 + version: 1.12.0 + prosemirror-state: + specifier: ^1.4.3 + version: 1.4.3 + prosemirror-view: + specifier: ^1.33.1 + version: 1.33.1 qrcode.react: specifier: ^3.1.0 version: 3.1.0(react@18.2.0) @@ -138,6 +150,9 @@ importers: react-transition-group: specifier: ^4.4.5 version: 4.4.5(react-dom@18.2.0)(react@18.2.0) + remark: + specifier: ^15.0.1 + version: 15.0.1 sanitize-filename: specifier: ^1.6.3 version: 1.6.3 @@ -4992,6 +5007,12 @@ packages: '@types/node': 20.8.10 dev: true + /@types/debug@4.1.12: + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + dependencies: + '@types/ms': 0.7.34 + dev: false + /@types/detect-port@1.3.3: resolution: {integrity: sha512-bV/jQlAJ/nPY3XqSatkGpu+nGzou+uSwrH1cROhn+jBFg47yaNH+blW4C7p9KhopC7QxCv/6M86s37k8dMk0Yg==} dev: true @@ -5113,6 +5134,12 @@ packages: resolution: {integrity: sha512-Vrjz5N5Ia4SEzWWgIVwnHNEnb1UE1XMkvY5DGXrAeOGE9imk0hgTHh5GyDjLDJi9OTCn9oo9dXH1uToK1VRfrg==} dev: true + /@types/mdast@4.0.3: + resolution: {integrity: sha512-LsjtqsyF+d2/yFOYaN22dHZI1Cpwkrj+g06G8+qtUKlhovPW89YhqSnfKtMbkgmEtYpH2gydRNULd6y8mciAFg==} + dependencies: + '@types/unist': 2.0.8 + dev: false + /@types/mdx@2.0.8: resolution: {integrity: sha512-r7/zWe+f9x+zjXqGxf821qz++ld8tp6Z4jUS6qmPZUXH6tfh4riXOhAqb12tWGWAevCFtMt1goLWkQMqIJKpsA==} dev: true @@ -5137,6 +5164,10 @@ packages: resolution: {integrity: sha512-ZYFzrvyWUNhaPomn80dsMNgMeXxNWZBdkuG/hWlUvXvbdUH8ZERNBGXnU87McuGcWDsyzX2aChCv/SVN348k3A==} dev: true + /@types/ms@0.7.34: + resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} + dev: false + /@types/node-fetch@2.6.6: resolution: {integrity: sha512-95X8guJYhfqiuVVhRFxVQcf4hW/2bCuoPwDasMf/531STFoNoWTT7YDnWdXHEZKqAGUigmpG31r2FE70LwnzJw==} dependencies: @@ -5293,7 +5324,10 @@ packages: /@types/unist@2.0.8: resolution: {integrity: sha512-d0XxK3YTObnWVp6rZuev3c49+j4Lo8g4L1ZRm9z5L0xpoZycUPshHgczK5gsUMaZOstjVYYi09p5gYvUtfChYw==} - dev: true + + /@types/unist@3.0.2: + resolution: {integrity: sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==} + dev: false /@types/webxr@0.5.7: resolution: {integrity: sha512-Rcgs5c2eNFnHp53YOjgtKfl/zWX1Y+uFGUwlSXrWcZWu3yhANRezmph4MninmqybUYT6g9ZE0aQ9QIdPkLR3Kg==} @@ -5944,7 +5978,6 @@ packages: /argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - dev: true /aria-hidden@1.2.3: resolution: {integrity: sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==} @@ -6226,6 +6259,10 @@ packages: resolution: {integrity: sha512-zj6Z6M7Eq+PBZ7PQxl5NT665MvJdAkzp0f60nAJ+sLaSCBPMwVak5ZegFbgVCzFcCJTKFoMizvM5Ld7+JrRJHA==} dev: false + /bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + dev: false + /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -6715,6 +6752,10 @@ packages: resolution: {integrity: sha512-CAtbGEDulyjzs05RXy3uKcwqeztz/dMEuAc1Xu9NQBsbrhuGMneL0u9Dj5SoutLKBFYun8txxYIwhjtLNfUmCA==} dev: false + /character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + dev: false + /check-error@1.0.3: resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} dependencies: @@ -7103,6 +7144,10 @@ packages: sha.js: 2.4.11 dev: true + /crelt@1.0.6: + resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} + dev: false + /cross-spawn@6.0.5: resolution: {integrity: sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==} engines: {node: '>=4.8'} @@ -7342,6 +7387,12 @@ packages: engines: {node: '>=10'} dev: true + /decode-named-character-reference@1.0.2: + resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} + dependencies: + character-entities: 2.0.2 + dev: false + /decompress-response@4.2.1: resolution: {integrity: sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==} engines: {node: '>=8'} @@ -7460,7 +7511,6 @@ packages: /dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} - dev: true /des.js@1.1.0: resolution: {integrity: sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==} @@ -7515,6 +7565,12 @@ packages: - supports-color dev: true + /devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + dependencies: + dequal: 2.0.3 + dev: false + /diff-sequences@29.6.3: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -7812,7 +7868,6 @@ packages: /entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} - dev: true /env-paths@2.2.1: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} @@ -9989,6 +10044,11 @@ packages: engines: {node: '>=0.10.0'} dev: true + /is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + dev: false + /is-plain-object@2.0.4: resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} engines: {node: '>=0.10.0'} @@ -10500,6 +10560,12 @@ packages: /lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + /linkify-it@5.0.0: + resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + dependencies: + uc.micro: 2.1.0 + dev: false + /listr2@3.14.0(enquirer@2.4.1): resolution: {integrity: sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==} engines: {node: '>=10.0.0'} @@ -10655,6 +10721,10 @@ packages: resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==} dev: false + /longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + dev: false + /looks-same@8.2.3: resolution: {integrity: sha512-0LK5r4+9t2D56XPVNH3hhG4o0yBYUdeu9FEd8z0ZCs/2fR9zJQj+6ob6ued8iHk3yddrSAdUA+9YGVK2FBMGUw==} engines: {node: '>= 12.0.0'} @@ -10800,6 +10870,18 @@ packages: resolution: {integrity: sha512-0aF7ZmVon1igznGI4VS30yugpduQW3y3GkcgGJOp7d8x8QrizhigUxjI/m2UojsXXto+jLAH3KSz+xOJTiORjg==} dev: true + /markdown-it@14.0.0: + resolution: {integrity: sha512-seFjF0FIcPt4P9U39Bq1JYblX0KZCjDLFFQPHpL5AzHpqPEKtosxmdq/LTVZnjfH7tjt9BxStm+wXcDBNuYmzw==} + hasBin: true + dependencies: + argparse: 2.0.1 + entities: 4.5.0 + linkify-it: 5.0.0 + mdurl: 2.0.0 + punycode.js: 2.3.1 + uc.micro: 2.1.0 + dev: false + /markdown-to-jsx@7.3.2(react@18.2.0): resolution: {integrity: sha512-B+28F5ucp83aQm+OxNrPkS8z0tMKaeHiy0lHJs3LqCyDQFtWuenaIrkaVTgAm1pf1AU85LXltva86hlaT17i8Q==} engines: {node: '>= 10'} @@ -10829,14 +10911,63 @@ packages: unist-util-visit: 2.0.3 dev: true + /mdast-util-from-markdown@2.0.0: + resolution: {integrity: sha512-n7MTOr/z+8NAX/wmhhDji8O3bRvPTV/U0oTCaZJkjhPSKTPhS3xufVhKGF8s1pJ7Ox4QgoIU7KHseh09S+9rTA==} + dependencies: + '@types/mdast': 4.0.3 + '@types/unist': 3.0.2 + decode-named-character-reference: 1.0.2 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.0 + micromark-util-decode-numeric-character-reference: 2.0.1 + micromark-util-decode-string: 2.0.0 + micromark-util-normalize-identifier: 2.0.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + dev: false + + /mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + dependencies: + '@types/mdast': 4.0.3 + unist-util-is: 6.0.0 + dev: false + + /mdast-util-to-markdown@2.1.0: + resolution: {integrity: sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==} + dependencies: + '@types/mdast': 4.0.3 + '@types/unist': 3.0.2 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-decode-string: 2.0.0 + unist-util-visit: 5.0.0 + zwitch: 2.0.4 + dev: false + /mdast-util-to-string@1.1.0: resolution: {integrity: sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A==} dev: true + /mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + dependencies: + '@types/mdast': 4.0.3 + dev: false + /mdn-data@2.0.14: resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} dev: false + /mdurl@2.0.0: + resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + dev: false + /media-typer@0.3.0: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} @@ -10892,6 +11023,181 @@ packages: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} engines: {node: '>= 0.6'} + /micromark-core-commonmark@2.0.0: + resolution: {integrity: sha512-jThOz/pVmAYUtkroV3D5c1osFXAMv9e0ypGDOIZuCeAe91/sD6BoE2Sjzt30yuXtwOYUmySOhMas/PVyh02itA==} + dependencies: + decode-named-character-reference: 1.0.2 + devlop: 1.1.0 + micromark-factory-destination: 2.0.0 + micromark-factory-label: 2.0.0 + micromark-factory-space: 2.0.0 + micromark-factory-title: 2.0.0 + micromark-factory-whitespace: 2.0.0 + micromark-util-character: 2.1.0 + micromark-util-chunked: 2.0.0 + micromark-util-classify-character: 2.0.0 + micromark-util-html-tag-name: 2.0.0 + micromark-util-normalize-identifier: 2.0.0 + micromark-util-resolve-all: 2.0.0 + micromark-util-subtokenize: 2.0.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-factory-destination@2.0.0: + resolution: {integrity: sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==} + dependencies: + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-factory-label@2.0.0: + resolution: {integrity: sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==} + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-factory-space@2.0.0: + resolution: {integrity: sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==} + dependencies: + micromark-util-character: 2.1.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-factory-title@2.0.0: + resolution: {integrity: sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==} + dependencies: + micromark-factory-space: 2.0.0 + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-factory-whitespace@2.0.0: + resolution: {integrity: sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==} + dependencies: + micromark-factory-space: 2.0.0 + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-util-character@2.1.0: + resolution: {integrity: sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==} + dependencies: + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-util-chunked@2.0.0: + resolution: {integrity: sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==} + dependencies: + micromark-util-symbol: 2.0.0 + dev: false + + /micromark-util-classify-character@2.0.0: + resolution: {integrity: sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==} + dependencies: + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-util-combine-extensions@2.0.0: + resolution: {integrity: sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==} + dependencies: + micromark-util-chunked: 2.0.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-util-decode-numeric-character-reference@2.0.1: + resolution: {integrity: sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ==} + dependencies: + micromark-util-symbol: 2.0.0 + dev: false + + /micromark-util-decode-string@2.0.0: + resolution: {integrity: sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA==} + dependencies: + decode-named-character-reference: 1.0.2 + micromark-util-character: 2.1.0 + micromark-util-decode-numeric-character-reference: 2.0.1 + micromark-util-symbol: 2.0.0 + dev: false + + /micromark-util-encode@2.0.0: + resolution: {integrity: sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==} + dev: false + + /micromark-util-html-tag-name@2.0.0: + resolution: {integrity: sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==} + dev: false + + /micromark-util-normalize-identifier@2.0.0: + resolution: {integrity: sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==} + dependencies: + micromark-util-symbol: 2.0.0 + dev: false + + /micromark-util-resolve-all@2.0.0: + resolution: {integrity: sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==} + dependencies: + micromark-util-types: 2.0.0 + dev: false + + /micromark-util-sanitize-uri@2.0.0: + resolution: {integrity: sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==} + dependencies: + micromark-util-character: 2.1.0 + micromark-util-encode: 2.0.0 + micromark-util-symbol: 2.0.0 + dev: false + + /micromark-util-subtokenize@2.0.0: + resolution: {integrity: sha512-vc93L1t+gpR3p8jxeVdaYlbV2jTYteDje19rNSS/H5dlhxUYll5Fy6vJ2cDwP8RnsXi818yGty1ayP55y3W6fg==} + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-util-symbol@2.0.0: + resolution: {integrity: sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==} + dev: false + + /micromark-util-types@2.0.0: + resolution: {integrity: sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==} + dev: false + + /micromark@4.0.0: + resolution: {integrity: sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==} + dependencies: + '@types/debug': 4.1.12 + debug: 4.3.4(supports-color@8.1.1) + decode-named-character-reference: 1.0.2 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.0 + micromark-factory-space: 2.0.0 + micromark-util-character: 2.1.0 + micromark-util-chunked: 2.0.0 + micromark-util-combine-extensions: 2.0.0 + micromark-util-decode-numeric-character-reference: 2.0.1 + micromark-util-encode: 2.0.0 + micromark-util-normalize-identifier: 2.0.0 + micromark-util-resolve-all: 2.0.0 + micromark-util-sanitize-uri: 2.0.0 + micromark-util-subtokenize: 2.0.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + transitivePeerDependencies: + - supports-color + dev: false + /micromatch@4.0.5: resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} engines: {node: '>=8.6'} @@ -11618,6 +11924,10 @@ packages: wcwidth: 1.0.1 dev: true + /orderedmap@2.1.1: + resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==} + dev: false + /os-browserify@0.3.0: resolution: {integrity: sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==} dev: true @@ -12252,6 +12562,120 @@ packages: object-assign: 4.1.1 react-is: 16.13.1 + /prosemirror-commands@1.5.2: + resolution: {integrity: sha512-hgLcPaakxH8tu6YvVAaILV2tXYsW3rAdDR8WNkeKGcgeMVQg3/TMhPdVoh7iAmfgVjZGtcOSjKiQaoeKjzd2mQ==} + dependencies: + prosemirror-model: 1.19.4 + prosemirror-state: 1.4.3 + prosemirror-transform: 1.8.0 + dev: false + + /prosemirror-dropcursor@1.8.1: + resolution: {integrity: sha512-M30WJdJZLyXHi3N8vxN6Zh5O8ZBbQCz0gURTfPmTIBNQ5pxrdU7A58QkNqfa98YEjSAL1HUyyU34f6Pm5xBSGw==} + dependencies: + prosemirror-state: 1.4.3 + prosemirror-transform: 1.8.0 + prosemirror-view: 1.33.1 + dev: false + + /prosemirror-example-setup@1.2.2: + resolution: {integrity: sha512-pHJc656IgYm249RNp0eQaWNmnyWGk6OrzysWfYI4+NwE14HQ7YNYOlRBLErUS6uCAHIYJLNXf0/XCmf1OCtNbQ==} + dependencies: + prosemirror-commands: 1.5.2 + prosemirror-dropcursor: 1.8.1 + prosemirror-gapcursor: 1.3.2 + prosemirror-history: 1.3.2 + prosemirror-inputrules: 1.4.0 + prosemirror-keymap: 1.2.2 + prosemirror-menu: 1.2.4 + prosemirror-schema-list: 1.3.0 + prosemirror-state: 1.4.3 + dev: false + + /prosemirror-gapcursor@1.3.2: + resolution: {integrity: sha512-wtjswVBd2vaQRrnYZaBCbyDqr232Ed4p2QPtRIUK5FuqHYKGWkEwl08oQM4Tw7DOR0FsasARV5uJFvMZWxdNxQ==} + dependencies: + prosemirror-keymap: 1.2.2 + prosemirror-model: 1.19.4 + prosemirror-state: 1.4.3 + prosemirror-view: 1.33.1 + dev: false + + /prosemirror-history@1.3.2: + resolution: {integrity: sha512-/zm0XoU/N/+u7i5zepjmZAEnpvjDtzoPWW6VmKptcAnPadN/SStsBjMImdCEbb3seiNTpveziPTIrXQbHLtU1g==} + dependencies: + prosemirror-state: 1.4.3 + prosemirror-transform: 1.8.0 + prosemirror-view: 1.33.1 + rope-sequence: 1.3.4 + dev: false + + /prosemirror-inputrules@1.4.0: + resolution: {integrity: sha512-6ygpPRuTJ2lcOXs9JkefieMst63wVJBgHZGl5QOytN7oSZs3Co/BYbc3Yx9zm9H37Bxw8kVzCnDsihsVsL4yEg==} + dependencies: + prosemirror-state: 1.4.3 + prosemirror-transform: 1.8.0 + dev: false + + /prosemirror-keymap@1.2.2: + resolution: {integrity: sha512-EAlXoksqC6Vbocqc0GtzCruZEzYgrn+iiGnNjsJsH4mrnIGex4qbLdWWNza3AW5W36ZRrlBID0eM6bdKH4OStQ==} + dependencies: + prosemirror-state: 1.4.3 + w3c-keyname: 2.2.8 + dev: false + + /prosemirror-markdown@1.12.0: + resolution: {integrity: sha512-6F5HS8Z0HDYiS2VQDZzfZP6A0s/I0gbkJy8NCzzDMtcsz3qrfqyroMMeoSjAmOhDITyon11NbXSzztfKi+frSQ==} + dependencies: + markdown-it: 14.0.0 + prosemirror-model: 1.19.4 + dev: false + + /prosemirror-menu@1.2.4: + resolution: {integrity: sha512-S/bXlc0ODQup6aiBbWVsX/eM+xJgCTAfMq/nLqaO5ID/am4wS0tTCIkzwytmao7ypEtjj39i7YbJjAgO20mIqA==} + dependencies: + crelt: 1.0.6 + prosemirror-commands: 1.5.2 + prosemirror-history: 1.3.2 + prosemirror-state: 1.4.3 + dev: false + + /prosemirror-model@1.19.4: + resolution: {integrity: sha512-RPmVXxUfOhyFdayHawjuZCxiROsm9L4FCUA6pWI+l7n2yCBsWy9VpdE1hpDHUS8Vad661YLY9AzqfjLhAKQ4iQ==} + dependencies: + orderedmap: 2.1.1 + dev: false + + /prosemirror-schema-list@1.3.0: + resolution: {integrity: sha512-Hz/7gM4skaaYfRPNgr421CU4GSwotmEwBVvJh5ltGiffUJwm7C8GfN/Bc6DR1EKEp5pDKhODmdXXyi9uIsZl5A==} + dependencies: + prosemirror-model: 1.19.4 + prosemirror-state: 1.4.3 + prosemirror-transform: 1.8.0 + dev: false + + /prosemirror-state@1.4.3: + resolution: {integrity: sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==} + dependencies: + prosemirror-model: 1.19.4 + prosemirror-transform: 1.8.0 + prosemirror-view: 1.33.1 + dev: false + + /prosemirror-transform@1.8.0: + resolution: {integrity: sha512-BaSBsIMv52F1BVVMvOmp1yzD3u65uC3HTzCBQV1WDPqJRQ2LuHKcyfn0jwqodo8sR9vVzMzZyI+Dal5W9E6a9A==} + dependencies: + prosemirror-model: 1.19.4 + dev: false + + /prosemirror-view@1.33.1: + resolution: {integrity: sha512-62qkYgSJIkwIMMCpuGuPzc52DiK1Iod6TWoIMxP4ja6BTD4yO8kCUL64PZ/WhH/dJ9fW0CDO39FhH1EMyhUFEg==} + dependencies: + prosemirror-model: 1.19.4 + prosemirror-state: 1.4.3 + prosemirror-transform: 1.8.0 + dev: false + /protodef-validator@1.3.1: resolution: {integrity: sha512-lZ5FWKZYR9xOjpMw1+EfZRfCjzNRQWPq+Dk+jki47Sikl2EeWEPnTfnJERwnU/EwFq6us+0zqHHzSsmLeYX+Lg==} hasBin: true @@ -12315,6 +12739,11 @@ packages: pump: 2.0.1 dev: true + /punycode.js@2.3.1: + resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} + engines: {node: '>=6'} + dev: false + /punycode@2.3.0: resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} engines: {node: '>=6'} @@ -12920,6 +13349,17 @@ packages: unist-util-visit: 2.0.3 dev: true + /remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + dependencies: + '@types/mdast': 4.0.3 + mdast-util-from-markdown: 2.0.0 + micromark-util-types: 2.0.0 + unified: 11.0.4 + transitivePeerDependencies: + - supports-color + dev: false + /remark-slug@6.1.0: resolution: {integrity: sha512-oGCxDF9deA8phWvxFuyr3oSJsdyUAxMFbA0mZ7Y1Sas+emILtO+e5WutF9564gDsEN4IXaQXm5pFo6MLH+YmwQ==} dependencies: @@ -12928,6 +13368,25 @@ packages: unist-util-visit: 2.0.3 dev: true + /remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + dependencies: + '@types/mdast': 4.0.3 + mdast-util-to-markdown: 2.1.0 + unified: 11.0.4 + dev: false + + /remark@15.0.1: + resolution: {integrity: sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A==} + dependencies: + '@types/mdast': 4.0.3 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.4 + transitivePeerDependencies: + - supports-color + dev: false + /request-progress@3.0.0: resolution: {integrity: sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==} dependencies: @@ -13066,6 +13525,10 @@ packages: optionalDependencies: fsevents: 2.3.3 + /rope-sequence@1.3.4: + resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} + dev: false + /rtl-css-js@1.16.1: resolution: {integrity: sha512-lRQgou1mu19e+Ya0LsTvKrVJ5TYUbqCVPAiImX3UfLTenarvPUl1QFdvu5Z3PYmHT9RCcwIfbjRQBntExyj3Zg==} dependencies: @@ -14162,6 +14625,10 @@ packages: engines: {node: '>=12'} dev: true + /trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + dev: false + /truncate-utf8-bytes@1.0.2: resolution: {integrity: sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==} dependencies: @@ -14337,6 +14804,10 @@ packages: resolution: {integrity: sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==} dev: false + /uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + dev: false + /ufo@1.3.1: resolution: {integrity: sha512-uY/99gMLIOlJPwATcMVYfqDSxUR9//AUcgZMzwfSTJPDKzA1S8mX4VLqa+fiAtveraQUBCz4FFcwVZBGbwBXIw==} dev: true @@ -14396,6 +14867,18 @@ packages: diff: 2.2.3 dev: false + /unified@11.0.4: + resolution: {integrity: sha512-apMPnyLjAX+ty4OrNap7yumyVAMlKx5IWU2wlzzUdYJO9A8f1p9m/gywF/GM2ZDFcjQPrx59Mc90KwmxsoklxQ==} + dependencies: + '@types/unist': 3.0.2 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.1 + dev: false + /union@0.5.0: resolution: {integrity: sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==} engines: {node: '>= 0.8.0'} @@ -14431,6 +14914,18 @@ packages: resolution: {integrity: sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==} dev: true + /unist-util-is@6.0.0: + resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} + dependencies: + '@types/unist': 3.0.2 + dev: false + + /unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + dependencies: + '@types/unist': 3.0.2 + dev: false + /unist-util-visit-parents@3.1.1: resolution: {integrity: sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==} dependencies: @@ -14438,6 +14933,13 @@ packages: unist-util-is: 4.1.0 dev: true + /unist-util-visit-parents@6.0.1: + resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==} + dependencies: + '@types/unist': 3.0.2 + unist-util-is: 6.0.0 + dev: false + /unist-util-visit@2.0.3: resolution: {integrity: sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==} dependencies: @@ -14446,6 +14948,14 @@ packages: unist-util-visit-parents: 3.1.1 dev: true + /unist-util-visit@5.0.0: + resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + dependencies: + '@types/unist': 3.0.2 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 + dev: false + /universalify@0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} @@ -14680,6 +15190,21 @@ packages: core-util-is: 1.0.2 extsprintf: 1.3.0 + /vfile-message@4.0.2: + resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} + dependencies: + '@types/unist': 3.0.2 + unist-util-stringify-position: 4.0.0 + dev: false + + /vfile@6.0.1: + resolution: {integrity: sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==} + dependencies: + '@types/unist': 3.0.2 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.2 + dev: false + /vite-node@0.34.6(@types/node@20.8.0): resolution: {integrity: sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==} engines: {node: '>=v14.18.0'} @@ -14837,6 +15362,10 @@ packages: - terser dev: true + /w3c-keyname@2.2.8: + resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + dev: false + /walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} dependencies: @@ -15389,6 +15918,10 @@ packages: react: 18.2.0 dev: false + /zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + dev: false + github.com/PrismarineJS/mineflayer/195b3cbd70a110080af9b77a4659991c5d9e484a: resolution: {tarball: https://codeload.github.com/PrismarineJS/mineflayer/tar.gz/195b3cbd70a110080af9b77a4659991c5d9e484a} name: mineflayer diff --git a/src/controls.ts b/src/controls.ts index a02f76c4b..48c051791 100644 --- a/src/controls.ts +++ b/src/controls.ts @@ -22,6 +22,10 @@ subscribe(customKeymaps, () => { localStorage.keymap = JSON.parse(customKeymaps) }) +const controlOptions = { + preventDefault: true +} + export const contro = new ControMax({ commands: { general: { @@ -58,6 +62,7 @@ export const contro = new ControMax({ } }, }, { + defaultControlOptions: controlOptions, target: document, captureEvents () { return bot && isGameActive(false) @@ -71,6 +76,10 @@ export const contro = new ControMax({ window.controMax = contro export type Command = CommandEventArgument['command'] +export const setDoPreventDefault = (state: boolean) => { + controlOptions.preventDefault = state +} + const setSprinting = (state: boolean) => { bot.setControlState('sprint', state) gameAdditionalState.isSprinting = state diff --git a/src/markdownToFormattedText.test.ts b/src/markdownToFormattedText.test.ts new file mode 100644 index 000000000..5c1d5f35e --- /dev/null +++ b/src/markdownToFormattedText.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, it } from 'vitest' +import markdownToFormattedText from './markdownToFormattedText' + +describe('markdownToFormattedText', () => { + it('should convert markdown to formatted text', () => { + const markdown = '**bold** *italic* [link](https://example.com) k `code`' + const text = markdownToFormattedText(markdown) + const command = '/data merge block ~ ~ ~ {Text1:\'' + JSON.stringify(text[0]) + '\',Text2: \'' + JSON.stringify(text[1]) + '\',Text3:\'' + JSON.stringify(text[2]) + '\',Text4:\'' + JSON.stringify(text[3]) + '\'}' // mojangson + expect(text).toMatchInlineSnapshot(` + [ + [ + [ + { + "bold": true, + "text": "bold", + }, + { + "text": " ", + }, + { + "italic": true, + "text": "italic", + }, + { + "text": " ", + }, + { + "text": " k ", + }, + "code", + ], + ], + "", + "", + "", + ] + `) + }) +}) diff --git a/src/markdownToFormattedText.ts b/src/markdownToFormattedText.ts new file mode 100644 index 000000000..5a09917d2 --- /dev/null +++ b/src/markdownToFormattedText.ts @@ -0,0 +1,58 @@ +import { remark } from 'remark' + +export default (markdown: string) => { + const arr = markdown.split('\n\n') + const lines = ['', '', '', ''] + for (const [i, ast] of arr.map(md => remark().parse(md)).entries()) { + lines[i] = transformToMinecraftJSON(ast as Element) + } + return lines +} + +function transformToMinecraftJSON (element: Element): any { + switch (element.type) { + case 'root': { + if (!element.children) return + return element.children.map(child => transformToMinecraftJSON(child)).filter(Boolean) + } + case 'paragraph': { + if (!element.children) return + const transformedChildren = element.children.map(child => transformToMinecraftJSON(child)).filter(Boolean) + return transformedChildren.flat() + } + case 'strong': { + if (!element.children) return + return [{ bold: true, text: element.children[0].value }] + } + case 'text': { + return { text: element.value } + } + case 'emphasis': { + if (!element.children) return + return [{ italic: true, text: element.children[0].value }] + } + default: + // todo leave untouched eg links + return element.value + } +} + +interface Position { + start: { + line: number; + column: number; + offset: number; + }; + end: { + line: number; + column: number; + offset: number; + }; + } + + interface Element { + type: string; + children?: Element[]; + value?: string; + position: Position; + } diff --git a/src/optionsGuiScheme.tsx b/src/optionsGuiScheme.tsx index c0b649791..13e6388b2 100644 --- a/src/optionsGuiScheme.tsx +++ b/src/optionsGuiScheme.tsx @@ -152,6 +152,22 @@ export const guiOptionsScheme: { }, chatSelect: { }, + }, + { + custom () { + return Sign Editor + }, + autoSignEditor: { + text: 'Enable Sign Editor', + }, + wysiwygSignEditor: { + text: 'WYSIWG Editor', + values: [ + 'auto', + 'always', + 'never' + ], + }, } ], controls: [ diff --git a/src/optionsStorage.ts b/src/optionsStorage.ts index 63ad0b98d..8490053cb 100644 --- a/src/optionsStorage.ts +++ b/src/optionsStorage.ts @@ -75,6 +75,9 @@ const defaultOptions = { autoRespawn: false, mutedSounds: [] as string[], plugins: [] as Array<{ enabled: boolean, name: string, description: string, script: string }>, + /** Wether to popup sign editor on server action */ + autoSignEditor: true, + wysiwygSignEditor: 'auto' as 'auto' | 'always' | 'never', } const migrateOptions = (options: Partial>) => { diff --git a/src/react/SignEditor.css b/src/react/SignEditor.css new file mode 100644 index 000000000..ae7e874ba --- /dev/null +++ b/src/react/SignEditor.css @@ -0,0 +1,115 @@ +.signs-editor-container { + position: fixed; + inset: 0; + background-color: rgba(0, 0, 0, 0.4); + display: flex; + justify-content: center; + align-items: center; +} + +.signs-editor-inner-container { + position: relative; + width: 700px; + aspect-ratio: 2; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.signs-editor-bg-image { + position: absolute; + z-index: -1; + width: 100%; + height: 100%; + image-rendering: pixelated; +} + +.sign-editor { + font: inherit; + font-size: 420%; + width: 90%; + max-height: 90%; + background-color: rgba(255, 255, 255, 0); + border-width: 0px; + overflow: auto; + outline: none; + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; + box-sizing: border-box; + resize: none; + text-align: center; +} + +/* @media (min-width: 1200px) { */ +/* .sign-editor { */ +/* font-size: 400%; */ +/* } */ +/* } */ + +.ProseMirror p { + font-size: 420%; + text-align: center; +} + +@media (max-width: 850px) { + .ProseMirror p { + font-size: 310%; + } + .sign-editor { + font-size: 310%; + } + .signs-editor-inner-container { + width: 500px; + } +} + +@media (max-width: 550px) { + .ProseMirror p { + font-size: 180%; + } + .sign-editor { + font-size: 180%; + } + .signs-editor-inner-container { + width: 300px; + } +} + +@media (max-width: 350px) { + .ProseMirror p { + font-size: 130%; + } + .sign-editor { + font-size: 130%; + } + .signs-editor-inner-container { + width: 220px; + } +} + +.wysiwyg-editor { + color: black; + max-height: 100%; + overflow: hidden; + width: 90%; + margin: 0px; + white-space: pre-wrap; + border: 1px solid #ccc; +} + +.sign-editor-button { + position: absolute; + right: 0px; + bottom: 0px; + width: 75px; +} + +.ProseMirror-menubar { + background-color: rgba(255, 255, 255, 0.7); +} + +.ProseMirror { + +} diff --git a/src/react/SignEditor.stories.tsx b/src/react/SignEditor.stories.tsx new file mode 100644 index 000000000..05d968f92 --- /dev/null +++ b/src/react/SignEditor.stories.tsx @@ -0,0 +1,25 @@ +import type { Meta, StoryObj } from '@storybook/react' + +import SignEditor from './SignEditor' + +const meta: Meta = { + component: SignEditor, + render (args) { + return { + console.log('handleClick', result) + }} /> + } +} + +export default meta +type Story = StoryObj; + +export const Primary: Story = { + args: { + handleInput () {}, + isWysiwyg: false + }, + parameters: { + noScaling: true + }, +} diff --git a/src/react/SignEditor.tsx b/src/react/SignEditor.tsx new file mode 100644 index 000000000..ffea0784e --- /dev/null +++ b/src/react/SignEditor.tsx @@ -0,0 +1,81 @@ +import { useEffect, useRef } from 'react' +import { focusable } from 'tabbable' +import markdownToFormattedText from '../markdownToFormattedText' +import { ProseMirrorView } from './prosemirror-markdown' +import Button from './Button' +import 'prosemirror-view/style/prosemirror.css' +import 'prosemirror-menu/style/menu.css' +import './SignEditor.css' + + +const imageSource = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAMCAYAAAB4MH11AAABbElEQVR4AY3BQY6cMBBA0Q+yQZZVi+ndcJVcKGfMgegdvShKVtuokzGSWwwiUd7rfv388Vst0UgMXCobmgsSA5VaQmKgUks0EgNHji8SA9W8GJCQwVNpLhzJ4KFs4B1HEgPVvBiQkMFTaS44tYTEQDXdIkfiHbuyobmguaDPFzIWGrWExEA13SJH4h1uzS/WbPyvroM1v6jWbFRrNv7GfX5EdmXjzTvUEjJ4zjQXjiQGdmXjzTvUEjJ4HF/UEt/kQqW5UEkMzIshY08jg6dRS3yTC5XmgpsXY7pFztQSEgPNJCNv3lGpJVSfTLfImVpCYsB1HdwfxpU1G9eeNF0H94dxZc2G+/yI7MoG3vEv82LI2NNIDLyVDbzjzFE2mnkxZOy5IoNnkpFGc2FXNpp5MWTsOXJ4h1qikrGnkhjYlY1m1icy9lQSA+TCzjvUEpWMPZXEwK5suPvDOFuzcdZ1sOYX1ZqNas3GlTUbzR+jQbEAcs8ZQAAAAABJRU5ErkJggg==' + +type Props = { + handleInput: (target: HTMLInputElement) => void, + isWysiwyg: boolean, + handleClick?: (view: ResultType) => void +} + +export type ResultType = { + plainText: string[] +} | { + dataText: string[] +} + +export default ({ handleInput, isWysiwyg, handleClick }: Props) => { + const prosemirrorContainer = useRef(null) + const editorView = useRef(null) + + useEffect(() => { + if (isWysiwyg) { + editorView.current = new ProseMirrorView(prosemirrorContainer.current, '') + } + }, [isWysiwyg]) + + return
{ + let { code } = e + if ((e.target as HTMLElement).matches('input') && e.key === 'Enter') code = 'ArrowDown' + if (code === 'ArrowDown' || code === 'ArrowUp') { + e.preventDefault() + const dir = code === 'ArrowDown' ? 1 : -1 + const elements = focusable(e.currentTarget) + const focusedElemIndex = elements.indexOf(document.activeElement as HTMLElement) + if (focusedElemIndex === -1) return + const nextElem = elements[focusedElemIndex + dir] + nextElem?.focus() + } + }}> +
+ + {isWysiwyg ? ( +

+ ) : [1, 2, 3, 4].map((value, index) => { + return { + handleInput(e.currentTarget) + }} /> + }) + } +
+
+} diff --git a/src/react/SignEditorProvider.tsx b/src/react/SignEditorProvider.tsx new file mode 100644 index 000000000..b6ade6255 --- /dev/null +++ b/src/react/SignEditorProvider.tsx @@ -0,0 +1,84 @@ +import { useMemo, useEffect, useState, useRef } from 'react' +import { showModal, hideModal } from '../globalState' +import { setDoPreventDefault } from '../controls' +import { options } from '../optionsStorage' +import { useIsModalActive } from './utils' +import SignEditor, { ResultType } from './SignEditor' + + +const isWysiwyg = async () => { + const items = await bot.tabComplete('/', true, true) + const commands = new Set(['data']) + for (const item of items) { + if (commands.has(item.match as unknown as string)) { + return true + } + } + return false +} + +export default () => { + const [location, setLocation] = useState<{x: number, y: number, z: number} | null>(null) + const [isFrontText, setIsFrontText] = useState(true) + const text = useRef(['', '', '', '']) + const [enableWysiwyg, setEnableWysiwyg] = useState(false) + const isModalActive = useIsModalActive('signs-editor-screen') + + const handleClick = (result: ResultType) => { + hideModal({ reactType: 'signs-editor-screen' }) + if ('plainText' in result) { + bot._client.write('update_sign', { + location, + isFrontText, + text1: result.plainText[0], + text2: result.plainText[1], + text3: result.plainText[2], + text4: result.plainText[3] + }) + } else { + if (!location) return + const command = `/data merge block ${location.x} ${location.y} ${location.z} {Text1:'` + JSON.stringify(result.dataText[0]) + '\',Text2: \'' + JSON.stringify(result.dataText[1]) + '\',Text3:\'' + JSON.stringify(result.dataText[2]) + '\',Text4:\'' + JSON.stringify(result.dataText[3]) + '\'}' // mojangson + bot.chat(command) + } + } + + const handleInput = (target: HTMLInputElement) => { + const smallSymbols = /[()[\]{} ]/ + const largeSymbols = /[;|',.]/ + let addLength = 0 + for (const letter of target.value) { + if (smallSymbols.test(letter)) { + addLength += 1 - 1 / 1.46 + } else if (largeSymbols.test(letter)) { + addLength += 1 - 1 / 3 + } + } + text.current[Number(target.dataset.key)] = target.value + target.setAttribute('maxlength', `${15 + Math.ceil(addLength)}`) + } + + useEffect(() => { + setDoPreventDefault(!isModalActive) // disable e.preventDefault() since we might be using wysiwyg editor which doesn't use textarea and need default browser behavior to ensure characters are being typed in contenteditable container. Ideally we should do e.preventDefault() only when either ctrl, cmd (meta) or alt key is pressed. + }, [isModalActive]) + + useMemo(() => { + bot._client.on('open_sign_entity', (packet) => { + if (!options.autoSignEditor) return + setIsFrontText((packet as any).isFrontText ?? true) + setLocation(prev => packet.location) + showModal({ reactType: 'signs-editor-screen' }) + if (options.wysiwygSignEditor === 'auto') { + void isWysiwyg().then((value) => { + setEnableWysiwyg(value) + }) + } else if (options.wysiwygSignEditor === 'always') { + setEnableWysiwyg(true) + } else { + setEnableWysiwyg(false) + } + }) + }, []) + + if (!isModalActive) return null + return +} diff --git a/src/react/prosemirror-markdown.ts b/src/react/prosemirror-markdown.ts new file mode 100644 index 000000000..9bb90bccb --- /dev/null +++ b/src/react/prosemirror-markdown.ts @@ -0,0 +1,77 @@ +import { EditorView } from 'prosemirror-view' +import { EditorState } from 'prosemirror-state' +import { schema, defaultMarkdownParser, defaultMarkdownSerializer } from 'prosemirror-markdown' +import { exampleSetup, buildMenuItems } from 'prosemirror-example-setup' +import { MarkSpec } from 'prosemirror-model' +import { toggleMark } from 'prosemirror-commands' + +export class ProseMirrorView { + view + + constructor (target, content) { + console.log('schema.marks', schema.marks) + //@ts-expect-error + schema.marks.textColor = { + spec: { + attrs: { color: {} }, + inline: true, + parseDOM: [ + { + style: 'color', + getAttrs: value => ({ color: value }) + } + ], + toDOM: mark => ['span', { style: `color: ${mark.attrs.color}` }, 0] + }, + } + + const fullMenu = buildMenuItems(schema).fullMenu as Array> + fullMenu[0] = fullMenu[0].filter(item => item.spec.title !== 'Add or remove link' && item.spec.title !== 'Toggle code font') + fullMenu.splice(3, 1); // remove the insert list, quote & checkbox menu + (fullMenu[1][0] as any).options.label = 'Color' // check-build error: fullMenu[1][0].options.label = 'Color' + // fullMenu[1][0].content[0].spec.label = 'Red' + // fullMenu[1][0].content[0].spec.run = (state, dispatch, view) => { + // console.log('state', state) + // // make

...

+ // const { from, to } = state.selection + // const { tr } = state + // console.log(schema.marks) + // tr.addMark(from, to, schema.marks.textColor.create({ color: 'red' })) + // dispatch(tr) + // toggleMark(schema.marks.textColor, { color: 'red' })(state, dispatch, view) + // } + fullMenu[1].splice(1, 1) // remove the type menu + console.log('fullMenu', fullMenu) + this.view = new EditorView(target, { + state: EditorState.create({ + doc: defaultMarkdownParser.parse(content) ?? undefined, + plugins: exampleSetup({ + schema, + menuContent: fullMenu, + }), + }), + attributes (state) { + return { + autocorrect: 'off', + autocapitalize: 'off', + spellcheck: 'false', + autofocus: 'true', + } + }, + }) + } + + get content () { + const content = defaultMarkdownSerializer.serialize(this.view.state.doc) + console.log('content', content) + return content + } + + focus () { + this.view.focus() + } + + destroy () { + this.view.destroy() + } +} diff --git a/src/reactUi.tsx b/src/reactUi.tsx index 2b474adc5..aa1d74dd9 100644 --- a/src/reactUi.tsx +++ b/src/reactUi.tsx @@ -15,6 +15,7 @@ import EnterFullscreenButton from './react/EnterFullscreenButton' import ChatProvider from './react/ChatProvider' import TitleProvider from './react/TitleProvider' import ScoreboardProvider from './react/ScoreboardProvider' +import SignEditorProvider from './react/SignEditorProvider' import SoundMuffler from './react/SoundMuffler' import TouchControls from './react/TouchControls' import widgets from './react/widgets' @@ -66,7 +67,10 @@ const InGameUi = () => { - + + + + {/* becaues of z-index */} diff --git a/vitest.config.ts b/vitest.config.ts index fc9d1e493..9a992a526 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -5,6 +5,7 @@ export default defineConfig({ test: { include: [ '../../src/botUtils.test.ts', + '../../src/markdownToFormattedText.test.ts', 'sign-renderer/tests.test.ts' ], }, From 2ee21d5f7d198a312bf8a143dd7cea853f8ac028 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 23 Mar 2024 19:21:06 +0300 Subject: [PATCH 3/3] fix: improve google integration ux by saving last folder + fix creating worlds --- src/googledrive.ts | 30 +++---- src/react/SingleplayerProvider.tsx | 132 +++++++++++++++-------------- 2 files changed, 85 insertions(+), 77 deletions(-) diff --git a/src/googledrive.ts b/src/googledrive.ts index 2128ae2f1..3846add3e 100644 --- a/src/googledrive.ts +++ b/src/googledrive.ts @@ -25,9 +25,9 @@ export const useGoogleLogIn = () => { const login = useGoogleLogin({ onSuccess (tokenResponse) { localStorage.hasEverLoggedIn = true - googleProviderData.accessToken = tokenResponse.access_token - googleProviderData.expiresIn = ref(new Date(Date.now() + tokenResponse.expires_in * 1000)) - googleProviderData.hasEverLoggedIn = true + googleProviderState.accessToken = tokenResponse.access_token + googleProviderState.expiresIn = ref(new Date(Date.now() + tokenResponse.expires_in * 1000)) + googleProviderState.hasEverLoggedIn = true }, // prompt: hasEverLoggedIn ? 'none' : 'consent', scope: SCOPES, @@ -35,12 +35,12 @@ export const useGoogleLogIn = () => { onError (error) { const accessDenied = error.error === 'access_denied' || error.error === 'invalid_scope' || (error as any).error_subtype === 'access_denied' if (accessDenied) { - googleProviderData.hasEverLoggedIn = false + googleProviderState.hasEverLoggedIn = false } } }) return () => login({ - prompt: googleProviderData.hasEverLoggedIn ? 'none' : 'consent' + prompt: googleProviderState.hasEverLoggedIn ? 'none' : 'consent' }) } @@ -61,11 +61,11 @@ export const possiblyHandleStateVariable = async () => { async callback (response) { if (response.error) { setLoadingScreenStatus('Error: ' + response.error, true) - googleProviderData.hasEverLoggedIn = false + googleProviderState.hasEverLoggedIn = false return } setLoadingScreenStatus('Opening world in read only mode...') - googleProviderData.accessToken = response.access_token + googleProviderState.accessToken = response.access_token await mountGoogleDriveFolder(true, parsed.ids[0]) await loadInMemorySave('/google') } @@ -73,14 +73,14 @@ export const possiblyHandleStateVariable = async () => { const choice = await showOptionsModal('Select an action...', ['Login']) if (choice === 'Login') { tokenClient.requestAccessToken({ - prompt: googleProviderData.hasEverLoggedIn ? '' : 'consent', + prompt: googleProviderState.hasEverLoggedIn ? '' : 'consent', }) } else { window.close() } } -export const googleProviderData = proxy({ +export const googleProviderState = proxy({ accessToken: (localStorage.saveAccessToken ? localStorage.accessToken : null) as string | null, hasEverLoggedIn: !!(localStorage.hasEverLoggedIn), isReady: false, @@ -92,18 +92,18 @@ export const googleProviderData = proxy({ } | null }) -subscribe(googleProviderData, () => { - localStorage.googleReadonlyMode = googleProviderData.readonlyMode - localStorage.lastSelectedFolder = googleProviderData.lastSelectedFolder ? JSON.stringify(googleProviderData.lastSelectedFolder) : null - if (googleProviderData.hasEverLoggedIn) { +subscribe(googleProviderState, () => { + localStorage.googleReadonlyMode = googleProviderState.readonlyMode + localStorage.lastSelectedFolder = googleProviderState.lastSelectedFolder ? JSON.stringify(googleProviderState.lastSelectedFolder) : null + if (googleProviderState.hasEverLoggedIn) { localStorage.hasEverLoggedIn = true } else { delete localStorage.hasEverLoggedIn } - if (localStorage.saveAccessToken && googleProviderData) { + if (localStorage.saveAccessToken && googleProviderState) { // For testing only - localStorage.accessToken = googleProviderData.accessToken || null + localStorage.accessToken = googleProviderState.accessToken || null } else { delete localStorage.accessToken } diff --git a/src/react/SingleplayerProvider.tsx b/src/react/SingleplayerProvider.tsx index 32d175f43..8eb3af0f6 100644 --- a/src/react/SingleplayerProvider.tsx +++ b/src/react/SingleplayerProvider.tsx @@ -7,7 +7,7 @@ import { googleDriveGetFileIdFromPath, mountExportFolder, mountGoogleDriveFolder import { hideCurrentModal, showModal } from '../globalState' import { haveDirectoryPicker, setLoadingScreenStatus } from '../utils' import { exportWorld } from '../builtinCommands' -import { googleProviderData, useGoogleLogIn, GoogleDriveProvider, isGoogleDriveAvailable, APP_ID } from '../googledrive' +import { googleProviderState, useGoogleLogIn, GoogleDriveProvider, isGoogleDriveAvailable, APP_ID } from '../googledrive' import Singleplayer, { WorldProps } from './Singleplayer' import { useIsModalActive } from './utils' import { showOptionsModal } from './SelectOption' @@ -18,6 +18,7 @@ const worldsProxy = proxy({ value: null as null | WorldProps[], brokenWorlds: [] as string[], selectedProvider: 'local' as 'local' | 'google', + selectedGoogleId: '', error: '', }) @@ -48,7 +49,7 @@ export const readWorlds = (abortController: AbortController) => { (async () => { const brokenWorlds = [] as string[] try { - const loggedIn = !!googleProviderData.accessToken + const loggedIn = !!googleProviderState.accessToken worldsProxy.value = null if (worldsProxy.selectedProvider === 'google' && !loggedIn) { worldsProxy.value = [] @@ -135,7 +136,7 @@ export const loadGoogleDriveApi = async () => { gapi.load('client', () => { gapi.load('client:picker', () => { void gapi.client.load('https://www.googleapis.com/discovery/v1/apis/drive/v3/rest').then(() => { - googleProviderData.isReady = true + googleProviderState.isReady = true resolve() }) }) @@ -145,24 +146,17 @@ export const loadGoogleDriveApi = async () => { const Inner = () => { const worlds = useSnapshot(worldsProxy).value as WorldProps[] | null - const { selectedProvider, error, brokenWorlds } = useSnapshot(worldsProxy) + const { selectedProvider, error, brokenWorlds, selectedGoogleId } = useSnapshot(worldsProxy) const readWorldsAbortController = useRef(new AbortController()) - useEffect(() => { - return () => { - worldsProxy.selectedProvider = 'local' - } - }, []) - // 3rd party providers useEffect(() => { if (selectedProvider !== 'google') return void loadGoogleDriveApi() }, [selectedProvider]) - const [selectedGoogleId, setSelectedGoogleId] = useState('') - const loggedIn = !!useSnapshot(googleProviderData).accessToken - const googleDriveReadonly = useSnapshot(googleProviderData).readonlyMode + const loggedIn = !!useSnapshot(googleProviderState).accessToken + const googleDriveReadonly = useSnapshot(googleProviderState).readonlyMode useEffect(() => { (async () => { @@ -171,7 +165,7 @@ const Inner = () => { worldsProxy.value = [] return } - await mountGoogleDriveFolder(googleProviderData.readonlyMode, selectedGoogleId) + await mountGoogleDriveFolder(googleProviderState.readonlyMode, selectedGoogleId) const exists = async (path) => { try { await fs.promises.stat(path) @@ -199,21 +193,74 @@ const Inner = () => { const googleLogIn = useGoogleLogIn() - const isGoogleProviderReady = useSnapshot(googleProviderData).isReady - const providerActions = selectedProvider === 'google' ? isGoogleProviderReady ? loggedIn ? { + const googlePicker = useRef/* */(null as any) + + useEffect(() => { + return () => { + googlePicker.current?.dispose() + } + }) + + const selectGoogleFolder = async () => { + if (googleProviderState.lastSelectedFolder) { + // ask to use saved previous fodler + const choice = await showOptionsModal(`Use previously selected folder "${googleProviderState.lastSelectedFolder.name}"?`, ['Yes', 'No']) + if (!choice) return + if (choice === 'Yes') { + worldsProxy.selectedGoogleId = googleProviderState.lastSelectedFolder.id + return + } + } + + const { google } = window + + const view = new google.picker.DocsView(google.picker.ViewId.FOLDERS) + .setIncludeFolders(true) + .setMimeTypes('application/vnd.google-apps.folder') + .setSelectFolderEnabled(true) + .setParent('root') + + + googlePicker.current = new google.picker.PickerBuilder() + .enableFeature(google.picker.Feature.NAV_HIDDEN) + .enableFeature(google.picker.Feature.MULTISELECT_ENABLED) + .setDeveloperKey('AIzaSyBTiHpEqaLL7mEcrsnSS4M-z8cpRH5UwY0') + .setAppId(APP_ID) + .setOAuthToken(googleProviderState.accessToken) + .addView(view) + .addView(new google.picker.DocsUploadView()) + .setTitle('Select a folder with your worlds') + .setCallback((data) => { + if (data.action === google.picker.Action.PICKED) { + googleProviderState.lastSelectedFolder = { + id: data.docs[0].id, + name: data.docs[0].name, + } + worldsProxy.selectedGoogleId = data.docs[0].id + } + }) + .build() + googlePicker.current.setVisible(true) + } + + const isGoogleProviderReady = useSnapshot(googleProviderState).isReady + const providerActions = loggedIn && selectedProvider === 'google' && isGoogleProviderReady && !selectedGoogleId ? { + 'Select Folder': selectGoogleFolder + } : selectedProvider === 'google' ? isGoogleProviderReady ? loggedIn ? { 'Log Out' () { - googleProviderData.hasEverLoggedIn = false - googleProviderData.accessToken = null - googleProviderData.lastSelectedFolder = null - window.google.accounts.oauth2.revoke(googleProviderData.accessToken) + googleProviderState.hasEverLoggedIn = false + googleProviderState.accessToken = null + googleProviderState.lastSelectedFolder = null + window.google.accounts.oauth2.revoke(googleProviderState.accessToken) }, async [`Read Only: ${googleDriveReadonly ? 'ON' : 'OFF'}`] () { - if (googleProviderData.readonlyMode) { + if (googleProviderState.readonlyMode) { const choice = await showOptionsModal('[Unstable Feature] Enabling world save might corrupt your worlds, eg remove entities (note: you can always restore previous version of files in Drive)', ['Continue']) if (choice !== 'Continue') return } - googleProviderData.readonlyMode = !googleProviderData.readonlyMode + googleProviderState.readonlyMode = !googleProviderState.readonlyMode }, + 'Select Folder': selectGoogleFolder, // 'Worlds Path': { // googleProviderData.worldsPath = e.target.value // }} /> @@ -224,48 +271,9 @@ const Inner = () => { } : undefined // end - useEffect(() => { - let picker - if (loggedIn && selectedProvider === 'google' && isGoogleProviderReady) { - const { google } = window - - const view = new google.picker.DocsView(google.picker.ViewId.FOLDERS) - .setIncludeFolders(true) - .setMimeTypes('application/vnd.google-apps.folder') - .setSelectFolderEnabled(true) - .setParent('root') - - - picker = new google.picker.PickerBuilder() - .enableFeature(google.picker.Feature.NAV_HIDDEN) - .enableFeature(google.picker.Feature.MULTISELECT_ENABLED) - .setDeveloperKey('AIzaSyBTiHpEqaLL7mEcrsnSS4M-z8cpRH5UwY0') - .setAppId(APP_ID) - .setOAuthToken(googleProviderData.accessToken) - .addView(view) - .addView(new google.picker.DocsUploadView()) - .setTitle('Select a folder with your worlds') - .setCallback((data) => { - if (data.action === google.picker.Action.PICKED) { - googleProviderData.lastSelectedFolder = { - id: data.docs[0].id, - name: data.docs[0].name, - } - setSelectedGoogleId(data.docs[0].id) - } - }) - .build() - picker.setVisible(true) - } - - return () => { - if (picker) picker.dispose() - } - }, [selectedProvider, loggedIn]) - return