Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

code-panel.tsx 5.5KB

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