瀏覽代碼

add excalidraw_embed into base repo (#2040)

Co-authored-by: Lipis <lipiridis@gmail.com>
vanilla_orig
David Luzar 5 年之前
父節點
當前提交
ab7073abdb
No account linked to committer's email address

+ 1
- 0
.eslintignore 查看文件

@@ -3,3 +3,4 @@ build/
3 3
 package-lock.json
4 4
 .vscode/
5 5
 firebase/
6
+dist/

+ 1
- 0
.gitignore 查看文件

@@ -13,3 +13,4 @@ yarn-debug.log*
13 13
 yarn-error.log*
14 14
 yarn.lock
15 15
 .idea
16
+dist/

+ 0
- 13
public/fonts.css 查看文件

@@ -1,13 +0,0 @@
1
-/* http://www.eaglefonts.com/fg-virgil-ttf-131249.htm */
2
-@font-face {
3
-  font-family: "Virgil";
4
-  src: url("FG_Virgil.woff2");
5
-  font-display: swap;
6
-}
7
-
8
-/* https://github.com/microsoft/cascadia-code */
9
-@font-face {
10
-  font-family: "Cascadia";
11
-  src: url("Cascadia.woff2");
12
-  font-display: swap;
13
-}

+ 0
- 2
public/index.html 查看文件

@@ -60,8 +60,6 @@
60 60
     <!-- OG tags require absolute url for images -->
61 61
     <meta name="twitter:image" content="https://excalidraw.com/og-image.png" />
62 62
     <link rel="shortcut icon" href="favicon.ico" type="image/x-icon" />
63
-    <link rel="stylesheet" href="fonts.css" type="text/css" />
64
-    <link rel="stylesheet" href="app.css" type="text/css" />
65 63
 
66 64
     <link
67 65
       rel="preload"

+ 12
- 4
src/components/App.tsx 查看文件

@@ -278,12 +278,13 @@ class App extends React.Component<ExcalidrawProps, AppState> {
278 278
     super(props);
279 279
     const defaultAppState = getDefaultAppState();
280 280
 
281
-    const { width, height } = props;
281
+    const { width, height, user } = props;
282 282
     this.state = {
283 283
       ...defaultAppState,
284 284
       isLoading: true,
285 285
       width,
286 286
       height,
287
+      username: user?.name || "",
287 288
       ...this.getCanvasOffsets(),
288 289
     };
289 290
 
@@ -334,6 +335,9 @@ class App extends React.Component<ExcalidrawProps, AppState> {
334 335
           onRoomCreate={this.openPortal}
335 336
           onRoomDestroy={this.closePortal}
336 337
           onUsernameChange={(username) => {
338
+            if (this.props.onUsernameChange) {
339
+              this.props.onUsernameChange(username);
340
+            }
337 341
             saveUsernameToLocalStorage(username);
338 342
             this.setState({
339 343
               username,
@@ -501,12 +505,12 @@ class App extends React.Component<ExcalidrawProps, AppState> {
501 505
       this.setState({ isLoading: true });
502 506
     }
503 507
 
504
-    let scene = await loadScene(null);
508
+    let scene = await loadScene(null, null, this.props.initialData);
505 509
 
506 510
     let isCollaborationScene = !!getCollaborationLinkData(window.location.href);
507 511
     const isExternalScene = !!(id || jsonMatch || isCollaborationScene);
508 512
 
509
-    if (isExternalScene) {
513
+    if (isExternalScene && !this.props.initialData) {
510 514
       if (
511 515
         this.shouldForceLoadScene(scene) ||
512 516
         window.confirm(t("alerts.loadSceneOverridePrompt"))
@@ -715,7 +719,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
715 719
 
716 720
   componentDidUpdate(prevProps: ExcalidrawProps, prevState: AppState) {
717 721
     const { width: prevWidth, height: prevHeight } = prevProps;
718
-    const { width: currentWidth, height: currentHeight } = this.props;
722
+    const { width: currentWidth, height: currentHeight, onChange } = this.props;
719 723
     if (prevWidth !== currentWidth || prevHeight !== currentHeight) {
720 724
       this.setState({
721 725
         width: currentWidth,
@@ -847,6 +851,10 @@ class App extends React.Component<ExcalidrawProps, AppState> {
847 851
     }
848 852
 
849 853
     history.record(this.state, this.scene.getElementsIncludingDeleted());
854
+
855
+    if (onChange) {
856
+      onChange(this.scene.getElementsIncludingDeleted(), this.state);
857
+    }
850 858
   }
851 859
 
852 860
   // Copy/paste

public/app.css → src/css/app.scss 查看文件

@@ -1,3 +1,17 @@
1
+/* http://www.eaglefonts.com/fg-virgil-ttf-131249.htm */
2
+@font-face {
3
+  font-family: "Virgil";
4
+  src: url("/FG_Virgil.woff2");
5
+  font-display: swap;
6
+}
7
+
8
+/* https://github.com/microsoft/cascadia-code */
9
+@font-face {
10
+  font-family: "Cascadia";
11
+  src: url("/Cascadia.woff2");
12
+  font-display: swap;
13
+}
14
+
1 15
 .visually-hidden {
2 16
   position: absolute !important;
3 17
   height: 1px;

+ 8
- 3
src/data/index.ts 查看文件

@@ -19,6 +19,7 @@ import { serializeAsJSON } from "./json";
19 19
 import { ExportType } from "../scene/types";
20 20
 import { restore } from "./restore";
21 21
 import { restoreFromLocalStorage } from "./localStorage";
22
+import { DataState } from "./types";
22 23
 
23 24
 export { loadFromBlob } from "./blob";
24 25
 export { saveAsJSON, loadFromJSON } from "./json";
@@ -234,7 +235,7 @@ export const exportToBackend = async (
234 235
 
235 236
 export const importFromBackend = async (
236 237
   id: string | null,
237
-  privateKey: string | undefined,
238
+  privateKey?: string | null,
238 239
 ) => {
239 240
   let elements: readonly ExcalidrawElement[] = [];
240 241
   let appState = getDefaultAppState();
@@ -364,14 +365,18 @@ export const exportCanvas = async (
364 365
   }
365 366
 };
366 367
 
367
-export const loadScene = async (id: string | null, privateKey?: string) => {
368
+export const loadScene = async (
369
+  id: string | null,
370
+  privateKey?: string | null,
371
+  initialData?: DataState,
372
+) => {
368 373
   let data;
369 374
   if (id != null) {
370 375
     // the private key is used to decrypt the content from the server, take
371 376
     // extra care not to leak it
372 377
     data = await importFromBackend(id, privateKey);
373 378
   } else {
374
-    data = restoreFromLocalStorage();
379
+    data = initialData || restoreFromLocalStorage();
375 380
   }
376 381
 
377 382
   return {

+ 2
- 0
src/excalidraw-embed/.gitignore 查看文件

@@ -0,0 +1,2 @@
1
+node_modules
2
+dist

+ 71
- 0
src/excalidraw-embed/index.tsx 查看文件

@@ -0,0 +1,71 @@
1
+import React, { useEffect } from "react";
2
+
3
+import { InitializeApp } from "../components/InitializeApp";
4
+import App from "../components/App";
5
+
6
+import "../css/app.scss";
7
+import "../css/styles.scss";
8
+
9
+import { ExcalidrawProps } from "../types";
10
+import { IsMobileProvider } from "../is-mobile";
11
+
12
+const Excalidraw = React.memo(
13
+  (props: ExcalidrawProps) => {
14
+    const {
15
+      width,
16
+      height,
17
+      onChange,
18
+      initialData,
19
+      user,
20
+      onUsernameChange,
21
+    } = props;
22
+
23
+    useEffect(() => {
24
+      // Block pinch-zooming on iOS outside of the content area
25
+      const handleTouchMove = (event: TouchEvent) => {
26
+        // @ts-ignore
27
+        if (typeof event.scale === "number" && event.scale !== 1) {
28
+          event.preventDefault();
29
+        }
30
+      };
31
+
32
+      document.addEventListener("touchmove", handleTouchMove, {
33
+        passive: false,
34
+      });
35
+
36
+      return () => {
37
+        document.removeEventListener("touchmove", handleTouchMove);
38
+      };
39
+    }, []);
40
+
41
+    return (
42
+      <InitializeApp>
43
+        <IsMobileProvider>
44
+          <App
45
+            width={width}
46
+            height={height}
47
+            onChange={onChange}
48
+            initialData={initialData}
49
+            user={user}
50
+            onUsernameChange={onUsernameChange}
51
+          />
52
+        </IsMobileProvider>
53
+      </InitializeApp>
54
+    );
55
+  },
56
+  (prevProps: ExcalidrawProps, nextProps: ExcalidrawProps) => {
57
+    const { initialData: prevInitialData, user: prevUser, ...prev } = prevProps;
58
+    const { initialData: nextInitialData, user: nextUser, ...next } = nextProps;
59
+
60
+    const prevKeys = Object.keys(prevProps) as (keyof typeof prev)[];
61
+    const nextKeys = Object.keys(nextProps) as (keyof typeof next)[];
62
+
63
+    return (
64
+      prevUser?.name === nextUser?.name &&
65
+      prevKeys.length === nextKeys.length &&
66
+      prevKeys.every((key) => prev[key] === next[key])
67
+    );
68
+  },
69
+);
70
+
71
+export default Excalidraw;

+ 6384
- 0
src/excalidraw-embed/package-lock.json
文件差異過大導致無法顯示
查看文件


+ 66
- 0
src/excalidraw-embed/package.json 查看文件

@@ -0,0 +1,66 @@
1
+{
2
+  "name": "@excalidraw/excalidraw",
3
+  "version": "0.7.0",
4
+  "main": "dist/excalidraw.min.js",
5
+  "files": [
6
+    "dist/*"
7
+  ],
8
+  "description": "Excalidraw as a React component",
9
+  "license": "MIT",
10
+  "keywords": [
11
+    "excalidraw",
12
+    "excalidraw-embed",
13
+    "react",
14
+    "npm",
15
+    "npm excalidraw"
16
+  ],
17
+  "browserslist": {
18
+    "production": [
19
+      ">0.2%",
20
+      "not dead",
21
+      "not ie <= 11",
22
+      "not op_mini all",
23
+      "not safari < 12",
24
+      "not kaios <= 2.5",
25
+      "not edge < 79",
26
+      "not chrome < 70",
27
+      "not and_uc < 13",
28
+      "not samsung < 10"
29
+    ],
30
+    "development": [
31
+      "last 1 chrome version",
32
+      "last 1 firefox version",
33
+      "last 1 safari version"
34
+    ]
35
+  },
36
+  "peerDependencies": {
37
+    "react": "16.13.1",
38
+    "react-dom": "16.13.1"
39
+  },
40
+  "devDependencies": {
41
+    "@babel/core": "7.9.0",
42
+    "@babel/plugin-transform-arrow-functions": "7.8.3",
43
+    "@babel/plugin-transform-async-to-generator": "7.8.3",
44
+    "@babel/plugin-transform-typescript": "7.9.4",
45
+    "@babel/preset-env": "7.9.5",
46
+    "@babel/preset-react": "7.9.4",
47
+    "@babel/preset-typescript": "7.9.0",
48
+    "babel-loader": "8.1.0",
49
+    "babel-plugin-transform-class-properties": "6.24.1",
50
+    "cross-env": "7.0.2",
51
+    "css-loader": "3.5.2",
52
+    "file-loader": "6.0.0",
53
+    "mini-css-extract-plugin": "0.8.0",
54
+    "sass-loader": "8.0.2",
55
+    "terser-webpack-plugin": "2.3.5",
56
+    "ts-loader": "7.0.0",
57
+    "webpack": "4.42.0",
58
+    "webpack-cli": "3.3.11"
59
+  },
60
+  "bugs": "https://github.com/excalidraw/excalidraw/issues",
61
+  "repository": "https://github.com/excalidraw/excalidraw",
62
+  "scripts": {
63
+    "build:umd": "cross-env NODE_ENV=production webpack --config webpack.prod.config.js",
64
+    "pack": "npm run build:umd && npm pack"
65
+  }
66
+}

+ 9
- 0
src/excalidraw-embed/tsconfig.prod.json 查看文件

@@ -0,0 +1,9 @@
1
+{
2
+  "compilerOptions": {
3
+    "target": "es5",
4
+    "module": "es2015",
5
+    "moduleResolution": "node",
6
+    "resolveJsonModule": true,
7
+    "jsx": "react"
8
+  }
9
+}

+ 99
- 0
src/excalidraw-embed/webpack.prod.config.js 查看文件

@@ -0,0 +1,99 @@
1
+const path = require("path");
2
+const webpack = require("webpack");
3
+const MiniCssExtractPlugin = require("mini-css-extract-plugin");
4
+const TerserPlugin = require("terser-webpack-plugin");
5
+
6
+module.exports = {
7
+  mode: "production",
8
+  entry: {
9
+    "excalidraw.min": "./index.tsx",
10
+  },
11
+  output: {
12
+    path: path.resolve(__dirname, "dist"),
13
+    library: "Excalidraw",
14
+    libraryTarget: "umd",
15
+    filename: "[name].js",
16
+  },
17
+  resolve: {
18
+    extensions: [".js", ".ts", ".tsx", ".css", ".scss"],
19
+  },
20
+  module: {
21
+    rules: [
22
+      {
23
+        test: /\.(sa|sc|c)ss$/,
24
+        exclude: /node_modules/,
25
+        use: [
26
+          MiniCssExtractPlugin.loader,
27
+          { loader: "css-loader" },
28
+          "sass-loader",
29
+        ],
30
+      },
31
+      {
32
+        test: /\.(ts|tsx|js|jsx|mjs)$/,
33
+        exclude: /node_modules\/(?!(roughjs|socket.io-client|browser-nativefs)\/).*/,
34
+        use: [
35
+          {
36
+            loader: "ts-loader",
37
+            options: {
38
+              transpileOnly: true,
39
+              configFile: path.resolve(__dirname, "tsconfig.prod.json"),
40
+            },
41
+          },
42
+          {
43
+            loader: "babel-loader",
44
+            options: {
45
+              presets: [
46
+                "@babel/preset-env",
47
+                "@babel/preset-react",
48
+                "@babel/preset-typescript",
49
+              ],
50
+              plugins: [
51
+                "@babel/plugin-proposal-object-rest-spread",
52
+                "@babel/plugin-transform-arrow-functions",
53
+                "transform-class-properties",
54
+                "@babel/plugin-transform-async-to-generator",
55
+              ],
56
+            },
57
+          },
58
+        ],
59
+      },
60
+      {
61
+        test: /\.(woff|woff2|eot|ttf|otf)$/,
62
+        use: [
63
+          {
64
+            loader: "file-loader",
65
+            options: {
66
+              name: "[name].[ext]",
67
+            },
68
+          },
69
+        ],
70
+      },
71
+    ],
72
+  },
73
+  optimization: {
74
+    minimize: true,
75
+    minimizer: [
76
+      new TerserPlugin({
77
+        test: /\.js($|\?)/i,
78
+      }),
79
+      new webpack.optimize.LimitChunkCountPlugin({
80
+        maxChunks: 1,
81
+      }),
82
+    ],
83
+  },
84
+  plugins: [new MiniCssExtractPlugin({ filename: "[name].css" })],
85
+  externals: {
86
+    react: {
87
+      root: "React",
88
+      commonjs2: "react",
89
+      commonjs: "react",
90
+      amd: "react",
91
+    },
92
+    "react-dom": {
93
+      root: "ReactDOM",
94
+      commonjs2: "react-dom",
95
+      commonjs: "react-dom",
96
+      amd: "react-dom",
97
+    },
98
+  },
99
+};

+ 31
- 34
src/i18n.ts 查看文件

@@ -8,44 +8,41 @@ const COMPLETION_THRESHOLD_TO_EXCEED = 85;
8 8
 interface Language {
9 9
   lng: string;
10 10
   label: string;
11
-  data: string;
12 11
   rtl?: boolean;
13 12
 }
14 13
 
15 14
 const allLanguages: Language[] = [
16
-  { lng: "bg-BG", label: "Български", data: "bg-BG.json" },
17
-  { lng: "de-DE", label: "Deutsch", data: "de-DE.json" },
18
-  { lng: "es-ES", label: "Español", data: "es-ES.json" },
19
-  { lng: "ca-ES", label: "Catalan", data: "ca-ES.json" },
20
-  { lng: "el-GR", label: "Ελληνικά", data: "el-GR.json" },
21
-  { lng: "fr-FR", label: "Français", data: "fr-FR.json" },
22
-  { lng: "id-ID", label: "Bahasa Indonesia", data: "id-ID.json" },
23
-  { lng: "it-IT", label: "Italiano", data: "it-IT.json" },
24
-  { lng: "hu-HU", label: "Magyar", data: "hu-HU.json" },
25
-  { lng: "nl-NL", label: "Nederlands", data: "nl-NL.json" },
26
-  { lng: "nb-NO", label: "Norsk bokmål", data: "nb-NO.json" },
27
-  { lng: "nn-NO", label: "Norsk nynorsk", data: "nn-NO.json" },
28
-  { lng: "pl-PL", label: "Polski", data: "pl-PL.json" },
29
-  { lng: "pt-PT", label: "Português", data: "pt-PT.json" },
30
-  { lng: "ru-RU", label: "Русский", data: "ru-RU.json" },
31
-  { lng: "uk-UA", label: "Українська", data: "uk-UA.json" },
32
-  { lng: "fi-FI", label: "Suomi", data: "fi-FI.json" },
33
-  { lng: "tr-TR", label: "Türkçe", data: "tr-TR.json" },
34
-  { lng: "ja-JP", label: "日本語", data: "ja-JP.json" },
35
-  { lng: "ko-KR", label: "한국어", data: "ko-KR.json" },
36
-  { lng: "zh-TW", label: "繁體中文", data: "zh-TW.json" },
37
-  { lng: "zh-CN", label: "简体中文", data: "zh-CN.json" },
38
-  { lng: "ar-SA", label: "العربية", data: "ar-SA.json", rtl: true },
39
-  { lng: "he-IL", label: "עברית", data: "he-IL.json", rtl: true },
40
-  { lng: "hi-IN", label: "हिन्दी", data: "hi-IN.json" },
41
-  { lng: "ta-IN", label: "தமிழ்", data: "ta-IN.json" },
42
-  { lng: "gl-ES", label: "Galego", data: "gl-ES.json" },
43
-  { lng: "vi-VN", label: "Tiếng Việt", data: "vi-VN.json" },
15
+  { lng: "bg-BG", label: "Български" },
16
+  { lng: "de-DE", label: "Deutsch" },
17
+  { lng: "es-ES", label: "Español" },
18
+  { lng: "ca-ES", label: "Catalan" },
19
+  { lng: "el-GR", label: "Ελληνικά" },
20
+  { lng: "fr-FR", label: "Français" },
21
+  { lng: "id-ID", label: "Bahasa Indonesia" },
22
+  { lng: "it-IT", label: "Italiano" },
23
+  { lng: "hu-HU", label: "Magyar" },
24
+  { lng: "nl-NL", label: "Nederlands" },
25
+  { lng: "nb-NO", label: "Norsk bokmål" },
26
+  { lng: "nn-NO", label: "Norsk nynorsk" },
27
+  { lng: "pl-PL", label: "Polski" },
28
+  { lng: "pt-PT", label: "Português" },
29
+  { lng: "ru-RU", label: "Русский" },
30
+  { lng: "uk-UA", label: "Українська" },
31
+  { lng: "fi-FI", label: "Suomi" },
32
+  { lng: "tr-TR", label: "Türkçe" },
33
+  { lng: "ja-JP", label: "日本語" },
34
+  { lng: "ko-KR", label: "한국어" },
35
+  { lng: "zh-TW", label: "繁體中文" },
36
+  { lng: "zh-CN", label: "简体中文" },
37
+  { lng: "ar-SA", label: "العربية", rtl: true },
38
+  { lng: "he-IL", label: "עברית", rtl: true },
39
+  { lng: "hi-IN", label: "हिन्दी" },
40
+  { lng: "ta-IN", label: "தமிழ்" },
41
+  { lng: "gl-ES", label: "Galego" },
42
+  { lng: "vi-VN", label: "Tiếng Việt" },
44 43
 ];
45 44
 
46
-export const languages: Language[] = [
47
-  { lng: "en", label: "English", data: "en.json" },
48
-]
45
+export const languages: Language[] = [{ lng: "en", label: "English" }]
49 46
   .concat(
50 47
     allLanguages.sort((left, right) => (left.label > right.label ? 1 : -1)),
51 48
   )
@@ -65,7 +62,7 @@ export const setLanguage = async (newLng: string | undefined) => {
65 62
 
66 63
   document.documentElement.dir = currentLanguage.rtl ? "rtl" : "ltr";
67 64
 
68
-  currentLanguageData = await import(`./locales/${currentLanguage.data}`);
65
+  currentLanguageData = await import(`./locales/${currentLanguage.lng}.json`);
69 66
 
70 67
   languageDetector.cacheUserLanguage(currentLanguage.lng);
71 68
 };
@@ -78,7 +75,7 @@ export const setLanguageFirstTime = async () => {
78 75
 
79 76
   document.documentElement.dir = currentLanguage.rtl ? "rtl" : "ltr";
80 77
 
81
-  currentLanguageData = await import(`./locales/${currentLanguage.data}`);
78
+  currentLanguageData = await import(`./locales/${currentLanguage.lng}.json`);
82 79
 
83 80
   languageDetector.cacheUserLanguage(currentLanguage.lng);
84 81
 };

+ 2
- 21
src/index.tsx 查看文件

@@ -5,12 +5,9 @@ import * as SentryIntegrations from "@sentry/integrations";
5 5
 
6 6
 import { EVENT } from "./constants";
7 7
 import { TopErrorBoundary } from "./components/TopErrorBoundary";
8
-import { InitializeApp } from "./components/InitializeApp";
9
-import { IsMobileProvider } from "./is-mobile";
10
-import App from "./components/App";
8
+import Excalidraw from "./excalidraw-embed/index";
11 9
 import { register as registerServiceWorker } from "./serviceWorker";
12 10
 
13
-import "./css/styles.scss";
14 11
 import { loadFromBlob } from "./data";
15 12
 
16 13
 // On Apple mobile devices add the proprietary app icon and splashscreen markup.
@@ -63,18 +60,6 @@ Sentry.init({
63 60
 
64 61
 window.__EXCALIDRAW_SHA__ = REACT_APP_GIT_SHA;
65 62
 
66
-// Block pinch-zooming on iOS outside of the content area
67
-document.addEventListener(
68
-  "touchmove",
69
-  (event) => {
70
-    // @ts-ignore
71
-    if (typeof event.scale === "number" && event.scale !== 1) {
72
-      event.preventDefault();
73
-    }
74
-  },
75
-  { passive: false },
76
-);
77
-
78 63
 function ExcalidrawApp() {
79 64
   const [dimensions, setDimensions] = useState({
80 65
     width: window.innerWidth,
@@ -97,11 +82,7 @@ function ExcalidrawApp() {
97 82
   const { width, height } = dimensions;
98 83
   return (
99 84
     <TopErrorBoundary>
100
-      <IsMobileProvider>
101
-        <InitializeApp>
102
-          <App width={width} height={height} />
103
-        </InitializeApp>
104
-      </IsMobileProvider>
85
+      <Excalidraw width={width} height={height} />
105 86
     </TopErrorBoundary>
106 87
   );
107 88
 }

+ 10
- 0
src/types.ts 查看文件

@@ -14,6 +14,7 @@ import { Point as RoughPoint } from "roughjs/bin/geometry";
14 14
 import { SocketUpdateDataSource } from "./data";
15 15
 import { LinearElementEditor } from "./element/linearElementEditor";
16 16
 import { SuggestedBinding } from "./element/binding";
17
+import { DataState } from "./data/types";
17 18
 
18 19
 export type FlooredNumber = number & { _brand: "FlooredNumber" };
19 20
 export type Point = Readonly<RoughPoint>;
@@ -122,4 +123,13 @@ export type LibraryItems = readonly LibraryItem[];
122 123
 export interface ExcalidrawProps {
123 124
   width: number;
124 125
   height: number;
126
+  onChange?: (
127
+    elements: readonly ExcalidrawElement[],
128
+    appState: AppState,
129
+  ) => void;
130
+  initialData?: DataState;
131
+  user?: {
132
+    name?: string | null;
133
+  };
134
+  onUsernameChange?: (username: string) => void;
125 135
 }

Loading…
取消
儲存