Initial commit: KiddOs v3 React kiosk
- React 18 + Vite 5 hot-reload OS (800x480 Nord theme) - 7 apps: Journal, Calculator, Drawing, CodeLab, Clock, Files, Scratch - Cage + Cog Wayland kiosk, offline localStorage - Scripts: dev.sh, run-kiosk.sh, run-prod.sh
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
import { useState } from 'react'
|
||||
|
||||
const BLOCKS = [
|
||||
{emoji:'🚀', name:'Move 10', code:'x += 10;'},
|
||||
{emoji:'🔁', name:'Repeat', code:'for(let i=0;i<4;i++){\n console.log("step "+i);\n}'},
|
||||
{emoji:'🎨', name:'Draw', code:'ctx.fillStyle="#88C0D0";\nctx.fillRect(x,y,40,40);'},
|
||||
{emoji:'🔊', name:'Say', code:'console.log("Hello!");'},
|
||||
]
|
||||
|
||||
export default function Scratch(){
|
||||
const [running, setRunning] = useState(false)
|
||||
const [log, setLog] = useState(['Scratch offline • No internet • Local only','Drag blocks is todo — for now edit JS below'])
|
||||
const [code, setCode] = useState(`// Offline Scratch-like playground\n// No external URLs, localhost only\nlet x=20, y=20;\nfunction drawBlock(){\n console.log("Drawing at "+x+","+y);\n x+=30;\n}\nfor(let i=0;i<5;i++) drawBlock();\nconsole.log("Done! Try editing.\n");`)
|
||||
|
||||
const run = ()=>{
|
||||
setRunning(true)
|
||||
const out=[]
|
||||
const c = {log: (...a)=> out.push(a.join(' '))}
|
||||
try{
|
||||
const fn = new Function('console','x','y','ctx','out', code+'\nreturn out;')
|
||||
// dummy ctx for offline
|
||||
const dummyCtx = {fillStyle:'', fillRect:()=>{}}
|
||||
fn(c, 20,20, dummyCtx, out)
|
||||
setLog([`▶ Ran at ${new Date().toLocaleTimeString()}`, ...out])
|
||||
}catch(e){
|
||||
setLog(['Error: '+e.message])
|
||||
}
|
||||
setRunning(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{display:'flex', height:'100%', background:'#2E3440'}}>
|
||||
<div style={{width:'32%', background:'#232A36', borderRight:'1px solid #4C566A', padding:'8px', display:'flex', flexDirection:'column', gap:'8px'}}>
|
||||
<div style={{fontSize:'12px', fontWeight:'700', color:'#88C0D0'}}>🧱 Blocks • Offline only</div>
|
||||
<div style={{fontSize:'10px', color:'#8E99A8'}}>No internet • localhost only • file:// only • Parent boundary respected</div>
|
||||
{BLOCKS.map(b=>(
|
||||
<div key={b.name} style={{background:'#3B4252', border:'1px solid #4C566A', borderRadius:'8px', padding:'8px', display:'flex', gap:'8px', alignItems:'center'}}>
|
||||
<span style={{fontSize:'20px'}}>{b.emoji}</span>
|
||||
<div style={{flex:1}}>
|
||||
<div style={{fontSize:'12px', fontWeight:'600'}}>{b.name}</div>
|
||||
<div style={{fontSize:'10px', color:'#8E99A8', fontFamily:'monospace'}}>{b.code.slice(0,20)}...</div>
|
||||
</div>
|
||||
<button onClick={()=>setCode(c=>c+'\n'+b.code)} style={{padding:'4px 8px', borderRadius:'6px', fontSize:'11px'}}>Add</button>
|
||||
</div>
|
||||
))}
|
||||
<div style={{marginTop:'auto', background:'#3B4252', border:'1px dashed #4C566A', borderRadius:'8px', padding:'8px', fontSize:'11px', color:'#8E99A8'}}>
|
||||
Future: real block drag-and-drop using Blockly local files (no CDN). Current: JS playground, offline.
|
||||
</div>
|
||||
</div>
|
||||
<div style={{flex:1, display:'flex', flexDirection:'column'}}>
|
||||
<div style={{display:'flex', gap:'8px', padding:'8px', background:'#3B4252', borderBottom:'1px solid #4C566A'}}>
|
||||
<button onClick={run} disabled={running} style={{background:'#A3BE8C', color:'#000', border:'none', padding:'8px 16px', borderRadius:'8px', fontWeight:'700'}}>{running?'Running...':'▶ Run'}</button>
|
||||
<button onClick={()=>setCode('')} style={{background:'#434C5E', border:'1px solid #4C566A', color:'#ECEFF4', padding:'8px 12px', borderRadius:'8px'}}>Clear</button>
|
||||
<span style={{fontSize:'11px', color:'#8E99A8', alignSelf:'center', marginLeft:'8px'}}>Offline • No external IP • Vite HMR localhost:5173 only</span>
|
||||
</div>
|
||||
<textarea value={code} onChange={e=>setCode(e.target.value)} style={{flex:1, background:'#1E222E', color:'#ECEFF4', border:'none', padding:'12px', fontFamily:'monospace', fontSize:'13px', resize:'none', outline:'none'}} spellCheck={false}/>
|
||||
<div style={{height:'32%', background:'#000', borderTop:'1px solid #4C566A', padding:'8px', overflow:'auto'}}>
|
||||
<div style={{fontSize:'11px', color:'#8E99A8', marginBottom:'4px'}}>Output:</div>
|
||||
{log.map((l,i)=><div key={i} style={{fontFamily:'monospace', fontSize:'12px', color:'#A3BE8C', whiteSpace:'pre-wrap'}}>{l}</div>)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user