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.

logger.ts 5.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. import { Data } from 'types'
  2. import clipboard from './clipboard'
  3. import state from './state'
  4. import { isDraft, current } from 'immer'
  5. import tld from 'utils/tld'
  6. import inputs from './inputs'
  7. interface LogEntry {
  8. eventName: string
  9. payload: any
  10. time: number
  11. didCauseUpdate: boolean
  12. }
  13. class Logger {
  14. filters = new Set([
  15. // 'MOVED_OVER_SHAPE',
  16. // 'RESIZED_WINDOW',
  17. // 'HOVERED_SHAPE',
  18. // 'UNHOVERED_SHAPE',
  19. // 'PANNED_CAMERA',
  20. 'STARTED_LOGGING',
  21. 'STOPPED_LOGGING',
  22. ])
  23. snapshotStart: Data
  24. snapshotEnd: Data
  25. log: LogEntry[] = []
  26. isSimulating = false
  27. isRunning = false
  28. speed = 0
  29. startTime = 0
  30. /**
  31. * Start the logger.
  32. *
  33. * ### Example
  34. *
  35. *```ts
  36. * logger.start()
  37. *```
  38. */
  39. start = (data: Data): Logger => {
  40. if (this.isRunning) return
  41. this.isRunning = true
  42. this.snapshotStart = isDraft(data) ? current(data) : data
  43. this.log = []
  44. this.startTime = Date.now()
  45. // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  46. // @ts-ignore
  47. this.snapshotStart.pageStates[data.currentPageId].selectedIds = [
  48. ...tld.getSelectedIds(data),
  49. ]
  50. return this
  51. }
  52. /**
  53. * Stop the logger.
  54. *
  55. * ### Example
  56. *
  57. *```ts
  58. * logger.stop()
  59. *```
  60. */
  61. stop = (data: Data): Logger => {
  62. if (!this.isRunning) return
  63. this.isRunning = false
  64. this.snapshotEnd = isDraft(data) ? current(data) : data
  65. // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  66. // @ts-ignore
  67. this.snapshotEnd.pageStates[data.currentPageId].selectedIds = [
  68. ...tld.getSelectedIds(data),
  69. ]
  70. // if (window.confirm('Stopped logging. Copy to clipboard?')) {
  71. // this.copyToJson()
  72. // }
  73. return this
  74. }
  75. /**
  76. * Add an event and payload to the log.
  77. *
  78. * ### Example
  79. *
  80. *```ts
  81. * logger.addToLog(eventName, payload)
  82. *```
  83. */
  84. addToLog(event: string, payload: any, didCauseUpdate = false) {
  85. if (!this.isRunning) return
  86. if (this.filters.has(event)) return
  87. didCauseUpdate
  88. // if (!didCauseUpdate) return
  89. this.log.push({
  90. eventName: event,
  91. payload: payload,
  92. time: Date.now() - this.startTime,
  93. didCauseUpdate: true,
  94. })
  95. }
  96. /**
  97. * Play back a log entry.
  98. *
  99. * ### Example
  100. *
  101. *```ts
  102. * logger.playback(log)
  103. *```
  104. */
  105. playback = (data: Data, log: string): Logger => {
  106. const parsed: { start: Data; end: Data; events: LogEntry[] } =
  107. JSON.parse(log)
  108. const { start, events } = parsed
  109. this.isSimulating = true
  110. try {
  111. data.pageStates[data.currentPageId].selectedIds = [
  112. ...start.pageStates[start.currentPageId].selectedIds,
  113. ]
  114. state.send('RESET_DOCUMENT_STATE').forceData(start)
  115. const pointerDowns = [
  116. 'POINTED_CANVAS',
  117. 'POINTED_SHAPE',
  118. 'POINTED_BOUNDS',
  119. 'DOUBLE_POINTED_SHAPE',
  120. 'POINTED_HANDLE',
  121. 'RIGHT_POINTED',
  122. ]
  123. const pointerChanges = [
  124. ...pointerDowns,
  125. 'MOVED_POINTER',
  126. 'MOVED_OVER_SHAPE',
  127. 'STOPPED_POINTING',
  128. 'DOUBLE_POINTED_CANVAS',
  129. ]
  130. const pointerUps = ['STOPPED_POINTING']
  131. for (const event of events) {
  132. if (pointerDowns.includes(event.eventName)) {
  133. inputs.pointer.origin = event.payload.point
  134. }
  135. if (pointerChanges.includes(event.eventName)) {
  136. inputs.activePointerId = event.payload.pointerId
  137. inputs.pointer = { ...inputs.pointer, ...event.payload }
  138. inputs.points[event.payload.pointerId] = inputs.pointer
  139. }
  140. if (pointerUps.includes(event.eventName)) {
  141. delete inputs.points[event.payload.pointerId]
  142. delete inputs.activePointerId
  143. // TODO: Double pointing
  144. }
  145. state.send(event.eventName, event.payload)
  146. }
  147. setTimeout(
  148. () => (this.isSimulating = false),
  149. events[events.length - 1].time + 1
  150. )
  151. } catch (e) {
  152. console.warn('Could not play back that state.')
  153. }
  154. return this
  155. }
  156. /**
  157. * Export the log as JSON.
  158. *
  159. * ### Example
  160. *
  161. *```ts
  162. * logger.toJson()
  163. *```
  164. */
  165. copyToJson = () => {
  166. const logAsString = JSON.stringify({
  167. start: this.snapshotStart,
  168. end: this.snapshotEnd,
  169. events: this.log,
  170. })
  171. clipboard.copyStringToClipboard(logAsString)
  172. return logAsString
  173. }
  174. /**
  175. * Add a new event filter. Filtered events will not be logged.
  176. *
  177. * ### Example
  178. *
  179. *```ts
  180. * logger.addFilter("SOME_EVENT")
  181. *```
  182. */
  183. addFilter(eventName: string) {
  184. this.filters.add(eventName)
  185. }
  186. /**
  187. * Remove an event from the filters. Filtered events will not be logged.
  188. *
  189. * ### Example
  190. *
  191. *```ts
  192. * logger.removeFilter("SOME_EVENT")
  193. *```
  194. */
  195. removeFilter(eventName: string) {
  196. this.filters.delete(eventName)
  197. }
  198. /**
  199. * Replace all of the filtered events.
  200. *
  201. * ### Example
  202. *
  203. *```ts
  204. * logger.setFilters(["SOME_EVENT", "SOME_OTHER_EVENT"])
  205. *```
  206. */
  207. setFilters(eventNames: string[]) {
  208. this.filters = new Set(eventNames)
  209. }
  210. /**
  211. * Clear the current set of event filters.
  212. *
  213. * ### Example
  214. *
  215. *```ts
  216. * logger.clear()
  217. *```
  218. */
  219. clearFilters() {
  220. this.filters.clear()
  221. }
  222. }
  223. export default new Logger()