340 lines
11 KiB
TypeScript
340 lines
11 KiB
TypeScript
import * as React from "react";
|
|
import { Play, Bug, StepForward, RotateCcw, Square, ChevronUp, ChevronDown, LayoutGrid } from "lucide-react";
|
|
import { CodeBlockContainer, INIT_CODEBLOCKS, CodeBlock, INSERT_CODEBLOCK, REMOVE_CODEBLOCK, SET_DROPZONE_Z_INDEX } from "../store/codeBlocks/types";
|
|
import { LevelInitializer, IPlayerData } from "../game/levels";
|
|
import { initCodeBlocks, insertCodeBlock, removeCodeBlock } from "../store/codeBlocks/actions";
|
|
import { AppState } from "../store";
|
|
import game from "../game/game"
|
|
import { connect } from "react-redux";
|
|
import { Link } from "react-router-dom";
|
|
import { START_EXECUTING, STEP_EXECUTION, TOGGLE_AUTOSTEP, TOGGLE_SPEED } from "../store/executionState/types";
|
|
|
|
|
|
|
|
const CodeBlockComponent = ({ type, blockIdx, remove, container, setDropZoneZIdx }) => {
|
|
return (<div className="op"
|
|
draggable={true}
|
|
onDrag={(e) =>
|
|
e.preventDefault()}
|
|
onDragStart={(e) => {
|
|
// e.preventDefault()
|
|
for (let e of document.getElementsByClassName("opdropzone")) {
|
|
(e as HTMLElement).style.background = ""
|
|
}
|
|
// console.log("BOOM START DRAGGOING");
|
|
(e.target as HTMLElement).style.zIndex = "3"
|
|
|
|
setDropZoneZIdx(2)
|
|
|
|
dragged = type
|
|
}}
|
|
onDragEnd={(e) => {
|
|
e.preventDefault()
|
|
for (let e of document.getElementsByClassName("opdropzone")) {
|
|
(e as HTMLElement).style.background = ""
|
|
}
|
|
(e.target as HTMLElement).style.zIndex = ""
|
|
setDropZoneZIdx(-1)
|
|
remove(container, blockIdx)
|
|
// console.log("LE END OF DRAG")
|
|
}}
|
|
> <img src={"assets/icons/" + type + ".svg"}></img>
|
|
</div >)
|
|
}
|
|
|
|
// show how many operations are possible
|
|
const OpIndicators = ({ n }) => {
|
|
let ops = []
|
|
for (let i = 0; i < n; i++) {
|
|
ops.push(<div className="opindicator"
|
|
key={i}
|
|
></div>)
|
|
}
|
|
return (<div className="opindicators">{ops}</div>)
|
|
}
|
|
|
|
// dropzones which determine how to insert
|
|
const OpDropZones = ({ n, cidx, insert, style }) => {
|
|
let dropZones = []
|
|
for (let i = 0; i < n; i++) {
|
|
dropZones.push(
|
|
<div className="opdropzone"
|
|
key={i}
|
|
onDragEnter={(e) => {
|
|
e.preventDefault();
|
|
(e.target as HTMLElement).style.background = "purple"
|
|
}}
|
|
onDragLeave={(e) => {
|
|
e.preventDefault();
|
|
(e.target as HTMLElement).style.background = ""
|
|
}}
|
|
onDragOver={(e) => { e.preventDefault() }}
|
|
onDrop={(e) => {
|
|
e.preventDefault()
|
|
let dragOver = ((e.target as HTMLElement).className);
|
|
// console.log(dragOver)
|
|
// console.log("DRAGGED", dragged)
|
|
insert({ name: dragged }, cidx, i, true)
|
|
}}>
|
|
<div className="frontdropper"
|
|
onDragEnter={e => {
|
|
(e.target as HTMLElement).style.background = "purple"
|
|
}}
|
|
onDragOver={e => { e.preventDefault() }}
|
|
onDragLeave={e => {
|
|
(e.target as HTMLElement).style.background = ""
|
|
}}
|
|
onDrop={e => {
|
|
(e.target as HTMLElement).style.background = ""
|
|
insert({ name: dragged }, cidx, i, false)
|
|
}}>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
return (<div className="opdropzones" style={style}>{dropZones}</div>)
|
|
}
|
|
|
|
const FnContainers = ({ code, dropZoneZIdx, setDropZoneZIdx, remove, insert }) => {
|
|
return (
|
|
<div className="fncontainers">
|
|
{
|
|
code.map((cv, idx) => {
|
|
return (
|
|
<div className="fncontainer" key={idx} >
|
|
<h3>{cv.name + "(){"}</h3>
|
|
<div style={{ height: Math.floor(cv.nMaxBlocks / 4 + 1) * 4 + 'rem' }}>
|
|
<OpIndicators n={cv.nMaxBlocks} />
|
|
<div className="ops" >
|
|
{cv.blocks.map((b, i) => {
|
|
return (<CodeBlockComponent type={b.name}
|
|
key={i}
|
|
blockIdx={i}
|
|
container={idx}
|
|
remove={remove}
|
|
setDropZoneZIdx={setDropZoneZIdx} />)
|
|
})}
|
|
</div>
|
|
<OpDropZones n={cv.nMaxBlocks}
|
|
cidx={idx}
|
|
insert={insert}
|
|
style={{ zIndex: dropZoneZIdx }} />
|
|
</div>
|
|
<h3>{"}"}</h3>
|
|
</div>)
|
|
})
|
|
}
|
|
</div>)
|
|
}
|
|
|
|
|
|
var dragged: string;
|
|
|
|
const OpsPalette = ({ ops, setDropZoneZIdx }) => {
|
|
let allowedOps = ops.map((e, i) => {
|
|
return (
|
|
<CodeBlockComponent type={e}
|
|
key={i}
|
|
blockIdx={0}
|
|
container={0}
|
|
remove={() => undefined}
|
|
setDropZoneZIdx={setDropZoneZIdx} />)
|
|
})
|
|
return (<div className="opspalette">{allowedOps}</div>)
|
|
}
|
|
|
|
const RuntimeControls = ({ start, fast, step, running, level, code, autostep, toggleAutoStep, toggleSpeed, finished }) => {
|
|
let DebugButton = () => (
|
|
<div className="controlbutton debug"
|
|
onClick={() => {
|
|
start(level, code, level.playerPos)
|
|
}}>
|
|
<Bug size={16} />
|
|
<span>debug</span>
|
|
</div>
|
|
)
|
|
let StepButton = () => (
|
|
<div className="controlbutton step"
|
|
onClick={step}>
|
|
<StepForward size={16} />
|
|
<span>step</span>
|
|
</div>
|
|
)
|
|
|
|
let StartButton = () => (
|
|
<div className="controlbutton run"
|
|
onClick={() => {
|
|
start(level, code, level.playerPos)
|
|
toggleAutoStep()
|
|
step()
|
|
}}>
|
|
<Play size={16} />
|
|
<span>run</span>
|
|
</div>
|
|
)
|
|
let LevelsButton = () => (
|
|
<Link to="/levels/" className="controlbutton levels">
|
|
<LayoutGrid size={16} />
|
|
<span>levels</span>
|
|
</Link>
|
|
)
|
|
let ResetButton = () => (
|
|
<div className="controlbutton reset"
|
|
onClick={() => game.reset()}>
|
|
<RotateCcw size={16} />
|
|
<span>reset</span>
|
|
</div>
|
|
)
|
|
|
|
let ToggleSpeedButton = () => (
|
|
<div className="controlbutton speed"
|
|
onClick={toggleSpeed}>
|
|
{fast ? <ChevronDown size={16} /> : <ChevronUp size={16} />}
|
|
<span>{fast ? "slower" : "faster"}</span>
|
|
</div>
|
|
)
|
|
|
|
let AbortButton = () => (
|
|
<div className="controlbutton abort"
|
|
onClick={() => console.log("TODO")}>
|
|
<Square size={16} />
|
|
<span>abort</span>
|
|
</div>
|
|
)
|
|
|
|
if (running) {
|
|
if (autostep) {
|
|
return (
|
|
<div className="controlbuttons">
|
|
<ToggleSpeedButton />
|
|
<AbortButton />
|
|
</div >
|
|
)
|
|
}
|
|
return (
|
|
<div className="controlbuttons">
|
|
<StepButton />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (finished) {
|
|
return (<div className="controlbuttons">
|
|
<ResetButton />
|
|
</div>)
|
|
}
|
|
return (<div className="controlbuttons">
|
|
<LevelsButton />
|
|
<StartButton />
|
|
<DebugButton />
|
|
</div>)
|
|
}
|
|
|
|
|
|
export interface EditorProps {
|
|
level: LevelInitializer,
|
|
code?: Array<CodeBlockContainer>,
|
|
init: typeof initCodeBlocks,
|
|
insert: typeof insertCodeBlock,
|
|
remove: typeof removeCodeBlock,
|
|
setDropZoneZIdx: (n: number) => void
|
|
dropZoneZIdx: number,
|
|
start: (level: LevelInitializer, code: Array<Array<string>>, playerPos: IPlayerData) => void,
|
|
running: boolean,
|
|
step: () => void,
|
|
toggleSpeed: () => void,
|
|
autostep: boolean,
|
|
fast: boolean,
|
|
toggleAutoStep: () => void,
|
|
finished: boolean
|
|
}
|
|
|
|
class Editor extends React.Component<EditorProps> {
|
|
|
|
constructor(props: EditorProps) {
|
|
super(props)
|
|
|
|
let { init, level } = props
|
|
// console.log(props)
|
|
init(level)
|
|
// initCodeBlocks(props.level)
|
|
|
|
}
|
|
|
|
componentDidMount() {
|
|
// console.log("YES IT MOUNTS")
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
// console.log("ME NOW UNMOUNT!")
|
|
}
|
|
|
|
render() {
|
|
return (
|
|
<div className="editor">
|
|
<div className="titlebar">
|
|
<h2 className="levelname">{this.props.level.name}</h2>
|
|
|
|
<RuntimeControls
|
|
start={this.props.start}
|
|
fast={this.props.fast}
|
|
running={this.props.running}
|
|
level={this.props.level}
|
|
code={this.props.code}
|
|
step={this.props.step}
|
|
autostep={this.props.autostep}
|
|
toggleAutoStep={this.props.toggleAutoStep}
|
|
toggleSpeed={this.props.toggleSpeed}
|
|
finished={this.props.finished} />
|
|
</div>
|
|
<FnContainers code={this.props.code}
|
|
dropZoneZIdx={this.props.dropZoneZIdx}
|
|
setDropZoneZIdx={this.props.setDropZoneZIdx}
|
|
remove={this.props.remove}
|
|
insert={this.props.insert} />
|
|
<OpsPalette ops={this.props.level.allowedCodeBlocks}
|
|
setDropZoneZIdx={this.props.setDropZoneZIdx} />
|
|
</div>);
|
|
}
|
|
}
|
|
|
|
const mapStateToProps = (state: AppState, ownProps?: { level: LevelInitializer }) => {
|
|
// console.log(ownProps)
|
|
return {
|
|
...ownProps,
|
|
code: state.codeBlocks,
|
|
dropZoneZIdx: state.opDropZoneZIdx,
|
|
running: state.executionState.running,
|
|
autostep: state.executionState.autostep,
|
|
finished: state.executionState.finished,
|
|
fast: state.executionState.fast
|
|
}
|
|
}
|
|
|
|
const mapDispatchToProps = (dispatch) => {
|
|
return {
|
|
init: (level: LevelInitializer) => dispatch({ type: INIT_CODEBLOCKS, level: level }),
|
|
insert: (cb: CodeBlock, cidx: number, bidx: number, ov: boolean) =>
|
|
dispatch({ type: INSERT_CODEBLOCK, block: cb, containerIdx: cidx, blocksIdx: bidx, overwrite: ov }),
|
|
remove: (cidx: number, bidx: number) => {
|
|
dispatch({
|
|
type: REMOVE_CODEBLOCK,
|
|
containerIdx: cidx, blocksIdx: bidx
|
|
})
|
|
},
|
|
setDropZoneZIdx: (idx: number) => {
|
|
dispatch({ type: SET_DROPZONE_Z_INDEX, zIdx: idx })
|
|
},
|
|
start: (level: LevelInitializer, code: Array<Array<string>>, playerPos: IPlayerData) => {
|
|
dispatch({ type: START_EXECUTING, level: level, code: code, playerPos: playerPos })
|
|
},
|
|
step: () => dispatch({ type: STEP_EXECUTION }),
|
|
toggleAutoStep: () => dispatch({ type: TOGGLE_AUTOSTEP }),
|
|
toggleSpeed: () => dispatch({ type: TOGGLE_SPEED })
|
|
}
|
|
}
|
|
|
|
export default connect(mapStateToProps, mapDispatchToProps)(Editor);
|
|
|
|
|