floppy-venture/src/components/Editor.tsx
2026-02-24 03:55:15 +01:00

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);