index.tsx 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. import React, { useState, useLayoutEffect } from "react";
  2. import ReactDOM from "react-dom";
  3. import * as Sentry from "@sentry/browser";
  4. import * as SentryIntegrations from "@sentry/integrations";
  5. import { EVENT } from "./constants";
  6. import { TopErrorBoundary } from "./components/TopErrorBoundary";
  7. import { InitializeApp } from "./components/InitializeApp";
  8. import { IsMobileProvider } from "./is-mobile";
  9. import App from "./components/App";
  10. import { register as registerServiceWorker } from "./serviceWorker";
  11. import "./css/styles.scss";
  12. import { loadFromBlob } from "./data";
  13. // On Apple mobile devices add the proprietary app icon and splashscreen markup.
  14. // No one should have to do this manually, and eventually this annoyance will
  15. // go away once https://bugs.webkit.org/show_bug.cgi?id=183937 is fixed.
  16. if (
  17. /\b(iPad|iPhone|iPod)\b/.test(navigator.userAgent) &&
  18. !matchMedia("(display-mode: standalone)").matches
  19. ) {
  20. import(/* webpackChunkName: "pwacompat" */ "pwacompat");
  21. }
  22. const SentryEnvHostnameMap: { [key: string]: string } = {
  23. "excalidraw.com": "production",
  24. "vercel.app": "staging",
  25. };
  26. const REACT_APP_DISABLE_SENTRY =
  27. process.env.REACT_APP_DISABLE_SENTRY === "true";
  28. const REACT_APP_GIT_SHA = process.env.REACT_APP_GIT_SHA as string;
  29. // Disable Sentry locally or inside the Docker to avoid noise/respect privacy
  30. const onlineEnv =
  31. !REACT_APP_DISABLE_SENTRY &&
  32. Object.keys(SentryEnvHostnameMap).find(
  33. (item) => window.location.hostname.indexOf(item) >= 0,
  34. );
  35. Sentry.init({
  36. dsn: onlineEnv
  37. ? "https://7bfc596a5bf945eda6b660d3015a5460@sentry.io/5179260"
  38. : undefined,
  39. environment: onlineEnv ? SentryEnvHostnameMap[onlineEnv] : undefined,
  40. release: REACT_APP_GIT_SHA,
  41. ignoreErrors: [
  42. "undefined is not an object (evaluating 'window.__pad.performLoop')", // Only happens on Safari, but spams our servers. Doesn't break anything
  43. ],
  44. integrations: [
  45. new SentryIntegrations.CaptureConsole({
  46. levels: ["error"],
  47. }),
  48. ],
  49. beforeSend(event) {
  50. if (event.request?.url) {
  51. event.request.url = event.request.url.replace(/#.*$/, "");
  52. }
  53. return event;
  54. },
  55. });
  56. window.__EXCALIDRAW_SHA__ = REACT_APP_GIT_SHA;
  57. // Block pinch-zooming on iOS outside of the content area
  58. document.addEventListener(
  59. "touchmove",
  60. (event) => {
  61. // @ts-ignore
  62. if (event.scale !== 1) {
  63. event.preventDefault();
  64. }
  65. },
  66. { passive: false },
  67. );
  68. function ExcalidrawApp() {
  69. const [dimensions, setDimensions] = useState({
  70. width: window.innerWidth,
  71. height: window.innerHeight,
  72. });
  73. const onResize = () => {
  74. setDimensions({
  75. width: window.innerWidth,
  76. height: window.innerHeight,
  77. });
  78. };
  79. useLayoutEffect(() => {
  80. window.addEventListener("resize", onResize);
  81. return () => window.removeEventListener("resize", onResize);
  82. }, []);
  83. const { width, height } = dimensions;
  84. return (
  85. <TopErrorBoundary>
  86. <IsMobileProvider>
  87. <InitializeApp>
  88. <App width={width} height={height} />
  89. </InitializeApp>
  90. </IsMobileProvider>
  91. </TopErrorBoundary>
  92. );
  93. }
  94. const rootElement = document.getElementById("root");
  95. ReactDOM.render(<ExcalidrawApp />, rootElement);
  96. registerServiceWorker({
  97. onUpdate: (registration) => {
  98. const waitingServiceWorker = registration.waiting;
  99. if (waitingServiceWorker) {
  100. waitingServiceWorker.addEventListener(
  101. EVENT.STATE_CHANGE,
  102. (event: Event) => {
  103. const target = event.target as ServiceWorker;
  104. const state = target.state as ServiceWorkerState;
  105. if (state === "activated") {
  106. window.location.reload();
  107. }
  108. },
  109. );
  110. waitingServiceWorker.postMessage({ type: "SKIP_WAITING" });
  111. }
  112. },
  113. });
  114. if ("launchQueue" in window && "LaunchParams" in window) {
  115. (window as any).launchQueue.setConsumer(
  116. async (launchParams: { files: any[] }) => {
  117. if (!launchParams.files.length) {
  118. return;
  119. }
  120. const fileHandle = launchParams.files[0];
  121. const blob = await fileHandle.getFile();
  122. loadFromBlob(blob);
  123. },
  124. );
  125. }