| 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 = useSelector((s) => s.data.settings.isCodeOpen)
- 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} isOpen={isOpen}>
- {isOpen ? (
- <Panel.Layout>
- <Panel.Header side="left">
- <IconButton onClick={() => state.send('TOGGLED_CODE_PANEL_OPEN')}>
- <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('TOGGLED_CODE_PANEL_OPEN')}>
- <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,
- },
- },
- })
|