From c1086ebdcb9e49df9a41012205b82df5c7838a70 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 21 Aug 2025 13:14:21 +0300 Subject: [PATCH] add monaco --- package.json | 1 + pnpm-lock.yaml | 58 +++++++++++++++----- src/core/ideChannels.ts | 106 +++++++++++++++++++++++++++++++++++++ src/customChannels.ts | 2 + src/react/MonacoEditor.css | 58 ++++++++++++++++++++ src/react/MonacoEditor.tsx | 73 +++++++++++++++++++++++++ src/reactUi.tsx | 3 +- 7 files changed, 286 insertions(+), 15 deletions(-) create mode 100644 src/core/ideChannels.ts create mode 100644 src/react/MonacoEditor.css create mode 100644 src/react/MonacoEditor.tsx diff --git a/package.json b/package.json index 1d9dfc0c..a8c2c4e7 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "dependencies": { "@dimaka/interface": "0.0.3-alpha.0", "@floating-ui/react": "^0.26.1", + "@monaco-editor/react": "^4.7.0", "@nxg-org/mineflayer-auto-jump": "^0.7.18", "@nxg-org/mineflayer-tracker": "1.3.0", "@react-oauth/google": "^0.12.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c7e1d1b9..8acb9681 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -42,6 +42,9 @@ importers: '@floating-ui/react': specifier: ^0.26.1 version: 0.26.28(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@monaco-editor/react': + specifier: ^4.7.0 + version: 4.7.0(monaco-editor@0.52.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@nxg-org/mineflayer-auto-jump': specifier: ^0.7.18 version: 0.7.18 @@ -430,13 +433,13 @@ importers: version: 1.3.9 prismarine-block: specifier: github:zardoy/prismarine-block#next-era - version: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) + version: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-chunk: specifier: github:zardoy/prismarine-chunk#master version: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) prismarine-schematic: specifier: ^1.2.0 - version: 1.2.3(prismarine-registry@1.11.0) + version: 1.2.3 process: specifier: ^0.11.10 version: 0.11.10 @@ -1989,6 +1992,16 @@ packages: '@module-federation/webpack-bundler-runtime@0.11.2': resolution: {integrity: sha512-WdwIE6QF+MKs/PdVu0cKPETF743JB9PZ62/qf7Uo3gU4fjsUMc37RnbJZ/qB60EaHHfjwp1v6NnhZw1r4eVsnw==} + '@monaco-editor/loader@1.5.0': + resolution: {integrity: sha512-hKoGSM+7aAc7eRTRjpqAZucPmoNOC4UUbknb/VNoTkEIkCPhqV8LfbsgM1webRM7S/z21eHEx9Fkwx8Z/C/+Xw==} + + '@monaco-editor/react@4.7.0': + resolution: {integrity: sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==} + peerDependencies: + monaco-editor: '>= 0.25.0 < 1' + react: ^18.2.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@msgpack/msgpack@2.8.0': resolution: {integrity: sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ==} engines: {node: '>= 10'} @@ -6769,6 +6782,9 @@ packages: mojangson@2.0.4: resolution: {integrity: sha512-HYmhgDjr1gzF7trGgvcC/huIg2L8FsVbi/KacRe6r1AswbboGVZDS47SOZlomPuMWvZLas8m9vuHHucdZMwTmQ==} + monaco-editor@0.52.2: + resolution: {integrity: sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==} + moo@0.5.2: resolution: {integrity: sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==} @@ -8373,6 +8389,9 @@ packages: stacktrace-js@2.0.2: resolution: {integrity: sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==} + state-local@1.0.7: + resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==} + static-extend@0.1.2: resolution: {integrity: sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==} engines: {node: '>=0.10.0'} @@ -11254,6 +11273,17 @@ snapshots: '@module-federation/runtime': 0.11.2 '@module-federation/sdk': 0.11.2 + '@monaco-editor/loader@1.5.0': + dependencies: + state-local: 1.0.7 + + '@monaco-editor/react@4.7.0(monaco-editor@0.52.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@monaco-editor/loader': 1.5.0 + monaco-editor: 0.52.2 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + '@msgpack/msgpack@2.8.0': {} '@ndelangen/get-tarball@3.0.9': @@ -11309,7 +11339,7 @@ snapshots: '@nxg-org/mineflayer-util-plugin': 1.8.4 minecraft-data: 3.92.0 mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13) - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-item: 1.17.0 prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b vec3: 0.1.10 @@ -17344,7 +17374,7 @@ snapshots: minecraft-data: 3.92.0 minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0) - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-chat: 1.11.0 prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) prismarine-entity: 2.5.0 @@ -17461,6 +17491,8 @@ snapshots: dependencies: nearley: 2.20.1 + monaco-editor@0.52.2: {} + moo@0.5.2: {} morgan@1.10.0: @@ -18135,7 +18167,7 @@ snapshots: minecraft-data: 3.92.0 prismarine-registry: 1.11.0 - prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0): + prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9: dependencies: minecraft-data: 3.92.0 prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0) @@ -18143,8 +18175,6 @@ snapshots: prismarine-item: 1.17.0 prismarine-nbt: 2.7.0 prismarine-registry: 1.11.0 - transitivePeerDependencies: - - prismarine-registry prismarine-chat@1.11.0: dependencies: @@ -18155,7 +18185,7 @@ snapshots: prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0): dependencies: prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0) - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-nbt: 2.7.0 prismarine-registry: 1.11.0 smart-buffer: 4.2.0 @@ -18189,7 +18219,7 @@ snapshots: prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.92.0): dependencies: - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) prismarine-nbt: 2.7.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c @@ -18213,18 +18243,16 @@ snapshots: prismarine-registry@1.11.0: dependencies: minecraft-data: 3.92.0 - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-nbt: 2.7.0 - prismarine-schematic@1.2.3(prismarine-registry@1.11.0): + prismarine-schematic@1.2.3: dependencies: minecraft-data: 3.92.0 - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-nbt: 2.7.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c vec3: 0.1.10 - transitivePeerDependencies: - - prismarine-registry prismarine-windows@2.9.0: dependencies: @@ -19521,6 +19549,8 @@ snapshots: stack-generator: 2.0.10 stacktrace-gps: 3.1.2 + state-local@1.0.7: {} + static-extend@0.1.2: dependencies: define-property: 0.2.5 diff --git a/src/core/ideChannels.ts b/src/core/ideChannels.ts new file mode 100644 index 00000000..a9c517f7 --- /dev/null +++ b/src/core/ideChannels.ts @@ -0,0 +1,106 @@ +import { proxy } from 'valtio' + +export const ideState = proxy({ + id: '', + contents: '', + line: 0, + column: 0, + language: 'typescript', + title: '', +}) +globalThis.ideState = ideState + +export const registerIdeChannels = () => { + registerIdeOpenChannel() + registerIdeSaveChannel() +} + +const registerIdeOpenChannel = () => { + const CHANNEL_NAME = 'minecraft-web-client:ide-open' + + const packetStructure = [ + 'container', + [ + { + name: 'id', + type: ['pstring', { countType: 'i16' }] + }, + { + name: 'language', + type: ['pstring', { countType: 'i16' }] + }, + { + name: 'contents', + type: ['pstring', { countType: 'i16' }] + }, + { + name: 'line', + type: 'i32' + }, + { + name: 'column', + type: 'i32' + }, + { + name: 'title', + type: ['pstring', { countType: 'i16' }] + } + ] + ] + + bot._client.registerChannel(CHANNEL_NAME, packetStructure, true) + + bot._client.on(CHANNEL_NAME as any, (data) => { + const { id, language, contents, line, column, title } = data + + ideState.contents = contents + ideState.line = line + ideState.column = column + ideState.id = id + ideState.language = language || 'typescript' + ideState.title = title + }) + + console.debug(`registered custom channel ${CHANNEL_NAME} channel`) +} +const IDE_SAVE_CHANNEL_NAME = 'minecraft-web-client:ide-save' +const registerIdeSaveChannel = () => { + + const packetStructure = [ + 'container', + [ + { + name: 'id', + type: ['pstring', { countType: 'i16' }] + }, + { + name: 'contents', + type: ['pstring', { countType: 'i16' }] + }, + { + name: 'language', + type: ['pstring', { countType: 'i16' }] + }, + { + name: 'line', + type: 'i32' + }, + { + name: 'column', + type: 'i32' + }, + ] + ] + bot._client.registerChannel(IDE_SAVE_CHANNEL_NAME, packetStructure, true) +} + +export const saveIde = () => { + bot._client.writeChannel(IDE_SAVE_CHANNEL_NAME, { + id: ideState.id, + contents: ideState.contents, + language: ideState.language, + // todo: reflect updated + line: ideState.line, + column: ideState.column, + }) +} diff --git a/src/customChannels.ts b/src/customChannels.ts index 6d3aa7e9..717c7c93 100644 --- a/src/customChannels.ts +++ b/src/customChannels.ts @@ -2,6 +2,7 @@ import PItem from 'prismarine-item' import { getThreeJsRendererMethods } from 'renderer/viewer/three/threeJsMethods' import { options } from './optionsStorage' import { jeiCustomCategories } from './inventoryWindows' +import { registerIdeChannels } from './core/ideChannels' export default () => { customEvents.on('mineflayerBotCreated', async () => { @@ -17,6 +18,7 @@ export default () => { registeredJeiChannel() registerBlockInteractionsCustomizationChannel() registerWaypointChannels() + registerIdeChannels() }) } diff --git a/src/react/MonacoEditor.css b/src/react/MonacoEditor.css new file mode 100644 index 00000000..86d2ad0a --- /dev/null +++ b/src/react/MonacoEditor.css @@ -0,0 +1,58 @@ +.monaco-editor-container { + position: fixed; + inset: 0; + z-index: 1000; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: 16px; + background-color: rgba(0, 0, 0, 0.5); +} + +.monaco-editor-title { + font-size: 20px; + font-weight: bold; + color: #fff; + margin-bottom: 8px; +} + +.monaco-editor-wrapper { + position: relative; + width: 100%; + height: 100%; + max-width: 80vw; + max-height: 80vh; + border: 3px solid #000; + background-color: #000; + padding: 3px; + box-shadow: inset 0 0 0 1px #fff, inset 0 0 0 2px #000; +} + +.monaco-editor-close { + position: fixed; + top: 16px; + left: 16px; + z-index: 1001; + cursor: pointer; + padding: 8px; +} + +@media (max-width: 768px) { + .monaco-editor-container { + padding: 0; + } + .monaco-editor-wrapper { + max-width: 100%; + max-height: 100%; + border-radius: 0; + } + .monaco-editor-close { + top: 8px; + left: 8px; + } + .monaco-editor-title { + /* todo: make it work on mobile */ + display: none; + } +} diff --git a/src/react/MonacoEditor.tsx b/src/react/MonacoEditor.tsx new file mode 100644 index 00000000..32162b21 --- /dev/null +++ b/src/react/MonacoEditor.tsx @@ -0,0 +1,73 @@ +import { proxy, useSnapshot } from 'valtio' +import { useEffect } from 'react' +import { Editor } from '@monaco-editor/react' +import PixelartIcon, { pixelartIcons } from '../react/PixelartIcon' +import { useIsModalActive } from '../react/utilsApp' +import { showNotification } from '../react/NotificationProvider' +import { hideModal, showModal } from '../globalState' +import { ideState, saveIde } from '../core/ideChannels' +import './MonacoEditor.css' + +export default () => { + const { contents, line, column, id, language, title } = useSnapshot(ideState) + const isModalActive = useIsModalActive('monaco-editor') + const bodyFont = getComputedStyle(document.body).fontFamily + + useEffect(() => { + if (id && !isModalActive) { + showModal({ reactType: 'monaco-editor' }) + } + if (!id && isModalActive) { + hideModal() + } + }, [id]) + + useEffect(() => { + if (!isModalActive && id) { + try { + saveIde() + } catch (err) { + reportError(err) + showNotification('Failed to save the editor', 'Please try again', true) + } + ideState.id = '' + ideState.contents = '' + } + }, [isModalActive]) + + if (!isModalActive) return null + + return
+
+ { + hideModal() + }} + /> +
+
+ {title} +
+
+ { + ideState.contents = value ?? '' + }} + value={contents} + options={{ + fontFamily: bodyFont, + minimap: { + enabled: true, + }, + }} + /> +
+
+} diff --git a/src/reactUi.tsx b/src/reactUi.tsx index 4f8c4541..1e6f13eb 100644 --- a/src/reactUi.tsx +++ b/src/reactUi.tsx @@ -67,6 +67,7 @@ import GlobalOverlayHints from './react/GlobalOverlayHints' import FullscreenTime from './react/FullscreenTime' import StorageConflictModal from './react/StorageConflictModal' import FireRenderer from './react/FireRenderer' +import MonacoEditor from './react/MonacoEditor' const isFirefox = ua.getBrowser().name === 'Firefox' if (isFirefox) { @@ -248,7 +249,6 @@ const App = () => { - @@ -259,6 +259,7 @@ const App = () => {
+