| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313 |
- /* 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 { getShapesFromCode } from "lib/code/generate"
- 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 fileId = "file0"
- const isReadOnly = useSelector((s) => s.data.isReadOnly)
- const file = useSelector((s) => s.data.document.code[fileId])
- 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: "runCode",
- SAVED_CODE: ["runCode", "saveCode"],
- 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 = getShapesFromCode(data.code)
- state.send("GENERATED_SHAPES_FROM_CODE", { shapes })
- } catch (e) {
- console.error(e)
- error = { message: e.message, ...getErrorLineAndColumn(e) }
- }
-
- data.error = error
- },
- saveCode(data) {
- state.send("CHANGED_CODE", { fileId, code: data.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 (
- <PanelContainer
- data-bp-desktop
- ref={rContainer}
- dragMomentum={false}
- isCollapsed={!isOpen}
- >
- {isOpen ? (
- <Content>
- <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>
- </Header>
- <EditorContainer>
- <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")} />
- </EditorContainer>
- <ErrorContainer>
- {error &&
- (error.line
- ? `(${Number(error.line) - 2}:${error.column}) ${error.message}`
- : error.message)}
- </ErrorContainer>
- </Content>
- ) : (
- <IconButton onClick={() => state.send("OPENED_CODE_PANEL")}>
- <Code />
- </IconButton>
- )}
- </PanelContainer>
- )
- }
-
- const PanelContainer = styled(motion.div, {
- position: "absolute",
- top: "8px",
- right: "8px",
- bottom: "48px",
- backgroundColor: "$panel",
- borderRadius: "4px",
- overflow: "hidden",
- border: "1px solid $border",
- pointerEvents: "all",
- userSelect: "none",
- zIndex: 200,
-
- button: {
- border: "none",
- },
-
- variants: {
- isCollapsed: {
- true: {},
- false: {},
- },
- },
- })
-
- const IconButton = styled("button", {
- height: "40px",
- width: "40px",
- backgroundColor: "$panel",
- borderRadius: "4px",
- border: "1px solid $border",
- padding: "0",
- margin: "0",
- display: "flex",
- alignItems: "center",
- justifyContent: "center",
- outline: "none",
- pointerEvents: "all",
- cursor: "pointer",
-
- "&:hover:not(:disabled)": {
- backgroundColor: "$panel",
- },
-
- "&:disabled": {
- opacity: "0.5",
- },
-
- svg: {
- height: "20px",
- width: "20px",
- strokeWidth: "2px",
- stroke: "$text",
- },
- })
-
- const Content = styled("div", {
- display: "grid",
- gridTemplateColumns: "1fr",
- gridTemplateRows: "auto 1fr 28px",
- height: "100%",
- width: 560,
- minWidth: "100%",
- maxWidth: 560,
- overflow: "hidden",
- userSelect: "none",
- pointerEvents: "all",
- })
-
- const Header = styled("div", {
- pointerEvents: "all",
- display: "grid",
- gridTemplateColumns: "auto 1fr",
- alignItems: "center",
- justifyContent: "center",
- borderBottom: "1px solid $border",
-
- "& button": {
- gridColumn: "1",
- gridRow: "1",
- },
-
- "& h3": {
- gridColumn: "1 / span 3",
- gridRow: "1",
- textAlign: "center",
- margin: "0",
- padding: "0",
- fontSize: "16px",
- },
- })
-
- const ButtonsGroup = styled("div", {
- gridRow: "1",
- gridColumn: "3",
- display: "flex",
- })
-
- const EditorContainer = styled("div", {
- position: "relative",
- pointerEvents: "all",
- overflowY: "scroll",
- })
-
- const ErrorContainer = styled("div", {
- overflowX: "scroll",
- color: "$text",
- font: "$debug",
- padding: "0 12px",
- display: "flex",
- alignItems: "center",
- })
-
- const FontSizeButtons = styled("div", {
- paddingRight: 4,
-
- "& > button": {
- height: "50%",
- width: "100%",
-
- "&:nth-of-type(1)": {
- paddingTop: 4,
- },
-
- "&:nth-of-type(2)": {
- paddingBottom: 4,
- },
-
- "& svg": {
- height: 12,
- },
- },
- })
|