Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

code-panel.tsx 6.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. /* eslint-disable @typescript-eslint/ban-ts-comment */
  2. import styled from 'styles'
  3. import { useStateDesigner } from '@state-designer/react'
  4. import React, { useEffect, useRef } from 'react'
  5. import state, { useSelector } from 'state'
  6. import { CodeFile } from 'types'
  7. import CodeDocs from './code-docs'
  8. import CodeEditor from './code-editor'
  9. import { generateFromCode } from 'state/code/generate'
  10. import * as Panel from '../panel'
  11. import { IconButton } from '../shared'
  12. import {
  13. X,
  14. Code,
  15. Info,
  16. PlayCircle,
  17. ChevronUp,
  18. ChevronDown,
  19. } from 'react-feather'
  20. const getErrorLineAndColumn = (e: any) => {
  21. if ('line' in e) {
  22. return { line: Number(e.line), column: e.column }
  23. }
  24. const result = e.stack.match(/:([0-9]+):([0-9]+)/)
  25. if (result) {
  26. return { line: Number(result[1]) - 1, column: result[2] }
  27. }
  28. }
  29. export default function CodePanel(): JSX.Element {
  30. const rContainer = useRef<HTMLDivElement>(null)
  31. const isReadOnly = useSelector((s) => s.data.isReadOnly)
  32. const fileId = useSelector((s) => s.data.currentCodeFileId)
  33. const file = useSelector(
  34. (s) => s.data.document.code[s.data.currentCodeFileId]
  35. )
  36. const isOpen = useSelector((s) => s.data.settings.isCodeOpen)
  37. const fontSize = useSelector((s) => s.data.settings.fontSize)
  38. const local = useStateDesigner({
  39. data: {
  40. code: file.code,
  41. error: null as { message: string; line: number; column: number } | null,
  42. },
  43. on: {
  44. MOUNTED: 'setCode',
  45. CHANGED_FILE: 'loadFile',
  46. },
  47. initial: 'editingCode',
  48. states: {
  49. editingCode: {
  50. on: {
  51. RAN_CODE: ['saveCode', 'runCode'],
  52. SAVED_CODE: ['saveCode', 'runCode'],
  53. CHANGED_CODE: { secretlyDo: 'setCode' },
  54. CLEARED_ERROR: { if: 'hasError', do: 'clearError' },
  55. TOGGLED_DOCS: { to: 'viewingDocs' },
  56. },
  57. },
  58. viewingDocs: {
  59. on: {
  60. TOGGLED_DOCS: { to: 'editingCode' },
  61. },
  62. },
  63. },
  64. conditions: {
  65. hasError(data) {
  66. return !!data.error
  67. },
  68. },
  69. actions: {
  70. loadFile(data, payload: { file: CodeFile }) {
  71. data.code = payload.file.code
  72. },
  73. setCode(data, payload: { code: string }) {
  74. data.code = payload.code
  75. },
  76. runCode(data) {
  77. let error = null
  78. try {
  79. const { shapes, controls } = generateFromCode(state.data, data.code)
  80. state.send('GENERATED_FROM_CODE', { shapes, controls })
  81. } catch (e) {
  82. console.error(e)
  83. error = { message: e.message, ...getErrorLineAndColumn(e) }
  84. }
  85. data.error = error
  86. },
  87. saveCode(data) {
  88. const { code } = data
  89. state.send('SAVED_CODE', { code })
  90. },
  91. clearError(data) {
  92. data.error = null
  93. },
  94. },
  95. })
  96. useEffect(() => {
  97. local.send('CHANGED_FILE', { file })
  98. }, [file])
  99. useEffect(() => {
  100. local.send('MOUNTED', { code: state.data.document.code[fileId].code })
  101. return () => {
  102. state.send('CHANGED_CODE', { fileId, code: local.data.code })
  103. }
  104. }, [])
  105. const { error } = local.data
  106. return (
  107. <Panel.Root
  108. bp={{ '@initial': 'mobile', '@sm': 'small' }}
  109. data-bp-desktop
  110. ref={rContainer}
  111. isOpen={isOpen}
  112. variant="code"
  113. >
  114. {isOpen ? (
  115. <Panel.Layout>
  116. <Panel.Header side="left">
  117. <IconButton
  118. bp={{ '@initial': 'mobile', '@sm': 'small' }}
  119. size="small"
  120. onClick={() => state.send('TOGGLED_CODE_PANEL_OPEN')}
  121. >
  122. <X />
  123. </IconButton>
  124. <h3>Code</h3>
  125. <ButtonsGroup>
  126. <FontSizeButtons>
  127. <IconButton
  128. bp={{ '@initial': 'mobile', '@sm': 'small' }}
  129. size="small"
  130. disabled={!local.isIn('editingCode')}
  131. onClick={() => state.send('INCREASED_CODE_FONT_SIZE')}
  132. >
  133. <ChevronUp />
  134. </IconButton>
  135. <IconButton
  136. size="small"
  137. disabled={!local.isIn('editingCode')}
  138. onClick={() => state.send('DECREASED_CODE_FONT_SIZE')}
  139. >
  140. <ChevronDown />
  141. </IconButton>
  142. </FontSizeButtons>
  143. <IconButton
  144. bp={{ '@initial': 'mobile', '@sm': 'small' }}
  145. size="small"
  146. onClick={() => local.send('TOGGLED_DOCS')}
  147. >
  148. <Info />
  149. </IconButton>
  150. <IconButton
  151. bp={{ '@initial': 'mobile', '@sm': 'small' }}
  152. size="small"
  153. disabled={!local.isIn('editingCode')}
  154. onClick={() => local.send('SAVED_CODE')}
  155. >
  156. <PlayCircle />
  157. </IconButton>
  158. </ButtonsGroup>
  159. </Panel.Header>
  160. <Panel.Content>
  161. <CodeEditor
  162. fontSize={fontSize}
  163. readOnly={isReadOnly}
  164. value={file.code}
  165. error={error}
  166. onChange={(code) => local.send('CHANGED_CODE', { code })}
  167. onSave={() => local.send('SAVED_CODE')}
  168. onKey={() => local.send('CLEARED_ERROR')}
  169. />
  170. <CodeDocs isHidden={!local.isIn('viewingDocs')} />
  171. </Panel.Content>
  172. <Panel.Footer>
  173. {error &&
  174. (error.line
  175. ? `(${Number(error.line) - 2}:${error.column}) ${error.message}`
  176. : error.message)}
  177. </Panel.Footer>
  178. </Panel.Layout>
  179. ) : (
  180. <IconButton
  181. bp={{ '@initial': 'mobile', '@sm': 'small' }}
  182. size="small"
  183. onClick={() => state.send('TOGGLED_CODE_PANEL_OPEN')}
  184. >
  185. <Code />
  186. </IconButton>
  187. )}
  188. </Panel.Root>
  189. )
  190. }
  191. const ButtonsGroup = styled('div', {
  192. gridRow: '1',
  193. gridColumn: '3',
  194. display: 'flex',
  195. })
  196. const FontSizeButtons = styled('div', {
  197. paddingRight: 4,
  198. display: 'flex',
  199. flexDirection: 'column',
  200. '& > button': {
  201. height: '50%',
  202. '&:nth-of-type(1)': {
  203. alignItems: 'flex-end',
  204. },
  205. '&:nth-of-type(2)': {
  206. alignItems: 'flex-start',
  207. },
  208. '& svg': {
  209. height: 12,
  210. },
  211. },
  212. })