You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

debug-panel.tsx 6.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. /* eslint-disable @typescript-eslint/ban-ts-comment */
  2. import styled from 'styles'
  3. import React, { useRef } from 'react'
  4. import state, { useSelector } from 'state'
  5. import * as Panel from '../panel'
  6. import { breakpoints, IconButton, RowButton, IconWrapper } from '../shared'
  7. import {
  8. Cross2Icon,
  9. PlayIcon,
  10. DotIcon,
  11. CrumpledPaperIcon,
  12. StopIcon,
  13. ClipboardIcon,
  14. ClipboardCopyIcon,
  15. TrashIcon,
  16. } from '@radix-ui/react-icons'
  17. import logger from 'state/logger'
  18. import { useStateDesigner } from '@state-designer/react'
  19. const stopPropagation = (e: React.KeyboardEvent) => e.stopPropagation()
  20. const toggleDebugPanel = () => state.send('TOGGLED_DEBUG_PANEL')
  21. const handleStateCopy = () => state.send('COPIED_STATE_TO_CLIPBOARD')
  22. const handleError = () => {
  23. throw Error('Error!')
  24. }
  25. export default function CodePanel(): JSX.Element {
  26. const rContainer = useRef<HTMLDivElement>(null)
  27. const isDebugging = useSelector((s) => s.data.settings.isDebugMode)
  28. const isOpen = useSelector((s) => s.data.settings.isDebugOpen)
  29. const rTextArea = useRef<HTMLTextAreaElement>(null)
  30. const local = useStateDesigner({
  31. initial: 'stopped',
  32. data: {
  33. log: '',
  34. },
  35. states: {
  36. stopped: {
  37. on: {
  38. CHANGED_LOG: 'setLog',
  39. COPIED_LOG: { if: 'hasLog', do: 'copyLog' },
  40. PLAYED_BACK_LOG: { if: 'hasLog', do: 'playbackLog' },
  41. STARTED_LOGGING: { do: 'startLogger', to: 'logging' },
  42. },
  43. },
  44. logging: {
  45. on: {
  46. STOPPED_LOGGING: { do: 'stopLogger', to: 'stopped' },
  47. },
  48. },
  49. },
  50. conditions: {
  51. hasLog(data) {
  52. return data.log !== ''
  53. },
  54. },
  55. actions: {
  56. setLog(data, payload: { value: string }) {
  57. data.log = payload.value
  58. },
  59. startLogger(data) {
  60. logger.start(state.data)
  61. data.log = ''
  62. },
  63. stopLogger(data) {
  64. logger.stop(state.data)
  65. data.log = logger.copyToJson()
  66. },
  67. playbackLog(data) {
  68. logger.playback(state.data, data.log)
  69. },
  70. copyLog() {
  71. logger.copyToJson()
  72. },
  73. },
  74. })
  75. if (!isDebugging) return null
  76. const handleLoggingStop = () => local.send('STOPPED_LOGGING')
  77. const handlePlayback = () =>
  78. local.send('PLAYED_BACK_LOG', { log: rTextArea.current?.value })
  79. const handleLoggingStart = () => local.send('STARTED_LOGGING')
  80. const handleLoggingCopy = () => local.send('COPIED_DEBUG_LOG')
  81. return (
  82. <StylePanelRoot
  83. dir="ltr"
  84. bp={breakpoints}
  85. data-bp-desktop
  86. ref={rContainer}
  87. variant="code"
  88. onWheel={(e) => e.stopPropagation()}
  89. >
  90. {isOpen ? (
  91. <Panel.Layout onKeyDown={stopPropagation}>
  92. <Panel.Header side="left">
  93. <IconButton
  94. bp={breakpoints}
  95. size="small"
  96. onClick={toggleDebugPanel}
  97. >
  98. <Cross2Icon />
  99. </IconButton>
  100. <span>Debugging</span>
  101. <div />
  102. </Panel.Header>
  103. <Panel.Content>
  104. <hr />
  105. <RowButton bp={breakpoints} onClick={handleStateCopy}>
  106. <span>Copy State</span>
  107. <IconWrapper size="small">
  108. <ClipboardCopyIcon />
  109. </IconWrapper>
  110. </RowButton>
  111. <RowButton bp={breakpoints} onClick={handleError}>
  112. <span>Create Error</span>
  113. <IconWrapper size="small">
  114. <TrashIcon />
  115. </IconWrapper>
  116. </RowButton>
  117. <hr />
  118. {local.isIn('stopped') ? (
  119. <RowButton bp={breakpoints} onClick={handleLoggingStart}>
  120. <span>Start Logger</span>
  121. <IconWrapper size="small">
  122. <DotIcon />
  123. </IconWrapper>
  124. </RowButton>
  125. ) : (
  126. <RowButton bp={breakpoints} onClick={handleLoggingStop}>
  127. <span>Stop Logger</span>
  128. <IconWrapper size="small">
  129. <StopIcon />
  130. </IconWrapper>
  131. </RowButton>
  132. )}
  133. <JSONTextAreaWrapper>
  134. <IconButton
  135. bp={breakpoints}
  136. onClick={handleLoggingCopy}
  137. disabled={!local.can('COPIED_LOG')}
  138. style={{ position: 'absolute', top: 2, right: 2 }}
  139. >
  140. <ClipboardIcon />
  141. </IconButton>
  142. <JSONTextArea
  143. ref={rTextArea}
  144. value={local.data.log}
  145. onChange={(e) =>
  146. local.send('CHANGED_LOG', { value: e.currentTarget.value })
  147. }
  148. />
  149. </JSONTextAreaWrapper>
  150. <RowButton
  151. bp={breakpoints}
  152. onClick={handlePlayback}
  153. disabled={!local.can('PLAYED_BACK_LOG')}
  154. >
  155. <span>Play Back Log</span>
  156. <IconWrapper size="small">
  157. <PlayIcon />
  158. </IconWrapper>
  159. </RowButton>
  160. </Panel.Content>
  161. </Panel.Layout>
  162. ) : (
  163. <IconButton bp={breakpoints} size="small" onClick={toggleDebugPanel}>
  164. <CrumpledPaperIcon />
  165. </IconButton>
  166. )}
  167. </StylePanelRoot>
  168. )
  169. }
  170. const StylePanelRoot = styled(Panel.Root, {
  171. marginRight: '8px',
  172. width: 'fit-content',
  173. maxWidth: 'fit-content',
  174. overflow: 'hidden',
  175. position: 'relative',
  176. border: '1px solid $panel',
  177. boxShadow: '$4',
  178. display: 'flex',
  179. flexDirection: 'column',
  180. alignItems: 'center',
  181. pointerEvents: 'all',
  182. padding: '$0',
  183. '& hr': {
  184. marginTop: 2,
  185. marginBottom: 2,
  186. marginLeft: '-$0',
  187. border: 'none',
  188. height: 1,
  189. backgroundColor: '$brushFill',
  190. width: 'calc(100% + 4px)',
  191. },
  192. })
  193. const JSONTextAreaWrapper = styled('div', {
  194. position: 'relative',
  195. margin: '4px 0',
  196. })
  197. const JSONTextArea = styled('textarea', {
  198. minHeight: '100px',
  199. width: '100%',
  200. font: '$mono',
  201. backgroundColor: '$panel',
  202. border: '1px solid $border',
  203. borderRadius: '4px',
  204. padding: '4px',
  205. outline: 'none',
  206. })