6 changed files with 191 additions and 182 deletions
@ -0,0 +1,108 @@
@@ -0,0 +1,108 @@
|
||||
import React, { useState, useLayoutEffect, useEffect } from "react"; |
||||
|
||||
import { LoadingMessage } from "../components/LoadingMessage"; |
||||
import { TopErrorBoundary } from "../components/TopErrorBoundary"; |
||||
import Excalidraw from "../excalidraw-embed/index"; |
||||
|
||||
import { |
||||
importFromLocalStorage, |
||||
importUsernameFromLocalStorage, |
||||
saveToLocalStorage, |
||||
saveUsernameToLocalStorage, |
||||
} from "../data/localStorage"; |
||||
|
||||
import { debounce } from "../utils"; |
||||
|
||||
import { SAVE_TO_LOCAL_STORAGE_TIMEOUT } from "../time_constants"; |
||||
import { EVENT } from "../constants"; |
||||
|
||||
import { ImportedDataState } from "../data/types"; |
||||
import { ExcalidrawElement } from "../element/types"; |
||||
import { AppState } from "../types"; |
||||
|
||||
const saveDebounced = debounce( |
||||
(elements: readonly ExcalidrawElement[], state: AppState) => { |
||||
saveToLocalStorage(elements, state); |
||||
}, |
||||
SAVE_TO_LOCAL_STORAGE_TIMEOUT, |
||||
); |
||||
|
||||
const onUsernameChange = (username: string) => { |
||||
saveUsernameToLocalStorage(username); |
||||
}; |
||||
|
||||
const onBlur = () => { |
||||
saveDebounced.flush(); |
||||
}; |
||||
|
||||
export default function ExcalidrawApp() { |
||||
// dimensions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const [dimensions, setDimensions] = useState({ |
||||
width: window.innerWidth, |
||||
height: window.innerHeight, |
||||
}); |
||||
useLayoutEffect(() => { |
||||
const onResize = () => { |
||||
setDimensions({ |
||||
width: window.innerWidth, |
||||
height: window.innerHeight, |
||||
}); |
||||
}; |
||||
|
||||
window.addEventListener("resize", onResize); |
||||
|
||||
return () => window.removeEventListener("resize", onResize); |
||||
}, []); |
||||
|
||||
// initial state
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const [initialState, setInitialState] = useState<{ |
||||
data: ImportedDataState; |
||||
user: { |
||||
name: string | null; |
||||
}; |
||||
} | null>(null); |
||||
|
||||
useEffect(() => { |
||||
setInitialState({ |
||||
data: importFromLocalStorage(), |
||||
user: { |
||||
name: importUsernameFromLocalStorage(), |
||||
}, |
||||
}); |
||||
}, []); |
||||
|
||||
// blur/unload
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
useEffect(() => { |
||||
window.addEventListener(EVENT.UNLOAD, onBlur, false); |
||||
window.addEventListener(EVENT.BLUR, onBlur, false); |
||||
return () => { |
||||
window.removeEventListener(EVENT.UNLOAD, onBlur, false); |
||||
window.removeEventListener(EVENT.BLUR, onBlur, false); |
||||
}; |
||||
}, []); |
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
if (!initialState) { |
||||
return <LoadingMessage />; |
||||
} |
||||
|
||||
return ( |
||||
<TopErrorBoundary> |
||||
<Excalidraw |
||||
width={dimensions.width} |
||||
height={dimensions.height} |
||||
onChange={saveDebounced} |
||||
initialData={initialState.data} |
||||
user={initialState.user} |
||||
onUsernameChange={onUsernameChange} |
||||
/> |
||||
</TopErrorBoundary> |
||||
); |
||||
} |
||||
@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
|
||||
import { register as registerServiceWorker } from "../serviceWorker"; |
||||
import { EVENT } from "../constants"; |
||||
|
||||
// On Apple mobile devices add the proprietary app icon and splashscreen markup.
|
||||
// No one should have to do this manually, and eventually this annoyance will
|
||||
// go away once https://bugs.webkit.org/show_bug.cgi?id=183937 is fixed.
|
||||
if ( |
||||
/\b(iPad|iPhone|iPod)\b/.test(navigator.userAgent) && |
||||
!matchMedia("(display-mode: standalone)").matches |
||||
) { |
||||
import(/* webpackChunkName: "pwacompat" */ "pwacompat"); |
||||
} |
||||
|
||||
registerServiceWorker({ |
||||
onUpdate: (registration) => { |
||||
const waitingServiceWorker = registration.waiting; |
||||
if (waitingServiceWorker) { |
||||
waitingServiceWorker.addEventListener( |
||||
EVENT.STATE_CHANGE, |
||||
(event: Event) => { |
||||
const target = event.target as ServiceWorker; |
||||
const state = target.state as ServiceWorkerState; |
||||
if (state === "activated") { |
||||
window.location.reload(); |
||||
} |
||||
}, |
||||
); |
||||
waitingServiceWorker.postMessage({ type: "SKIP_WAITING" }); |
||||
} |
||||
}, |
||||
}); |
||||
@ -0,0 +1,39 @@
@@ -0,0 +1,39 @@
|
||||
import * as Sentry from "@sentry/browser"; |
||||
import * as SentryIntegrations from "@sentry/integrations"; |
||||
|
||||
const SentryEnvHostnameMap: { [key: string]: string } = { |
||||
"excalidraw.com": "production", |
||||
"vercel.app": "staging", |
||||
}; |
||||
|
||||
const REACT_APP_DISABLE_SENTRY = |
||||
process.env.REACT_APP_DISABLE_SENTRY === "true"; |
||||
|
||||
// Disable Sentry locally or inside the Docker to avoid noise/respect privacy
|
||||
const onlineEnv = |
||||
!REACT_APP_DISABLE_SENTRY && |
||||
Object.keys(SentryEnvHostnameMap).find( |
||||
(item) => window.location.hostname.indexOf(item) >= 0, |
||||
); |
||||
|
||||
Sentry.init({ |
||||
dsn: onlineEnv |
||||
? "https://7bfc596a5bf945eda6b660d3015a5460@sentry.io/5179260" |
||||
: undefined, |
||||
environment: onlineEnv ? SentryEnvHostnameMap[onlineEnv] : undefined, |
||||
release: process.env.REACT_APP_GIT_SHA, |
||||
ignoreErrors: [ |
||||
"undefined is not an object (evaluating 'window.__pad.performLoop')", // Only happens on Safari, but spams our servers. Doesn't break anything
|
||||
], |
||||
integrations: [ |
||||
new SentryIntegrations.CaptureConsole({ |
||||
levels: ["error"], |
||||
}), |
||||
], |
||||
beforeSend(event) { |
||||
if (event.request?.url) { |
||||
event.request.url = event.request.url.replace(/#.*$/, ""); |
||||
} |
||||
return event; |
||||
}, |
||||
}); |
||||
@ -1,185 +1,10 @@
@@ -1,185 +1,10 @@
|
||||
import React, { useState, useLayoutEffect, useEffect } from "react"; |
||||
import React from "react"; |
||||
import ReactDOM from "react-dom"; |
||||
import * as Sentry from "@sentry/browser"; |
||||
import * as SentryIntegrations from "@sentry/integrations"; |
||||
import ExcalidrawApp from "./excalidraw-app"; |
||||
|
||||
import { EVENT } from "./constants"; |
||||
import { TopErrorBoundary } from "./components/TopErrorBoundary"; |
||||
import Excalidraw from "./excalidraw-embed/index"; |
||||
import { register as registerServiceWorker } from "./serviceWorker"; |
||||
import "./excalidraw-app/pwa"; |
||||
import "./excalidraw-app/sentry"; |
||||
|
||||
import { debounce } from "./utils"; |
||||
import { |
||||
importFromLocalStorage, |
||||
importUsernameFromLocalStorage, |
||||
saveUsernameToLocalStorage, |
||||
saveToLocalStorage, |
||||
} from "./data/localStorage"; |
||||
window.__EXCALIDRAW_SHA__ = process.env.REACT_APP_GIT_SHA; |
||||
|
||||
import { SAVE_TO_LOCAL_STORAGE_TIMEOUT } from "./time_constants"; |
||||
import { ImportedDataState } from "./data/types"; |
||||
import { LoadingMessage } from "./components/LoadingMessage"; |
||||
import { ExcalidrawElement } from "./element/types"; |
||||
import { AppState } from "./types"; |
||||
|
||||
// On Apple mobile devices add the proprietary app icon and splashscreen markup.
|
||||
// No one should have to do this manually, and eventually this annoyance will
|
||||
// go away once https://bugs.webkit.org/show_bug.cgi?id=183937 is fixed.
|
||||
if ( |
||||
/\b(iPad|iPhone|iPod)\b/.test(navigator.userAgent) && |
||||
!matchMedia("(display-mode: standalone)").matches |
||||
) { |
||||
import(/* webpackChunkName: "pwacompat" */ "pwacompat"); |
||||
} |
||||
|
||||
const SentryEnvHostnameMap: { [key: string]: string } = { |
||||
"excalidraw.com": "production", |
||||
"vercel.app": "staging", |
||||
}; |
||||
|
||||
const REACT_APP_DISABLE_SENTRY = |
||||
process.env.REACT_APP_DISABLE_SENTRY === "true"; |
||||
const REACT_APP_GIT_SHA = process.env.REACT_APP_GIT_SHA as string; |
||||
|
||||
// Disable Sentry locally or inside the Docker to avoid noise/respect privacy
|
||||
const onlineEnv = |
||||
!REACT_APP_DISABLE_SENTRY && |
||||
Object.keys(SentryEnvHostnameMap).find( |
||||
(item) => window.location.hostname.indexOf(item) >= 0, |
||||
); |
||||
|
||||
Sentry.init({ |
||||
dsn: onlineEnv |
||||
? "https://7bfc596a5bf945eda6b660d3015a5460@sentry.io/5179260" |
||||
: undefined, |
||||
environment: onlineEnv ? SentryEnvHostnameMap[onlineEnv] : undefined, |
||||
release: REACT_APP_GIT_SHA, |
||||
ignoreErrors: [ |
||||
"undefined is not an object (evaluating 'window.__pad.performLoop')", // Only happens on Safari, but spams our servers. Doesn't break anything
|
||||
], |
||||
integrations: [ |
||||
new SentryIntegrations.CaptureConsole({ |
||||
levels: ["error"], |
||||
}), |
||||
], |
||||
beforeSend(event) { |
||||
if (event.request?.url) { |
||||
event.request.url = event.request.url.replace(/#.*$/, ""); |
||||
} |
||||
return event; |
||||
}, |
||||
}); |
||||
|
||||
window.__EXCALIDRAW_SHA__ = REACT_APP_GIT_SHA; |
||||
|
||||
const saveDebounced = debounce( |
||||
(elements: readonly ExcalidrawElement[], state: AppState) => { |
||||
saveToLocalStorage(elements, state); |
||||
}, |
||||
SAVE_TO_LOCAL_STORAGE_TIMEOUT, |
||||
); |
||||
|
||||
const onUsernameChange = (username: string) => { |
||||
saveUsernameToLocalStorage(username); |
||||
}; |
||||
|
||||
const onBlur = () => { |
||||
saveDebounced.flush(); |
||||
}; |
||||
|
||||
function ExcalidrawApp() { |
||||
// dimensions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const [dimensions, setDimensions] = useState({ |
||||
width: window.innerWidth, |
||||
height: window.innerHeight, |
||||
}); |
||||
useLayoutEffect(() => { |
||||
const onResize = () => { |
||||
setDimensions({ |
||||
width: window.innerWidth, |
||||
height: window.innerHeight, |
||||
}); |
||||
}; |
||||
|
||||
window.addEventListener("resize", onResize); |
||||
|
||||
return () => window.removeEventListener("resize", onResize); |
||||
}, []); |
||||
|
||||
// initial state
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const [initialState, setInitialState] = useState<{ |
||||
data: ImportedDataState; |
||||
user: { |
||||
name: string | null; |
||||
}; |
||||
} | null>(null); |
||||
|
||||
useEffect(() => { |
||||
setInitialState({ |
||||
data: importFromLocalStorage(), |
||||
user: { |
||||
name: importUsernameFromLocalStorage(), |
||||
}, |
||||
}); |
||||
}, []); |
||||
|
||||
// blur/unload
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
useEffect(() => { |
||||
window.addEventListener(EVENT.UNLOAD, onBlur, false); |
||||
window.addEventListener(EVENT.BLUR, onBlur, false); |
||||
return () => { |
||||
window.removeEventListener(EVENT.UNLOAD, onBlur, false); |
||||
window.removeEventListener(EVENT.BLUR, onBlur, false); |
||||
}; |
||||
}, []); |
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
if (!initialState) { |
||||
return <LoadingMessage />; |
||||
} |
||||
|
||||
return ( |
||||
<TopErrorBoundary> |
||||
<Excalidraw |
||||
width={dimensions.width} |
||||
height={dimensions.height} |
||||
offsetLeft={0} |
||||
offsetTop={0} |
||||
onChange={saveDebounced} |
||||
initialData={initialState.data} |
||||
user={initialState.user} |
||||
onUsernameChange={onUsernameChange} |
||||
/> |
||||
</TopErrorBoundary> |
||||
); |
||||
} |
||||
|
||||
const rootElement = document.getElementById("root"); |
||||
|
||||
ReactDOM.render(<ExcalidrawApp />, rootElement); |
||||
|
||||
registerServiceWorker({ |
||||
onUpdate: (registration) => { |
||||
const waitingServiceWorker = registration.waiting; |
||||
if (waitingServiceWorker) { |
||||
waitingServiceWorker.addEventListener( |
||||
EVENT.STATE_CHANGE, |
||||
(event: Event) => { |
||||
const target = event.target as ServiceWorker; |
||||
const state = target.state as ServiceWorkerState; |
||||
if (state === "activated") { |
||||
window.location.reload(); |
||||
} |
||||
}, |
||||
); |
||||
waitingServiceWorker.postMessage({ type: "SKIP_WAITING" }); |
||||
} |
||||
}, |
||||
}); |
||||
ReactDOM.render(<ExcalidrawApp />, document.getElementById("root")); |
||||
|
||||
Loading…
Reference in new issue