pipipi-pikachu 5 år sedan
förälder
incheckning
fdbb6dde16

+ 204 - 153
package-lock.json

@@ -1,6 +1,6 @@
 {
-  "name": "ppt_online_editor",
-  "version": "0.1.0",
+  "name": "hamster_ppt",
+  "version": "0.0.1",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
@@ -2258,122 +2258,6 @@
         "tslint": "^5.20.1",
         "webpack": "^4.0.0",
         "yorkie": "^2.0.0"
-      },
-      "dependencies": {
-        "ansi-styles": {
-          "version": "4.3.0",
-          "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-4.3.0.tgz?cache=0&sync_timestamp=1606792369066&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-4.3.0.tgz",
-          "integrity": "sha1-7dgDYornHATIWuegkG7a00tkiTc=",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "color-convert": "^2.0.1"
-          }
-        },
-        "chalk": {
-          "version": "4.1.0",
-          "resolved": "https://registry.npm.taobao.org/chalk/download/chalk-4.1.0.tgz?cache=0&sync_timestamp=1591687000046&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fchalk%2Fdownload%2Fchalk-4.1.0.tgz",
-          "integrity": "sha1-ThSHCmGNni7dl92DRf2dncMVZGo=",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "ansi-styles": "^4.1.0",
-            "supports-color": "^7.1.0"
-          }
-        },
-        "color-convert": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npm.taobao.org/color-convert/download/color-convert-2.0.1.tgz?cache=0&sync_timestamp=1566248870121&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcolor-convert%2Fdownload%2Fcolor-convert-2.0.1.tgz",
-          "integrity": "sha1-ctOmjVmMm9s68q0ehPIdiWq9TeM=",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "color-name": "~1.1.4"
-          }
-        },
-        "color-name": {
-          "version": "1.1.4",
-          "resolved": "https://registry.npm.taobao.org/color-name/download/color-name-1.1.4.tgz",
-          "integrity": "sha1-wqCah6y95pVD3m9j+jmVyCbFNqI=",
-          "dev": true,
-          "optional": true
-        },
-        "fork-ts-checker-webpack-plugin-v5": {
-          "version": "npm:fork-ts-checker-webpack-plugin@5.2.1",
-          "resolved": "https://registry.npm.taobao.org/fork-ts-checker-webpack-plugin/download/fork-ts-checker-webpack-plugin-5.2.1.tgz",
-          "integrity": "sha1-eTJthpeXkG+osk4qvPlCH8gFRQ0=",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "@babel/code-frame": "^7.8.3",
-            "@types/json-schema": "^7.0.5",
-            "chalk": "^4.1.0",
-            "cosmiconfig": "^6.0.0",
-            "deepmerge": "^4.2.2",
-            "fs-extra": "^9.0.0",
-            "memfs": "^3.1.2",
-            "minimatch": "^3.0.4",
-            "schema-utils": "2.7.0",
-            "semver": "^7.3.2",
-            "tapable": "^1.0.0"
-          }
-        },
-        "has-flag": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npm.taobao.org/has-flag/download/has-flag-4.0.0.tgz",
-          "integrity": "sha1-lEdx/ZyByBJlxNaUGGDaBrtZR5s=",
-          "dev": true,
-          "optional": true
-        },
-        "lru-cache": {
-          "version": "6.0.0",
-          "resolved": "https://registry.npm.taobao.org/lru-cache/download/lru-cache-6.0.0.tgz?cache=0&sync_timestamp=1594427484405&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Flru-cache%2Fdownload%2Flru-cache-6.0.0.tgz",
-          "integrity": "sha1-bW/mVw69lqr5D8rR2vo7JWbbOpQ=",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "yallist": "^4.0.0"
-          }
-        },
-        "schema-utils": {
-          "version": "2.7.0",
-          "resolved": "https://registry.npm.taobao.org/schema-utils/download/schema-utils-2.7.0.tgz",
-          "integrity": "sha1-FxUfdtjq5n+793lgwzxnatn078c=",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "@types/json-schema": "^7.0.4",
-            "ajv": "^6.12.2",
-            "ajv-keywords": "^3.4.1"
-          }
-        },
-        "semver": {
-          "version": "7.3.4",
-          "resolved": "https://registry.npm.taobao.org/semver/download/semver-7.3.4.tgz?cache=0&sync_timestamp=1606852064928&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsemver%2Fdownload%2Fsemver-7.3.4.tgz",
-          "integrity": "sha1-J6qn0uTKdkUvmNOt0JOnLJQ+3Jc=",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "lru-cache": "^6.0.0"
-          }
-        },
-        "supports-color": {
-          "version": "7.2.0",
-          "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-7.2.0.tgz?cache=0&sync_timestamp=1608033330722&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsupports-color%2Fdownload%2Fsupports-color-7.2.0.tgz",
-          "integrity": "sha1-G33NyzK4E4gBs+R4umpRyqiWSNo=",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "has-flag": "^4.0.0"
-          }
-        },
-        "yallist": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npm.taobao.org/yallist/download/yallist-4.0.0.tgz",
-          "integrity": "sha1-m7knkNnA7/7GO+c1GeEaNQGaOnI=",
-          "dev": true,
-          "optional": true
-        }
       }
     },
     "@vue/cli-plugin-unit-jest": {
@@ -2537,17 +2421,6 @@
             "unique-filename": "^1.1.1"
           }
         },
-        "chalk": {
-          "version": "4.1.0",
-          "resolved": "https://registry.npm.taobao.org/chalk/download/chalk-4.1.0.tgz?cache=0&sync_timestamp=1591687000046&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fchalk%2Fdownload%2Fchalk-4.1.0.tgz",
-          "integrity": "sha1-ThSHCmGNni7dl92DRf2dncMVZGo=",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "ansi-styles": "^4.1.0",
-            "supports-color": "^7.1.0"
-          }
-        },
         "cliui": {
           "version": "6.0.0",
           "resolved": "https://registry.npm.taobao.org/cliui/download/cliui-6.0.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcliui%2Fdownload%2Fcliui-6.0.0.tgz",
@@ -2610,18 +2483,6 @@
             "graceful-fs": "^4.1.6"
           }
         },
-        "loader-utils": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npm.taobao.org/loader-utils/download/loader-utils-2.0.0.tgz",
-          "integrity": "sha1-5MrOW4FtQloWa18JfhDNErNgZLA=",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "big.js": "^5.2.2",
-            "emojis-list": "^3.0.0",
-            "json5": "^2.1.2"
-          }
-        },
         "source-map": {
           "version": "0.6.1",
           "resolved": "https://registry.npm.taobao.org/source-map/download/source-map-0.6.1.tgz",
@@ -2670,18 +2531,6 @@
           "integrity": "sha1-tkb2m+OULavOzJ1mOcgNwQXvqmY=",
           "dev": true
         },
-        "vue-loader-v16": {
-          "version": "npm:vue-loader@16.1.2",
-          "resolved": "https://registry.npm.taobao.org/vue-loader/download/vue-loader-16.1.2.tgz?cache=0&sync_timestamp=1608187974157&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvue-loader%2Fdownload%2Fvue-loader-16.1.2.tgz",
-          "integrity": "sha1-XAO2xQ0qX5g8fOuhXFDXjKKymPQ=",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "chalk": "^4.1.0",
-            "hash-sum": "^2.0.0",
-            "loader-utils": "^2.0.0"
-          }
-        },
         "wrap-ansi": {
           "version": "6.2.0",
           "resolved": "https://registry.npm.taobao.org/wrap-ansi/download/wrap-ansi-6.2.0.tgz",
@@ -7473,6 +7322,122 @@
         "worker-rpc": "^0.1.0"
       }
     },
+    "fork-ts-checker-webpack-plugin-v5": {
+      "version": "npm:fork-ts-checker-webpack-plugin@5.2.1",
+      "resolved": "https://registry.npm.taobao.org/fork-ts-checker-webpack-plugin/download/fork-ts-checker-webpack-plugin-5.2.1.tgz",
+      "integrity": "sha1-eTJthpeXkG+osk4qvPlCH8gFRQ0=",
+      "dev": true,
+      "optional": true,
+      "requires": {
+        "@babel/code-frame": "^7.8.3",
+        "@types/json-schema": "^7.0.5",
+        "chalk": "^4.1.0",
+        "cosmiconfig": "^6.0.0",
+        "deepmerge": "^4.2.2",
+        "fs-extra": "^9.0.0",
+        "memfs": "^3.1.2",
+        "minimatch": "^3.0.4",
+        "schema-utils": "2.7.0",
+        "semver": "^7.3.2",
+        "tapable": "^1.0.0"
+      },
+      "dependencies": {
+        "ansi-styles": {
+          "version": "4.3.0",
+          "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-4.3.0.tgz?cache=0&sync_timestamp=1606792302448&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-4.3.0.tgz",
+          "integrity": "sha1-7dgDYornHATIWuegkG7a00tkiTc=",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "color-convert": "^2.0.1"
+          }
+        },
+        "chalk": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npm.taobao.org/chalk/download/chalk-4.1.0.tgz",
+          "integrity": "sha1-ThSHCmGNni7dl92DRf2dncMVZGo=",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "ansi-styles": "^4.1.0",
+            "supports-color": "^7.1.0"
+          }
+        },
+        "color-convert": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npm.taobao.org/color-convert/download/color-convert-2.0.1.tgz",
+          "integrity": "sha1-ctOmjVmMm9s68q0ehPIdiWq9TeM=",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "color-name": "~1.1.4"
+          }
+        },
+        "color-name": {
+          "version": "1.1.4",
+          "resolved": "https://registry.npm.taobao.org/color-name/download/color-name-1.1.4.tgz",
+          "integrity": "sha1-wqCah6y95pVD3m9j+jmVyCbFNqI=",
+          "dev": true,
+          "optional": true
+        },
+        "has-flag": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npm.taobao.org/has-flag/download/has-flag-4.0.0.tgz?cache=0&sync_timestamp=1596294337050&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fhas-flag%2Fdownload%2Fhas-flag-4.0.0.tgz",
+          "integrity": "sha1-lEdx/ZyByBJlxNaUGGDaBrtZR5s=",
+          "dev": true,
+          "optional": true
+        },
+        "lru-cache": {
+          "version": "6.0.0",
+          "resolved": "https://registry.npm.taobao.org/lru-cache/download/lru-cache-6.0.0.tgz",
+          "integrity": "sha1-bW/mVw69lqr5D8rR2vo7JWbbOpQ=",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "yallist": "^4.0.0"
+          }
+        },
+        "schema-utils": {
+          "version": "2.7.0",
+          "resolved": "https://registry.npm.taobao.org/schema-utils/download/schema-utils-2.7.0.tgz",
+          "integrity": "sha1-FxUfdtjq5n+793lgwzxnatn078c=",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "@types/json-schema": "^7.0.4",
+            "ajv": "^6.12.2",
+            "ajv-keywords": "^3.4.1"
+          }
+        },
+        "semver": {
+          "version": "7.3.4",
+          "resolved": "https://registry.npm.taobao.org/semver/download/semver-7.3.4.tgz?cache=0&sync_timestamp=1606852122426&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsemver%2Fdownload%2Fsemver-7.3.4.tgz",
+          "integrity": "sha1-J6qn0uTKdkUvmNOt0JOnLJQ+3Jc=",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "lru-cache": "^6.0.0"
+          }
+        },
+        "supports-color": {
+          "version": "7.2.0",
+          "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-7.2.0.tgz",
+          "integrity": "sha1-G33NyzK4E4gBs+R4umpRyqiWSNo=",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "has-flag": "^4.0.0"
+          }
+        },
+        "yallist": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npm.taobao.org/yallist/download/yallist-4.0.0.tgz",
+          "integrity": "sha1-m7knkNnA7/7GO+c1GeEaNQGaOnI=",
+          "dev": true,
+          "optional": true
+        }
+      }
+    },
     "form-data": {
       "version": "2.3.3",
       "resolved": "https://registry.npm.taobao.org/form-data/download/form-data-2.3.3.tgz",
@@ -10796,6 +10761,11 @@
         "through2": "^2.0.0"
       }
     },
+    "mitt": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npm.taobao.org/mitt/download/mitt-2.1.0.tgz?cache=0&sync_timestamp=1594823587749&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmitt%2Fdownload%2Fmitt-2.1.0.tgz",
+      "integrity": "sha1-90BXfCMXbGIFsSGylzUU6t4bIjA="
+    },
     "mixin-deep": {
       "version": "1.3.2",
       "resolved": "https://registry.npm.taobao.org/mixin-deep/download/mixin-deep-1.3.2.tgz",
@@ -15913,6 +15883,87 @@
         }
       }
     },
+    "vue-loader-v16": {
+      "version": "npm:vue-loader@16.1.2",
+      "resolved": "https://registry.npm.taobao.org/vue-loader/download/vue-loader-16.1.2.tgz?cache=0&sync_timestamp=1608187974157&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvue-loader%2Fdownload%2Fvue-loader-16.1.2.tgz",
+      "integrity": "sha1-XAO2xQ0qX5g8fOuhXFDXjKKymPQ=",
+      "dev": true,
+      "optional": true,
+      "requires": {
+        "chalk": "^4.1.0",
+        "hash-sum": "^2.0.0",
+        "loader-utils": "^2.0.0"
+      },
+      "dependencies": {
+        "ansi-styles": {
+          "version": "4.3.0",
+          "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-4.3.0.tgz?cache=0&sync_timestamp=1606792302448&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-4.3.0.tgz",
+          "integrity": "sha1-7dgDYornHATIWuegkG7a00tkiTc=",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "color-convert": "^2.0.1"
+          }
+        },
+        "chalk": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npm.taobao.org/chalk/download/chalk-4.1.0.tgz",
+          "integrity": "sha1-ThSHCmGNni7dl92DRf2dncMVZGo=",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "ansi-styles": "^4.1.0",
+            "supports-color": "^7.1.0"
+          }
+        },
+        "color-convert": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npm.taobao.org/color-convert/download/color-convert-2.0.1.tgz",
+          "integrity": "sha1-ctOmjVmMm9s68q0ehPIdiWq9TeM=",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "color-name": "~1.1.4"
+          }
+        },
+        "color-name": {
+          "version": "1.1.4",
+          "resolved": "https://registry.npm.taobao.org/color-name/download/color-name-1.1.4.tgz",
+          "integrity": "sha1-wqCah6y95pVD3m9j+jmVyCbFNqI=",
+          "dev": true,
+          "optional": true
+        },
+        "has-flag": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npm.taobao.org/has-flag/download/has-flag-4.0.0.tgz?cache=0&sync_timestamp=1596294337050&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fhas-flag%2Fdownload%2Fhas-flag-4.0.0.tgz",
+          "integrity": "sha1-lEdx/ZyByBJlxNaUGGDaBrtZR5s=",
+          "dev": true,
+          "optional": true
+        },
+        "loader-utils": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npm.taobao.org/loader-utils/download/loader-utils-2.0.0.tgz",
+          "integrity": "sha1-5MrOW4FtQloWa18JfhDNErNgZLA=",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "big.js": "^5.2.2",
+            "emojis-list": "^3.0.0",
+            "json5": "^2.1.2"
+          }
+        },
+        "supports-color": {
+          "version": "7.2.0",
+          "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-7.2.0.tgz",
+          "integrity": "sha1-G33NyzK4E4gBs+R4umpRyqiWSNo=",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "has-flag": "^4.0.0"
+          }
+        }
+      }
+    },
     "vue-router": {
       "version": "4.0.1",
       "resolved": "https://registry.npm.taobao.org/vue-router/download/vue-router-4.0.1.tgz?cache=0&sync_timestamp=1607347245114&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvue-router%2Fdownload%2Fvue-router-4.0.1.tgz",

+ 1 - 0
package.json

@@ -15,6 +15,7 @@
     "crypto-js": "^4.0.0",
     "dexie": "^3.0.3",
     "lodash": "^4.17.20",
+    "mitt": "^2.1.0",
     "store2": "^2.12.0",
     "vue": "^3.0.0",
     "vue-router": "^4.0.0-0",

+ 0 - 6
src/configs/element.ts

@@ -10,7 +10,6 @@ export enum ElementTypes {
 }
 
 export const DEFAULT_TEXT = {
-  type: 'text',
   left: 0,
   top: 0,
   width: 300,
@@ -22,20 +21,17 @@ export const DEFAULT_TEXT = {
 }
 
 export const DEFAULT_IMAGE = {
-  type: 'image',
   left: 0,
   top: 0,
   lockRatio: true,
 }
 
 export const DEFAULT_SHAPE = {
-  type: 'shape',
   fill: DEFAULT_COLOR,
   lockRatio: false,
 }
 
 export const DEFAULT_LINE = {
-  type: 'line',
   style: 'solid',
   marker: ['', ''],
   width: 4,
@@ -43,7 +39,6 @@ export const DEFAULT_LINE = {
 }
 
 export const DEFAULT_CHART = {
-  type: 'chart',
   left: 0,
   top: 0,
   width: 500,
@@ -51,7 +46,6 @@ export const DEFAULT_CHART = {
 }
 
 export const DEFAULT_TABLE = {
-  type: 'table',
   left: 0,
   top: 0,
   isLock: false,

+ 144 - 130
src/views/Editor/Canvas/hooks/useCreateElement.ts

@@ -1,131 +1,145 @@
-import { createRandomCode } from '@/utils/common'
-import { getImageSize } from '@/utils/image'
-import { VIEWPORT_SIZE, VIEWPORT_ASPECT_RATIO } from '@/configs/canvas'
-import { TableElementCell } from '@/types/slides'
-import {
-  DEFAULT_IMAGE,
-  DEFAULT_TEXT,
-  DEFAULT_SHAPE,
-  DEFAULT_LINE,
-  DEFAULT_CHART,
-  DEFAULT_TABLE,
-} from '@/configs/element'
-
-interface CommonElementPosition {
-  top: number;
-  left: number;
-  width: number;
-  height: number;
-}
-
-interface LineElementPosition {
-  top: number;
-  left: number;
-  start: [number, number];
-  end: [number, number];
-}
-
-export default () => {
-  const insertImage = (imgUrl: string) => {
-    getImageSize(imgUrl).then(({ width, height }) => {
-      const scale = width / height
-  
-      if(scale < VIEWPORT_ASPECT_RATIO && width > VIEWPORT_SIZE) {
-        width = VIEWPORT_SIZE
-        height = width * scale
-      }
-      else if(height > VIEWPORT_SIZE * VIEWPORT_ASPECT_RATIO) {
-        height = VIEWPORT_SIZE * VIEWPORT_ASPECT_RATIO
-        width = height / scale
-      }
-  
-      return {
-        ...DEFAULT_IMAGE,
-        elId: createRandomCode(),
-        imgUrl,
-        width,
-        height,
-      }
-    })
-  }
-  
-  const insertChart = (chartType: string, data: Object) => {
-    return {
-      ...DEFAULT_CHART,
-      elId: createRandomCode(),
-      chartType,
-      data,
-    }
-  }
-  
-  const insertTable = (rowCount: number, colCount: number) => {
-    const row: TableElementCell[] = new Array(colCount).fill({ colspan: 1, rowspan: 1, content: '' })
-    const data: TableElementCell[][] = new Array(rowCount).fill(row)
-  
-    const DEFAULT_CELL_WIDTH = 80
-    const DEFAULT_CELL_HEIGHT = 35
-    const DEFAULT_BORDER_WIDTH = 2
-  
-    const colSizes: number[] = new Array(colCount).fill(DEFAULT_CELL_WIDTH)
-    const rowSizes: number[] = new Array(rowCount).fill(DEFAULT_CELL_HEIGHT)
-  
-    return {
-      ...DEFAULT_TABLE,
-      elId: createRandomCode(),
-      width: colCount * DEFAULT_CELL_WIDTH + DEFAULT_BORDER_WIDTH,
-      height: rowCount * DEFAULT_CELL_HEIGHT + DEFAULT_BORDER_WIDTH,
-      colSizes,
-      rowSizes,
-      data,
-    }
-  }
-  
-  const insertText = (position: CommonElementPosition) => {
-    const { left, top, width, height } = position
-    return {
-      ...DEFAULT_TEXT,
-      elId: createRandomCode(),
-      left, 
-      top, 
-      width, 
-      height,
-    }
-  }
-  
-  const insertShape = (position: CommonElementPosition, svgCode: string) => {
-    const { left, top, width, height } = position
-    return {
-      ...DEFAULT_SHAPE,
-      elId: createRandomCode(),
-      left, 
-      top, 
-      width, 
-      height,
-      svgCode,
-    }
-  }
-  
-  const insertLine = (position: LineElementPosition, marker: [string, string], lineType: string) => {
-    const { left, top, start, end } = position
-  
-    return {
-      ...DEFAULT_LINE,
-      elId: createRandomCode(),
-      left, 
-      top, 
-      start,
-      end,
-      marker,
-      lineType,
-    }
-  }
-
-  return {
-    insertImage,
-    insertChart,
-    insertTable,
-    insertText,
-    insertShape,
-    insertLine,
-  }
+import { useStore } from 'vuex'
+import { MutationTypes } from '@/store'
+import { createRandomCode } from '@/utils/common'
+import { getImageSize } from '@/utils/image'
+import { VIEWPORT_SIZE, VIEWPORT_ASPECT_RATIO } from '@/configs/canvas'
+import { PPTElement, TableElementCell } from '@/types/slides'
+import {
+  DEFAULT_IMAGE,
+  DEFAULT_TEXT,
+  DEFAULT_SHAPE,
+  DEFAULT_LINE,
+  DEFAULT_CHART,
+  DEFAULT_TABLE,
+} from '@/configs/element'
+
+interface CommonElementPosition {
+  top: number;
+  left: number;
+  width: number;
+  height: number;
+}
+
+interface LineElementPosition {
+  top: number;
+  left: number;
+  start: [number, number];
+  end: [number, number];
+}
+
+export default () => {
+  const store = useStore()
+
+  const createElement = (element: PPTElement) => {
+    store.commit(MutationTypes.ADD_ELEMENT, element)
+    store.commit(MutationTypes.SET_ACTIVE_ELEMENT_ID_LIST, [element.elId])
+  }
+
+  const createImageElement = (imgUrl: string) => {
+    getImageSize(imgUrl).then(({ width, height }) => {
+      const scale = width / height
+  
+      if(scale < VIEWPORT_ASPECT_RATIO && width > VIEWPORT_SIZE) {
+        width = VIEWPORT_SIZE
+        height = width * scale
+      }
+      else if(height > VIEWPORT_SIZE * VIEWPORT_ASPECT_RATIO) {
+        height = VIEWPORT_SIZE * VIEWPORT_ASPECT_RATIO
+        width = height / scale
+      }
+
+      createElement({
+        ...DEFAULT_IMAGE,
+        type: 'image',
+        elId: createRandomCode(),
+        imgUrl,
+        width,
+        height,
+      })
+    })
+  }
+  
+  const createChartElement = (chartType: string, data: string) => {
+    createElement({
+      ...DEFAULT_CHART,
+      type: 'chart',
+      elId: createRandomCode(),
+      chartType,
+      data,
+    })
+  }
+  
+  const createTableElement = (rowCount: number, colCount: number) => {
+    const row: TableElementCell[] = new Array(colCount).fill({ colspan: 1, rowspan: 1, content: '' })
+    const data: TableElementCell[][] = new Array(rowCount).fill(row)
+  
+    const DEFAULT_CELL_WIDTH = 80
+    const DEFAULT_CELL_HEIGHT = 35
+    const DEFAULT_BORDER_WIDTH = 2
+  
+    const colSizes: number[] = new Array(colCount).fill(DEFAULT_CELL_WIDTH)
+    const rowSizes: number[] = new Array(rowCount).fill(DEFAULT_CELL_HEIGHT)
+  
+    createElement({
+      ...DEFAULT_TABLE,
+      type: 'table',
+      elId: createRandomCode(),
+      width: colCount * DEFAULT_CELL_WIDTH + DEFAULT_BORDER_WIDTH,
+      height: rowCount * DEFAULT_CELL_HEIGHT + DEFAULT_BORDER_WIDTH,
+      colSizes,
+      rowSizes,
+      data,
+    })
+  }
+  
+  const createTextElement = (position: CommonElementPosition) => {
+    const { left, top, width, height } = position
+    createElement({
+      ...DEFAULT_TEXT,
+      type: 'text',
+      elId: createRandomCode(),
+      left, 
+      top, 
+      width, 
+      height,
+    })
+  }
+  
+  const createShapeElement = (position: CommonElementPosition, svgCode: string) => {
+    const { left, top, width, height } = position
+    createElement({
+      ...DEFAULT_SHAPE,
+      type: 'shape',
+      elId: createRandomCode(),
+      left, 
+      top, 
+      width, 
+      height,
+      svgCode,
+    })
+  }
+  
+  const createLineElement = (position: LineElementPosition, marker: [string, string], lineType: string) => {
+    const { left, top, start, end } = position
+    createElement({
+      ...DEFAULT_LINE,
+      type: 'line',
+      elId: createRandomCode(),
+      left, 
+      top, 
+      start,
+      end,
+      marker,
+      lineType,
+    })
+  }
+
+  return {
+    createImageElement,
+    createChartElement,
+    createTableElement,
+    createTextElement,
+    createShapeElement,
+    createLineElement,
+  }
 }

+ 72 - 0
src/hooks/usePasteTextClipboardData.ts

@@ -0,0 +1,72 @@
+import { computed, Ref } from 'vue'
+import { useStore } from 'vuex'
+import { MutationTypes, State } from '@/store'
+import { decrypt } from '@/utils/crypto'
+import { PPTElement, Slide } from '@/types/slides'
+import { createRandomCode } from '@/utils/common'
+
+export default () => {
+  const store = useStore<State>()
+  const currentSlide: Ref<Slide> = computed(() => store.getters.currentSlide)
+
+  const pasteElement = (elements: PPTElement[]) => {
+    const groupIdMap = {}
+    const elIdMap = {}
+    for(const element of elements) {
+      const groupId = element.groupId
+      if(groupId && !groupIdMap[groupId]) {
+        groupIdMap[groupId] = createRandomCode()
+      }
+      elIdMap[element.elId] = createRandomCode()
+    }
+    const currentSlideElementIdList = currentSlide.value.elements.map(el => el.elId)
+    
+    for(const element of elements) {
+      const inCurrentSlide = currentSlideElementIdList.includes(element.elId)
+      
+      element.elId = elIdMap[element.elId]
+
+      if(inCurrentSlide) {
+        element.left = element.left + 10
+        element.top = element.top + 10
+      }
+
+      if(element.groupId) element.groupId = groupIdMap[element.groupId]
+    }
+    store.commit(MutationTypes.ADD_ELEMENT, elements)
+    store.commit(MutationTypes.SET_ACTIVE_ELEMENT_ID_LIST, Object.values(elIdMap))
+  }
+
+  const pasteSlide = (slides: Slide[]) => {
+    console.log(slides)
+  }
+
+  const pasteText = (text: string) => {
+    console.log(text)
+  }
+
+  const pasteTextClipboardData = (text: string) => {
+    let clipboardData
+    try {
+      clipboardData = JSON.parse(decrypt(text))
+    }
+    catch {
+      clipboardData = text
+    }
+
+    // 粘贴自定义元素或页面
+    if(typeof clipboardData === 'object') {
+      const { type, data } = clipboardData
+
+      if(type === 'elements') pasteElement(data)
+      else if(type === 'slide') pasteSlide(data)
+    }
+
+    // 粘贴普通文本
+    else pasteText(clipboardData)
+  }
+
+  return {
+    pasteTextClipboardData,
+  }
+}

+ 4 - 4
src/types/slides.ts

@@ -11,9 +11,9 @@ export enum ElementTypes {
 
 export interface PPTElementBaseProps {
   elId: string;
-  isLock: boolean;
   left: number;
   top: number;
+  isLock?: boolean;
   groupId?: string;
 }
 
@@ -78,8 +78,8 @@ export interface PPTLineElement extends PPTElementBaseProps {
 export interface PPTChartElement extends PPTElementBaseProps, PPTElementSizeProps, PPTElementBorderProps {
   type: 'chart';
   chartType: string;
-  theme: string;
   data: string;
+  theme?: string;
 }
 
 export interface TableElementCell {
@@ -90,8 +90,8 @@ export interface TableElementCell {
 }
 export interface PPTTableElement extends PPTElementBaseProps, PPTElementSizeProps {
   type: 'table';
-  borderTheme: string;
-  theme: string;
+  borderTheme?: string;
+  theme?: string;
   rowSizes: number[];
   colSizes: number[];
   data: TableElementCell[][];

+ 5 - 0
src/utils/emitter.ts

@@ -0,0 +1,5 @@
+import mitt, { Emitter } from 'mitt'
+
+const emitter: Emitter = mitt()
+
+export default emitter

+ 5 - 9
src/views/Editor/Canvas/hooks/useCopyAndPasteElement.ts

@@ -3,14 +3,17 @@ import { useStore } from 'vuex'
 import { State, MutationTypes } from '@/store'
 import { PPTElement } from '@/types/slides'
 import { copyText, readClipboard } from '@/utils/clipboard'
-import { encrypt, decrypt } from '@/utils/crypto'
+import { encrypt } from '@/utils/crypto'
 import { message } from 'ant-design-vue'
+import usePasteTextClipboardData from '@/hooks/usePasteTextClipboardData'
 
 export default (deleteElement: () => void) => {
   const store = useStore<State>()
   const activeElementIdList = computed(() => store.state.activeElementIdList)
   const activeElementList: Ref<PPTElement[]> = computed(() => store.getters.activeElementList)
 
+  const { pasteTextClipboardData } = usePasteTextClipboardData()
+
   const copyElement = () => {
     if(!activeElementIdList.value.length) return
 
@@ -32,14 +35,7 @@ export default (deleteElement: () => void) => {
 
   const pasteElement = () => {
     readClipboard().then(text => {
-      let clipboardData
-      try {
-        clipboardData = JSON.parse(decrypt(text))
-      }
-      catch {
-        clipboardData = text
-      }
-      console.log(clipboardData)
+      pasteTextClipboardData(text)
     }).catch(err => message.warning(err))
   }
 

+ 4 - 3
src/views/Editor/Canvas/hooks/useDropImageElement.ts

@@ -1,16 +1,17 @@
 import { onMounted, onUnmounted, Ref } from 'vue'
 import { getImageDataURL } from '@/utils/image'
+import useCreateElement from '@/hooks/useCreateElement'
 
 export default (elementRef: Ref<HTMLElement | null>) => {
+  const { createImageElement } = useCreateElement()
+
   const handleDrop = (e: DragEvent) => {
     if(!e.dataTransfer) return
     const file = e.dataTransfer.items[0]
     if( file.kind === 'file' && file.type.indexOf('image') !== -1 ) {
       const imageFile = file.getAsFile()
       if(imageFile) {
-        getImageDataURL(imageFile).then(dataURL => {
-          console.log(dataURL)
-        })
+        getImageDataURL(imageFile).then(dataURL => createImageElement(dataURL))
       }
     }
   }

+ 2 - 2
src/views/Editor/Canvas/index.vue

@@ -75,7 +75,7 @@ import { computed, defineComponent, Ref, ref, watch, watchEffect } from 'vue'
 import { useStore } from 'vuex'
 import { State, MutationTypes } from '@/store'
 import { ContextmenuItem } from '@/components/Contextmenu/types'
-import { PPTElement } from '@/types/slides'
+import { PPTElement, Slide } from '@/types/slides'
 import { AlignmentLineProps } from './types/index'
 
 import useViewportSize from './hooks/useViewportSize'
@@ -122,7 +122,7 @@ export default defineComponent({
     const activeGroupElementId = ref('')
     watch(handleElementId, () => activeGroupElementId.value = '')
 
-    const currentSlide = computed(() => store.getters.currentSlide)
+    const currentSlide: Ref<Slide> = computed(() => store.getters.currentSlide)
     const elementList = ref<PPTElement[]>([])
     const setLocalElementList = () => {
       elementList.value = currentSlide.value ? JSON.parse(JSON.stringify(currentSlide.value.elements)) : []

+ 1 - 1
src/views/Editor/Thumbnails/index.vue

@@ -10,7 +10,7 @@
     <draggable 
       class="thumbnail-list"
       :modelValue="slides"
-      :animation="80"
+      :animation="300"
       :scroll="true"
       :scrollSensitivity="50"
       @end="handleDragEnd"

+ 7 - 16
src/views/Editor/usePasteEvent.ts

@@ -1,8 +1,9 @@
 import { computed, onMounted, onUnmounted } from 'vue'
 import { useStore } from 'vuex'
 import { State } from '@/store'
-import { decrypt } from '@/utils/crypto'
 import { getImageDataURL } from '@/utils/image'
+import usePasteTextClipboardData from '@/hooks/usePasteTextClipboardData'
+import useCreateElement from '@/hooks/useCreateElement'
 
 export default () => {
   const store = useStore<State>()
@@ -10,21 +11,11 @@ export default () => {
   const thumbnailsFocus = computed(() => store.state.thumbnailsFocus)
   const disableHotkeys = computed(() => store.state.disableHotkeys)
 
-  const pasteImageFile = (imageFile: File) => {
-    getImageDataURL(imageFile).then(dataURL => {
-      console.log(dataURL)
-    })
-  }
+  const { pasteTextClipboardData } = usePasteTextClipboardData()
+  const { createImageElement } = useCreateElement()
 
-  const pasteText = (text: string) => {
-    let content
-    try {
-      content = JSON.parse(decrypt(text))
-    }
-    catch {
-      content = text
-    }
-    console.log(content)
+  const pasteImageFile = (imageFile: File) => {
+    getImageDataURL(imageFile).then(dataURL => createImageElement(dataURL))
   }
 
   const pasteListener = (e: ClipboardEvent) => {
@@ -47,7 +38,7 @@ export default () => {
     }
 
     if( clipboardDataFirstItem.kind === 'string' && clipboardDataFirstItem.type === 'text/plain' ) {
-      clipboardDataFirstItem.getAsString(text => pasteText(text))
+      clipboardDataFirstItem.getAsString(text => pasteTextClipboardData(text))
     }
   }