123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205 |
- /* eslint-disable @typescript-eslint/ban-ts-comment */
- import styled from "styles"
- import { useStateDesigner } from "@state-designer/react"
- import React, { useEffect, useRef } from "react"
- import { motion } from "framer-motion"
- import state, { useSelector } from "state"
- import { CodeFile } from "types"
- import CodeDocs from "./code-docs"
- import CodeEditor from "./code-editor"
- import { generateFromCode } from "lib/code/generate"
- import * as Panel from "../panel"
- import { IconButton } from "../shared"
- import {
- X,
- Code,
- Info,
- PlayCircle,
- ChevronUp,
- ChevronDown,
- } from "react-feather"
-
- const getErrorLineAndColumn = (e: any) => {
- if ("line" in e) {
- return { line: Number(e.line), column: e.column }
- }
-
- const result = e.stack.match(/:([0-9]+):([0-9]+)/)
- if (result) {
- return { line: Number(result[1]) - 1, column: result[2] }
- }
- }
-
- export default function CodePanel() {
- const rContainer = useRef<HTMLDivElement>(null)
- const isReadOnly = useSelector((s) => s.data.isReadOnly)
- const fileId = useSelector((s) => s.data.currentCodeFileId)
- const file = useSelector(
- (s) => s.data.document.code[s.data.currentCodeFileId]
- )
- const isOpen = true
- const fontSize = useSelector((s) => s.data.settings.fontSize)
-
- const local = useStateDesigner({
- data: {
- code: file.code,
- error: null as { message: string; line: number; column: number } | null,
- },
- on: {
- MOUNTED: "setCode",
- CHANGED_FILE: "loadFile",
- },
- initial: "editingCode",
- states: {
- editingCode: {
- on: {
- RAN_CODE: ["saveCode", "runCode"],
- SAVED_CODE: ["saveCode", "runCode"],
- CHANGED_CODE: { secretlyDo: "setCode" },
- CLEARED_ERROR: { if: "hasError", do: "clearError" },
- TOGGLED_DOCS: { to: "viewingDocs" },
- },
- },
- viewingDocs: {
- on: {
- TOGGLED_DOCS: { to: "editingCode" },
- },
- },
- },
- conditions: {
- hasError(data) {
- return !!data.error
- },
- },
- actions: {
- loadFile(data, payload: { file: CodeFile }) {
- data.code = payload.file.code
- },
- setCode(data, payload: { code: string }) {
- data.code = payload.code
- },
- runCode(data) {
- let error = null
-
- try {
- const { shapes, controls } = generateFromCode(data.code)
- state.send("GENERATED_FROM_CODE", { shapes, controls })
- } catch (e) {
- console.error(e)
- error = { message: e.message, ...getErrorLineAndColumn(e) }
- }
-
- data.error = error
- },
- saveCode(data) {
- const { code } = data
- state.send("SAVED_CODE", { code })
- },
- clearError(data) {
- data.error = null
- },
- },
- })
-
- useEffect(() => {
- local.send("CHANGED_FILE", { file })
- }, [file])
-
- useEffect(() => {
- local.send("MOUNTED", { code: state.data.document.code[fileId].code })
- return () => {
- state.send("CHANGED_CODE", { fileId, code: local.data.code })
- }
- }, [])
-
- const { error } = local.data
-
- return (
- <Panel.Root data-bp-desktop ref={rContainer} isCollapsed={!isOpen}>
- {isOpen ? (
- <Panel.Layout>
- <Panel.Header>
- <IconButton onClick={() => state.send("CLOSED_CODE_PANEL")}>
- <X />
- </IconButton>
- <h3>Code</h3>
- <ButtonsGroup>
- <FontSizeButtons>
- <IconButton
- disabled={!local.isIn("editingCode")}
- onClick={() => state.send("INCREASED_CODE_FONT_SIZE")}
- >
- <ChevronUp />
- </IconButton>
- <IconButton
- disabled={!local.isIn("editingCode")}
- onClick={() => state.send("DECREASED_CODE_FONT_SIZE")}
- >
- <ChevronDown />
- </IconButton>
- </FontSizeButtons>
- <IconButton onClick={() => local.send("TOGGLED_DOCS")}>
- <Info />
- </IconButton>
- <IconButton
- disabled={!local.isIn("editingCode")}
- onClick={() => local.send("SAVED_CODE")}
- >
- <PlayCircle />
- </IconButton>
- </ButtonsGroup>
- </Panel.Header>
- <Panel.Content>
- <CodeEditor
- fontSize={fontSize}
- readOnly={isReadOnly}
- value={file.code}
- error={error}
- onChange={(code) => local.send("CHANGED_CODE", { code })}
- onSave={() => local.send("SAVED_CODE")}
- onKey={() => local.send("CLEARED_ERROR")}
- />
- <CodeDocs isHidden={!local.isIn("viewingDocs")} />
- </Panel.Content>
- <Panel.Footer>
- {error &&
- (error.line
- ? `(${Number(error.line) - 2}:${error.column}) ${error.message}`
- : error.message)}
- </Panel.Footer>
- </Panel.Layout>
- ) : (
- <IconButton onClick={() => state.send("OPENED_CODE_PANEL")}>
- <Code />
- </IconButton>
- )}
- </Panel.Root>
- )
- }
-
- const ButtonsGroup = styled("div", {
- gridRow: "1",
- gridColumn: "3",
- display: "flex",
- })
-
- const FontSizeButtons = styled("div", {
- paddingRight: 4,
- display: "flex",
- flexDirection: "column",
-
- "& > button": {
- height: "50%",
- "&:nth-of-type(1)": {
- alignItems: "flex-end",
- },
-
- "&:nth-of-type(2)": {
- alignItems: "flex-start",
- },
-
- "& svg": {
- height: 12,
- },
- },
- })
|