diff --git a/package-lock.json b/package-lock.json index 1460c2b..889d019 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,8 +12,10 @@ "@codemirror/lang-html": "^6.4.3", "@codemirror/lang-javascript": "^6.1.9", "@fortawesome/fontawesome-free": "^6.7.2", + "@monaco-editor/react": "^4.7.0", "@uiw/codemirror-theme-vscode": "^4.21.9", "@uiw/react-codemirror": "^4.21.9", + "monaco-editor": "^0.52.2", "react": "^19.0.0", "react-dom": "^19.0.0", "react-router-dom": "^6.27.0", @@ -1256,6 +1258,29 @@ "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", "license": "MIT" }, + "node_modules/@monaco-editor/loader": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.5.0.tgz", + "integrity": "sha512-hKoGSM+7aAc7eRTRjpqAZucPmoNOC4UUbknb/VNoTkEIkCPhqV8LfbsgM1webRM7S/z21eHEx9Fkwx8Z/C/+Xw==", + "license": "MIT", + "dependencies": { + "state-local": "^1.0.6" + } + }, + "node_modules/@monaco-editor/react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.7.0.tgz", + "integrity": "sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==", + "license": "MIT", + "dependencies": { + "@monaco-editor/loader": "^1.5.0" + }, + "peerDependencies": { + "monaco-editor": ">= 0.25.0 < 1", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/@parcel/watcher": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", @@ -2946,6 +2971,12 @@ "node": "*" } }, + "node_modules/monaco-editor": { + "version": "0.52.2", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz", + "integrity": "sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -3345,6 +3376,12 @@ "node": ">=0.10.0" } }, + "node_modules/state-local": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz", + "integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==", + "license": "MIT" + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", diff --git a/package.json b/package.json index 61efa9e..a8ec0eb 100644 --- a/package.json +++ b/package.json @@ -14,8 +14,10 @@ "@codemirror/lang-html": "^6.4.3", "@codemirror/lang-javascript": "^6.1.9", "@fortawesome/fontawesome-free": "^6.7.2", + "@monaco-editor/react": "^4.7.0", "@uiw/codemirror-theme-vscode": "^4.21.9", "@uiw/react-codemirror": "^4.21.9", + "monaco-editor": "^0.52.2", "react": "^19.0.0", "react-dom": "^19.0.0", "react-router-dom": "^6.27.0", diff --git a/src/components/EditorPanel.jsx b/src/components/EditorPanel.jsx new file mode 100644 index 0000000..fc7c247 --- /dev/null +++ b/src/components/EditorPanel.jsx @@ -0,0 +1,16 @@ +import React from 'react'; +import Editor from '@monaco-editor/react'; + +export default function EditorPanel({ code, onChange }) { + return ( +
+ onChange(v || '')} + theme="vs-dark" + /> +
+ ); +} \ No newline at end of file diff --git a/src/components/PreviewPanel.jsx b/src/components/PreviewPanel.jsx new file mode 100644 index 0000000..f94a471 --- /dev/null +++ b/src/components/PreviewPanel.jsx @@ -0,0 +1,141 @@ +import React, { useState } from 'react'; + +export default function PreviewPanel({ code }) { + const [gameUrl, setGameUrl] = useState(''); + const [snakeApiUrl, setSnakeApiUrl] = useState(''); + const [settings, setSettings] = useState(null); + const [moveRes, setMoveRes] = useState(null); + const [loadingSetup, setLoadingSetup] = useState(false); + const [loadingMove, setLoadingMove] = useState(false); + + + const fetchSetup = async (e) => { + e.preventDefault(); + setLoadingSetup(true); + try { + const res = await fetch( + `/api/fetch-board?url=${encodeURIComponent(gameUrl.trim())}` + ); + setSettings(await res.json()); + setMoveRes(null); + } catch (err) { + console.error(err); + } + setLoadingSetup(false); + }; + + const testMove = async (e) => { + e.preventDefault(); + if (!settings) return; + setLoadingMove(true); + try { + const res = await fetch('/api/move', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ code, boardState: settings }) + }); + setMoveRes(await res.json()); + } catch (err) { + console.error(err); + } + setLoadingMove(false); + }; + + const gameId = gameUrl.trim().split('/').pop(); + + return ( +
+

Battlesnake Preview

+ + {/* game board URL */} +
+ setGameUrl(e.target.value)} + style={{ flex: 1 }} + /> + +
+ + {/* API server URL */} +
+ setSnakeApiUrl(e.target.value)} + style={{ flex: 1 }} + /> + +
+ + {/* Test Move */} +
+ +
+ + {settings && gameId && ( +
+