Merge pull request #22 from JBB0807/save_functionality-b

working save and deploy
This commit is contained in:
JB Balahadia 2025-05-08 01:28:23 -07:00 committed by GitHub
commit cfcd91bf3d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 91 additions and 53 deletions

View file

@ -8,7 +8,7 @@ export default function EditorPanel({ code, onChange }) {
border: '1px solid #444', border: '1px solid #444',
borderRadius: '8px', borderRadius: '8px',
backgroundColor: '#1e1e1e', backgroundColor: '#1e1e1e',
height: '90%', height: '80%',
boxShadow: '0 0 10px rgba(255, 0, 255, 0.2)', boxShadow: '0 0 10px rgba(255, 0, 255, 0.2)',
overflow: 'hidden' overflow: 'hidden'
}}> }}>

View file

@ -1,28 +1,19 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
export default function PreviewPanel({ code }) { const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
const [gameUrl, setGameUrl] = useState('');
const [settings, setSettings] = useState(null);
const [loadingSetup, setLoadingSetup] = useState(false);
const fetchBoard = async () => { export default function PreviewPanel({ code }) {
if (!gameUrl.trim()) return; const [gameId, setGameId] = useState('');
setLoadingSetup(true); const [submitted, setSubmitted] = useState(false);
try {
const res = await fetch( const isValid = uuidRegex.test(gameId);
`/api/fetch-board?url=${encodeURIComponent(gameUrl.trim())}`
); const handleClick = () => {
if (!res.ok) throw new Error('Fetch board failed'); if (isValid) {
setSettings(await res.json()); setSubmitted(true);
} catch (err) {
console.error(err);
} finally {
setLoadingSetup(false);
} }
}; };
const gameId = gameUrl.trim().split('/').pop();
return ( return (
<div style={{ <div style={{
flex: 1, flex: 1,
@ -61,9 +52,9 @@ export default function PreviewPanel({ code }) {
}}> }}>
<input <input
type="text" type="text"
placeholder="Game URL" placeholder="Game ID (UUID)"
value={gameUrl} value={gameId}
onChange={e => setGameUrl(e.target.value)} onChange={e => { setGameId(e.target.value.trim()); setSubmitted(false); }}
style={{ style={{
width: '100%', width: '100%',
padding: '0.5rem', padding: '0.5rem',
@ -75,44 +66,42 @@ export default function PreviewPanel({ code }) {
outline: 'none' outline: 'none'
}} }}
/> />
{!isValid && gameId && (
<p style={{ color: 'salmon', margin: '0 0 0.5rem' }}>
Invalid Game ID format.
</p>
)}
<button <button
type="button" type="button"
onClick={fetchBoard} onClick={handleClick}
disabled={!gameUrl.trim() || loadingSetup} disabled={!isValid}
style={{ style={{
width: '100%', width: '100%',
padding: '0.75rem', padding: '0.75rem',
backgroundColor: '#ff2a6d', backgroundColor: isValid ? '#ff2a6d' : '#555',
border: 'none', border: 'none',
borderRadius: '20px', borderRadius: '20px',
color: '#fff', color: '#fff',
fontWeight: 'bold', fontWeight: 'bold',
cursor: 'pointer' cursor: isValid ? 'pointer' : 'not-allowed'
}} }}
> >
{loadingSetup ? 'Loading…' : 'FETCH BOARD'} {submitted ? 'Loaded' : 'LOAD BOARD'}
</button> </button>
</div> </div>
{/* <div style={{
background: 'rgba(255,255,255,0.05)',
border: '1px solid #ff2a6d',
borderRadius: '8px',
padding: '1rem',
marginBottom: '1rem'
}}>
</div> */}
<div style={{ <div style={{
flex: 1, flex: 1,
overflow: 'hidden', overflow: 'hidden',
borderRadius: '8px' borderRadius: '8px'
}}> }}>
{settings && gameId && ( {submitted && (
<iframe <iframe
src={`/proxy/?game=${encodeURIComponent(gameId)}&autoplay=false&showControls=true`} src={`https://gameboard-service-aged-glitter-8141.fly.dev/?game=${encodeURIComponent(gameId)}&autoplay=false&showControls=true`}
style={{ width: '100%', height: '100%', border: 'none' }}
title="Battlesnake Board" title="Battlesnake Board"
style={{ width: '100%', height: '100%', border: 'none' }}
allowFullScreen
scrolling="no"
/> />
)} )}
</div> </div>

View file

@ -3,6 +3,8 @@ import { useLocation } from "react-router-dom";
import EditorPanel from "../components/EditorPanel"; import EditorPanel from "../components/EditorPanel";
import PreviewPanel from "../components/PreviewPanel"; import PreviewPanel from "../components/PreviewPanel";
const ASSIGNMENT_BASE = "http://localhost:8082";
export default function PageCodeEditor() { export default function PageCodeEditor() {
const location = useLocation(); const location = useLocation();
@ -12,13 +14,15 @@ export default function PageCodeEditor() {
const [appName, setAppName] = useState(""); const [appName, setAppName] = useState("");
const [code, setCode] = useState("# NOW LOADING"); const [code, setCode] = useState("# NOW LOADING");
const [isSaving, setIsSaving] = useState(false);
const [isDeploying, setIsDeploying] = useState(false);
useEffect(() => { useEffect(() => {
document.title = "Snake Brain Editor"; document.title = "Snake Brain Editor";
}, []); }, []);
useEffect(() => { useEffect(() => {
fetch(`http://localhost:8082/student/assignment/${qrCodeNumber}`) fetch(`${ASSIGNMENT_BASE}/student/assignment/${qrCodeNumber}`)
.then((res) => { .then((res) => {
if (!res.ok) throw new Error("Failed to fetch assignment"); if (!res.ok) throw new Error("Failed to fetch assignment");
return res.json(); return res.json();
@ -29,7 +33,7 @@ export default function PageCodeEditor() {
useEffect(() => { useEffect(() => {
if (!appName) return; if (!appName) return;
fetch(`http://localhost:8082/notebook/${appName}`) fetch(`${ASSIGNMENT_BASE}/notebook/${appName}`)
.then((res) => { .then((res) => {
if (!res.ok) throw new Error("Failed to fetch notebook"); if (!res.ok) throw new Error("Failed to fetch notebook");
return res.json(); return res.json();
@ -44,6 +48,44 @@ export default function PageCodeEditor() {
.catch((err) => console.error("Notebook fetch error:", err)); .catch((err) => console.error("Notebook fetch error:", err));
}, [appName]); }, [appName]);
const handleSave = async () => {
if (isSaving) return;
setIsSaving(true);
try {
const res = await fetch(`${ASSIGNMENT_BASE}/student/save`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ appName, code })
});
if (!res.ok) throw new Error("Save failed");
alert("Notebook saved");
} catch (err) {
console.error("Save error:", err);
alert(`Save error: ${err.message}`);
} finally {
setIsSaving(false);
}
};
const handleDeploy = async () => {
if (isDeploying) return;
setIsDeploying(true);
try {
const res = await fetch(`${ASSIGNMENT_BASE}/student/restart`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ appName })
});
if (!res.ok) throw new Error("Restart failed");
alert("App restarted");
} catch (err) {
console.error("Restart error:", err);
alert(`Restart error: ${err.message}`);
} finally {
setIsDeploying(false);
}
};
return ( return (
<main className="code-editor-page" style={{ paddingTop: "35px" }}> <main className="code-editor-page" style={{ paddingTop: "35px" }}>
<div <div
@ -53,7 +95,7 @@ export default function PageCodeEditor() {
gap: "1rem", gap: "1rem",
width: "120rem", width: "120rem",
padding: "1rem", padding: "1rem",
fontFamily: "'Fira Code', 'Courier New', monospace", fontFamily: "'Fira Code', 'Courier New', monospace"
}} }}
> >
{/* Python Editor */} {/* Python Editor */}
@ -66,7 +108,7 @@ export default function PageCodeEditor() {
padding: "1rem", padding: "1rem",
color: "#eee", color: "#eee",
minHeight: "80vh", minHeight: "80vh",
overflow: "auto", overflow: "auto"
}} }}
> >
<h3 <h3
@ -75,7 +117,7 @@ export default function PageCodeEditor() {
fontSize: "1.2rem", fontSize: "1.2rem",
color: "#05d9e8", color: "#05d9e8",
textShadow: "0 0 5px #05d9e8", textShadow: "0 0 5px #05d9e8",
marginBottom: "1rem", marginBottom: "1rem"
}} }}
> >
🐍 Snake Brain (Python) 🐍 Snake Brain (Python)
@ -83,31 +125,38 @@ export default function PageCodeEditor() {
<EditorPanel code={code} onChange={setCode} /> <EditorPanel code={code} onChange={setCode} />
<div style={{ marginTop: "1rem", display: "flex" }}> <div style={{ marginTop: "1rem", display: "flex" }}>
<button <button
onClick={handleSave}
disabled={isSaving}
style={{ style={{
backgroundColor: "#ff2a6d", backgroundColor: "#ff2a6d",
color: "#fff", color: "#fff",
marginLeft: "50rem",
padding: "0.5rem 2rem", padding: "0.5rem 2rem",
border: "none", border: "none",
borderRadius: "20px", borderRadius: "20px",
fontWeight: "bold", fontWeight: "bold",
cursor: "pointer", cursor: isSaving ? "not-allowed" : "pointer"
}} }}
> >
Save {isSaving ? "Saving..." : "Save"}
</button> </button>
<button <button
onClick={handleDeploy}
disabled={isDeploying}
style={{ style={{
marginLeft: "1rem",
backgroundColor: "#ff2a6d", backgroundColor: "#ff2a6d",
color: "#fff", color: "#fff",
padding: "0.5rem 2rem", padding: "0.5rem 2rem",
marginLeft: "1rem",
border: "none", border: "none",
borderRadius: "20px", borderRadius: "20px",
fontWeight: "bold", fontWeight: "bold",
cursor: "pointer", cursor: isDeploying ? "not-allowed" : "pointer"
}} }}
> >
Deploy {isDeploying ? "Deploying..." : "Deploy"}
</button> </button>
</div> </div>
</div> </div>
@ -121,7 +170,7 @@ export default function PageCodeEditor() {
borderRadius: "12px", borderRadius: "12px",
padding: "1rem", padding: "1rem",
color: "#eee", color: "#eee",
minHeight: "80vh", minHeight: "80vh"
}} }}
> >
<h3 <h3
@ -132,7 +181,7 @@ export default function PageCodeEditor() {
marginBottom: "1rem", marginBottom: "1rem",
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
gap: "0.5rem", gap: "0.5rem"
}} }}
> >
🎯 Live Arena Output 🎯 Live Arena Output
@ -141,7 +190,7 @@ export default function PageCodeEditor() {
style={{ style={{
color: "#fff", color: "#fff",
textAlign: "center", textAlign: "center",
marginBottom: "1rem", marginBottom: "1rem"
}} }}
> >
Battlesnake Preview Battlesnake Preview