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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. import * as Sentry from '@sentry/node'
  2. import React, { useEffect, useRef } from 'react'
  3. import { ErrorBoundary } from 'react-error-boundary'
  4. import state, { useSelector } from 'state'
  5. import styled from 'styles'
  6. import useCamera from 'hooks/useCamera'
  7. import useCanvasEvents from 'hooks/useCanvasEvents'
  8. import useZoomEvents from 'hooks/useZoomEvents'
  9. import Bounds from './bounds/bounding-box'
  10. import BoundsBg from './bounds/bounds-bg'
  11. import Handles from './bounds/handles'
  12. import ContextMenu from './context-menu/context-menu'
  13. import Coop from './coop/coop'
  14. import Brush from './brush'
  15. import Defs from './defs'
  16. import Page from './page'
  17. import useSafariFocusOutFix from 'hooks/useSafariFocusOutFix'
  18. function resetError() {
  19. null
  20. }
  21. export default function Canvas(): JSX.Element {
  22. const rCanvas = useRef<SVGSVGElement>(null)
  23. const rGroup = useRef<SVGGElement>(null)
  24. useCamera(rGroup)
  25. useZoomEvents()
  26. useSafariFocusOutFix()
  27. const events = useCanvasEvents(rCanvas)
  28. const isSettingCamera = useSelector((s) => s.isIn('settingCamera'))
  29. const isReady = useSelector((s) => s.isIn('ready'))
  30. useEffect(() => {
  31. if (isSettingCamera) {
  32. state.send('MOUNTED_SHAPES')
  33. }
  34. }, [isSettingCamera])
  35. return (
  36. <ContextMenu>
  37. <MainSVG ref={rCanvas} {...events}>
  38. <ErrorBoundary FallbackComponent={ErrorFallback} onReset={resetError}>
  39. <Defs />
  40. <g ref={rGroup} id="shapes" opacity={isReady ? 1 : 0}>
  41. <BoundsBg />
  42. <Page />
  43. <Coop />
  44. <Bounds />
  45. <Handles />
  46. <Brush />
  47. </g>
  48. </ErrorBoundary>
  49. </MainSVG>
  50. </ContextMenu>
  51. )
  52. }
  53. const MainSVG = styled('svg', {
  54. position: 'fixed',
  55. overflow: 'hidden',
  56. top: 0,
  57. left: 0,
  58. width: '100%',
  59. height: '100%',
  60. touchAction: 'none',
  61. zIndex: 100,
  62. pointerEvents: 'all',
  63. backgroundColor: '$canvas',
  64. borderTop: '1px solid $border',
  65. borderBottom: '1px solid $border',
  66. '& *': {
  67. userSelect: 'none',
  68. },
  69. })
  70. function ErrorFallback({ error, resetErrorBoundary }) {
  71. React.useEffect(() => {
  72. const copy =
  73. 'Sorry, something went wrong. Press Ok to reset the document, or press cancel to continue and see if it resolves itself.'
  74. console.error(error)
  75. Sentry.captureException(error)
  76. if (window.confirm(copy)) {
  77. state.send('RESET_DOCUMENT_STATE')
  78. resetErrorBoundary()
  79. }
  80. }, [])
  81. return <g />
  82. }