Changed layout, three-column
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useCallback, useRef } from 'react'
|
import React, {useState, useCallback, useRef, useEffect} from 'react'
|
||||||
import ForgeEditor from "./forge_editor"
|
import ForgeEditor from "./forge_editor"
|
||||||
import EmulatorDisplay from "./emulator_display"
|
import EmulatorDisplay from "./emulator_display"
|
||||||
import { WasmCPU, assemble_snippet, compile_forge } from '../pkg/vweb.js'
|
import { WasmCPU, assemble_snippet, compile_forge } from '../pkg/vweb.js'
|
||||||
@@ -14,17 +14,42 @@ export default function({ src: defaultSrc }) {
|
|||||||
// Whether the emulator should be running. Has to be a ref because the CPU setTimeout loop won't ever see changes in it otherwise
|
// Whether the emulator should be running. Has to be a ref because the CPU setTimeout loop won't ever see changes in it otherwise
|
||||||
const running = useRef(false)
|
const running = useRef(false)
|
||||||
|
|
||||||
|
const [currentFile, setCurrentFile] = useState(() => (Object.keys(window.localStorage)[0] || 'example'))
|
||||||
|
useEffect(() => {
|
||||||
|
if (!currentFile) {
|
||||||
|
setCurrentFile('example')
|
||||||
|
window.localStorage.setItem('example', defaultSrc)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const selectFile = useCallback((name) => {
|
||||||
|
setSrc(window.localStorage.getItem(name))
|
||||||
|
setCurrentFile(name)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const addFile = useCallback(() => {
|
||||||
|
const fileList = Object.keys(window.localStorage)
|
||||||
|
let name = null
|
||||||
|
while(!name || fileList.indexOf(name) >= 0) {
|
||||||
|
name = prompt('Create new file named:')
|
||||||
|
}
|
||||||
|
window.localStorage[name] = ''
|
||||||
|
selectFile(name)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const removeFile = useCallback((name) => {}, [])
|
||||||
|
|
||||||
// The current Forge source. Our initial state tries to look something up from localStorage,
|
// The current Forge source. Our initial state tries to look something up from localStorage,
|
||||||
// then if that fails uses the prop.
|
// then if that fails uses the prop.
|
||||||
const [src, setSrc] = useState(() => {
|
const [src, setSrc] = useState(() => {
|
||||||
return window.localStorage.getItem('src') || undent(defaultSrc)
|
return window.localStorage.getItem(currentFile) || undent(defaultSrc)
|
||||||
})
|
})
|
||||||
|
|
||||||
// When we update the src, also update the key in localStorage
|
// When we update the src, also update the key in localStorage
|
||||||
const updateSrc = useCallback((newSrc) => {
|
const updateSrc = useCallback((newSrc) => {
|
||||||
setSrc(newSrc)
|
setSrc(newSrc)
|
||||||
window.localStorage.setItem('src', newSrc)
|
window.localStorage.setItem(currentFile, newSrc)
|
||||||
}, [])
|
}, [currentFile])
|
||||||
|
|
||||||
// Callback for the build button
|
// Callback for the build button
|
||||||
const build = useCallback(() => {
|
const build = useCallback(() => {
|
||||||
@@ -71,21 +96,42 @@ export default function({ src: defaultSrc }) {
|
|||||||
setStatus('Stopped by user')
|
setStatus('Stopped by user')
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
let content;
|
||||||
|
if (activeTab === 'assembly') {
|
||||||
|
content = <pre>{assembly}</pre>
|
||||||
|
} else if (activeTab === 'errors') {
|
||||||
|
content = <pre>{errors}</pre>
|
||||||
|
} else if (activeTab === 'editor') {
|
||||||
|
content = <ForgeEditor build={build} run={run} updateSrc={updateSrc} src={src} fileName={currentFile}/>
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Tabbar activeTab={activeTab} setActiveTab={setActiveTab} anyErrors={!!errors}/>
|
<Tabbar activeTab={activeTab} setActiveTab={setActiveTab} anyErrors={!!errors}/>
|
||||||
<Toolbar activeTab={activeTab} running={running.current} build={build} run={run} stop={stop}/>
|
<Toolbar activeTab={activeTab} running={running.current} build={build} run={run} stop={stop} addFile={addFile} removeFile={removeFile}/>
|
||||||
<div className='content'>
|
<FileList fileList={Object.keys(window.localStorage)} selectFile={selectFile}/>
|
||||||
{activeTab === 'editor' && <ForgeEditor build={build} run={run} updateSrc={updateSrc} src={src}/>}
|
<div className='content'>{content}</div>
|
||||||
{activeTab === 'assembly' && <pre>{assembly}</pre>}
|
<EmulatorDisplay cpu={cpu}/>
|
||||||
{activeTab === 'display' && <EmulatorDisplay cpu={cpu}/>}
|
|
||||||
{activeTab === 'errors' && <pre>{errors}</pre>}
|
|
||||||
</div>
|
|
||||||
<Status message={status}/>
|
<Status message={status}/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A list of "files" stored in localStorage, that we can display / build / compile
|
||||||
|
function FileList({ fileList, selectFile }) {
|
||||||
|
const clickFile = useCallback((event) => {
|
||||||
|
const name = event.target.getAttribute('data-name')
|
||||||
|
selectFile(name)
|
||||||
|
event.preventDefault()
|
||||||
|
}, [selectFile])
|
||||||
|
const fileRows = fileList.map(file => <div className='file' onClick={clickFile} data-name={file} key={file}>{file}</div>)
|
||||||
|
return (
|
||||||
|
<div className='files'>
|
||||||
|
{fileRows}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// A tabbar of the various pages in the simulator
|
// A tabbar of the various pages in the simulator
|
||||||
function Tabbar({ activeTab, setActiveTab, anyErrors }) {
|
function Tabbar({ activeTab, setActiveTab, anyErrors }) {
|
||||||
let onChangeTab = useCallback((event) => {
|
let onChangeTab = useCallback((event) => {
|
||||||
@@ -97,7 +143,6 @@ function Tabbar({ activeTab, setActiveTab, anyErrors }) {
|
|||||||
return (
|
return (
|
||||||
<div className='tabbar'>
|
<div className='tabbar'>
|
||||||
<a className={classNames('editor')} onClick={onChangeTab} data-tab='editor'>[Editor]</a>
|
<a className={classNames('editor')} onClick={onChangeTab} data-tab='editor'>[Editor]</a>
|
||||||
<a className={classNames('display')} onClick={onChangeTab} data-tab='display'>[Display]</a>
|
|
||||||
<a className={classNames('assembly')} onClick={onChangeTab} data-tab='assembly'>[Assembly]</a>
|
<a className={classNames('assembly')} onClick={onChangeTab} data-tab='assembly'>[Assembly]</a>
|
||||||
{anyErrors && <a className={classNames('errors')} onClick={onChangeTab} data-tab='errors'>[Errors]</a>}
|
{anyErrors && <a className={classNames('errors')} onClick={onChangeTab} data-tab='errors'>[Errors]</a>}
|
||||||
</div>
|
</div>
|
||||||
@@ -105,7 +150,7 @@ function Tabbar({ activeTab, setActiveTab, anyErrors }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Toolbar of the controls for the simulator
|
// Toolbar of the controls for the simulator
|
||||||
function Toolbar({ activeTab, running, compile, build, run, stop }) {
|
function Toolbar({activeTab, running, compile, build, run, stop, addFile, removeFile }) {
|
||||||
let buildBtn, runBtn
|
let buildBtn, runBtn
|
||||||
// TODO: Make this assemble on assembly tab, make that editable
|
// TODO: Make this assemble on assembly tab, make that editable
|
||||||
buildBtn = <a className='build' onClick={build}>[Build]</a>
|
buildBtn = <a className='build' onClick={build}>[Build]</a>
|
||||||
@@ -117,14 +162,20 @@ function Toolbar({ activeTab, running, compile, build, run, stop }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='buttons'>
|
<>
|
||||||
{buildBtn}
|
<div className='file-buttons'>
|
||||||
{runBtn}
|
<a className='new' onClick={addFile}>[new]</a>
|
||||||
</div>
|
<a className='del' onClick={removeFile}>[del]</a>
|
||||||
|
</div>
|
||||||
|
<div className='buttons'>
|
||||||
|
{buildBtn}
|
||||||
|
{runBtn}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The short status line at the bottom
|
// The short status line at the bottom
|
||||||
function Status({ message }) {
|
function Status({message}) {
|
||||||
return <div className='status'>{message}</div>
|
return <div className='status'>{message}</div>
|
||||||
}
|
}
|
||||||
+17
-32
@@ -10,18 +10,19 @@ a { color: #cccccc; }
|
|||||||
.simulator {
|
.simulator {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template:
|
grid-template:
|
||||||
"tab" 1em
|
"filebtns tab btns" 1em
|
||||||
"btns" 1em
|
"files content screen " 1fr
|
||||||
"content" 1fr
|
"files status screen" 1em / 16em 1fr calc(640px + 3em);
|
||||||
"status" 1em / 1fr;
|
|
||||||
row-gap: 0.3em;
|
row-gap: 0.3em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Setting where things go in the grid */
|
/* Setting where things go in the grid */
|
||||||
.tabbar { grid-area: tab }
|
.tabbar { grid-area: tab }
|
||||||
.buttons { grid-area: btns }
|
.buttons { grid-area: btns }
|
||||||
|
.file-buttons { grid-area: filebtns }
|
||||||
.content { grid-area: content }
|
.content { grid-area: content }
|
||||||
.status { grid-area: status }
|
.status { grid-area: status }
|
||||||
|
.files { grid-area: files }
|
||||||
|
|
||||||
/* Make the content fill the remainder of the screen */
|
/* Make the content fill the remainder of the screen */
|
||||||
.content {
|
.content {
|
||||||
@@ -40,48 +41,32 @@ a { color: #cccccc; }
|
|||||||
height: calc(100vh - 5em)
|
height: calc(100vh - 5em)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!* Make the textarea look presentable (TODO: replace with CodeMirror) *!*/
|
|
||||||
/*.content textarea {*/
|
|
||||||
/* height: 100%;*/
|
|
||||||
/* width: 100%;*/
|
|
||||||
/* padding: 0;*/
|
|
||||||
/* margin: 0;*/
|
|
||||||
/* background: #000000;*/
|
|
||||||
/* color: #a0a0a0;*/
|
|
||||||
/* border: none;*/
|
|
||||||
/* resize: none;*/
|
|
||||||
/* outline: none;*/
|
|
||||||
/*}*/
|
|
||||||
|
|
||||||
/*.forge-editor textarea:focus {*/
|
|
||||||
/* border: none;*/
|
|
||||||
/* outline: none;*/
|
|
||||||
/*}*/
|
|
||||||
|
|
||||||
/* Status, btns, tabs all look right */
|
/* Status, btns, tabs all look right */
|
||||||
.status, .buttons, .tabbar { font-weight: bold; }
|
.status, .buttons, .file-buttons, .tabbar { font-weight: bold; }
|
||||||
.tabbar a, .buttons a { cursor: pointer; }
|
.tabbar a, .buttons a, .file-buttons a, .file { cursor: pointer; }
|
||||||
|
|
||||||
/* Make the display the right size by centering the canvas in a rectangle*/
|
/* Make the display the right size by centering the canvas in a rectangle */
|
||||||
|
/* The display is in the screen section of the main grid but it's also got to define its own grid */
|
||||||
.display {
|
.display {
|
||||||
|
grid-area: screen;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template:
|
grid-template:
|
||||||
"lt top rt" 1fr
|
"lt top rt" 1fr
|
||||||
"lt screen rt" calc(480px + 4em)
|
"lt screen rt" calc(480px + 2em)
|
||||||
"lt btm rt" 1fr / 1fr calc(640px + 4em) 1fr;
|
"lt btm rt" 1fr / 1fr calc(640px + 2em) 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The beige monitor border around the screen */
|
/* The beige monitor border around the screen */
|
||||||
.display { background-color: #0f0f0f }
|
.display { background-color: #0f0f0f }
|
||||||
.display canvas {
|
.display canvas {
|
||||||
grid-area: screen;
|
grid-area: screen;
|
||||||
border: 2em solid #bb9;
|
border: 1em solid #bb9;
|
||||||
border-radius: 2em;
|
border-radius: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Colors of tabs and buttons */
|
/* Colors of tabs and buttons */
|
||||||
.tabbar a.active { background-color: #686868; }
|
.tabbar a.active { background-color: #686868; }
|
||||||
.buttons .run { color: #66bb66 }
|
.buttons .run, .file-buttons .new { color: #66bb66 }
|
||||||
.buttons .stop { color: #bb6666 }
|
.buttons .stop, .file-buttons .del { color: #bb6666 }
|
||||||
.buttons .build { color: #aaaaff; }
|
.buttons .build { color: #aaaaff; }
|
||||||
|
|||||||
Reference in New Issue
Block a user