This commit is contained in:
pipipi-pikachu 2020-12-11 22:55:25 +08:00
parent ac795e2e24
commit 39d5b1fc20
12 changed files with 567 additions and 174 deletions

354
package-lock.json generated
View File

@ -1762,6 +1762,12 @@
"integrity": "sha1-fuMwunyq+5gJC+zoal7kQRWQTCw=",
"dev": true
},
"@types/resize-observer-browser": {
"version": "0.1.4",
"resolved": "https://registry.npm.taobao.org/@types/resize-observer-browser/download/@types/resize-observer-browser-0.1.4.tgz",
"integrity": "sha1-hIeaTW1Krv3lPUspyRwMTLz/wm8=",
"dev": true
},
"@types/serve-static": {
"version": "1.13.8",
"resolved": "https://registry.npm.taobao.org/@types/serve-static/download/@types/serve-static-1.13.8.tgz?cache=0&sync_timestamp=1605657732347&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fserve-static%2Fdownload%2F%40types%2Fserve-static-1.13.8.tgz",
@ -2252,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?cache=0&sync_timestamp=1607084938170&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffork-ts-checker-webpack-plugin%2Fdownload%2Ffork-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=1606205010380&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": {
@ -2531,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",
@ -2604,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",
@ -2664,18 +2531,6 @@
"integrity": "sha1-tkb2m+OULavOzJ1mOcgNwQXvqmY=",
"dev": true
},
"vue-loader-v16": {
"version": "npm:vue-loader@16.1.1",
"resolved": "https://registry.npm.taobao.org/vue-loader/download/vue-loader-16.1.1.tgz?cache=0&sync_timestamp=1607093677581&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvue-loader%2Fdownload%2Fvue-loader-16.1.1.tgz",
"integrity": "sha1-9bKG1grGiGaExjoXoYQ5HMngGZo=",
"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",
@ -7462,6 +7317,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?cache=0&sync_timestamp=1607084938170&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffork-ts-checker-webpack-plugin%2Fdownload%2Ffork-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?cache=0&sync_timestamp=1606205010380&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
}
}
},
"form-data": {
"version": "2.3.3",
"resolved": "https://registry.npm.taobao.org/form-data/download/form-data-2.3.3.tgz",
@ -15897,6 +15868,87 @@
}
}
},
"vue-loader-v16": {
"version": "npm:vue-loader@16.1.1",
"resolved": "https://registry.npm.taobao.org/vue-loader/download/vue-loader-16.1.1.tgz?cache=0&sync_timestamp=1607093677581&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvue-loader%2Fdownload%2Fvue-loader-16.1.1.tgz",
"integrity": "sha1-9bKG1grGiGaExjoXoYQ5HMngGZo=",
"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?cache=0&sync_timestamp=1606205010380&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"
}
}
}
},
"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",

View File

@ -24,6 +24,7 @@
"@types/clipboard": "^2.0.1",
"@types/crypto-js": "^4.0.1",
"@types/jest": "^24.0.19",
"@types/resize-observer-browser": "^0.1.4",
"@typescript-eslint/eslint-plugin": "^2.33.0",
"@typescript-eslint/parser": "^2.33.0",
"@vue/cli-plugin-babel": "~4.5.0",
@ -36,17 +37,17 @@
"@vue/compiler-sfc": "^3.0.0",
"@vue/eslint-config-typescript": "^5.0.2",
"@vue/test-utils": "^2.0.0-0",
"babel-plugin-import": "^1.13.3",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^7.0.0-0",
"babel-plugin-import": "^1.13.3",
"less": "^3.12.2",
"less-loader": "^7.1.0",
"sass": "^1.26.5",
"sass-loader": "^8.0.2",
"typescript": "~3.9.3",
"vue-jest": "^5.0.0-0",
"stylelint": "^13.8.0",
"stylelint-config-standard": "^20.0.0",
"stylelint-webpack-plugin": "^2.1.1"
"stylelint-webpack-plugin": "^2.1.1",
"typescript": "~3.9.3",
"vue-jest": "^5.0.0-0"
}
}

View File

View File

@ -0,0 +1,35 @@
<template>
<iframe
class="iframe-wrapper"
frameborder="0"
allowfullscreen="true"
:src="src"
:width="width"
:height="height"
></iframe>
</template>
<script lang="ts">
export default {
name: 'iframe-wrapper',
props: {
src: {
type: String,
required: true,
},
width: {
type: Number,
},
height: {
type: Number,
},
},
}
</script>
<style lang="scss" scoped>
.iframe-wrapper {
transform-origin: 0 0;
display: block;
}
</style>

View File

@ -0,0 +1,15 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
version="1.1"
>
<slot></slot>
</svg>
</template>
<script lang="ts">
export default {
name: 'svg-wrapper',
}
</script>

View File

@ -0,0 +1,51 @@
<template>
<div class="upload-input" @click="handleClick()">
<slot></slot>
<input
class="file-input"
type="file"
name="upload"
ref="inputRef"
:accept="accept"
@change="$event => handleChange($event)"
>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
export default defineComponent({
name: 'upload-input',
props: {
accept: {
type: String,
default: 'image/*',
},
},
setup(props, { emit }) {
const inputRef = ref<HTMLInputElement | null>(null)
const handleClick = () => {
if(!inputRef.value) return
inputRef.value.value = ''
inputRef.value.click()
}
const handleChange = (e: InputEvent) => {
const files = (e.target as HTMLInputElement).files
if(files) emit('change', files)
}
return {
handleClick,
handleChange,
}
},
})
</script>
<style lang="scss" scoped>
.file-input {
display: none;
}
</style>

View File

@ -9,15 +9,15 @@ export type State = {
thumbnailsFocus: boolean;
editorAreaFocus: boolean;
availableFonts: string[];
slides: Slide[],
slideIndex: number,
slides: Slide[];
slideIndex: number;
}
export const state: State = {
activeElementIdList: [],
handleElementId: '',
isShowGridLines: false,
editorAreaShowScale: 80,
editorAreaShowScale: 85,
canvasScale: 1,
thumbnailsFocus: false,
editorAreaFocus: false,

View File

@ -0,0 +1,66 @@
<template>
<div :class="`mouse-selection quadrant-${quadrant}`"
:style="{
top: top + 'px',
left: left + 'px',
width: width + 'px',
height: height + 'px',
}"
></div>
</template>
<script lang="ts">
export default {
name: 'mouse-selection',
props: {
top: {
type: Number,
required: true,
},
left: {
type: Number,
required: true,
},
width: {
type: Number,
required: true,
},
height: {
type: Number,
required: true,
},
quadrant: {
type: Number,
required: true,
validator(value: number) {
return [1, 2, 3, 4].includes(value)
},
},
}
}
</script>
<style lang="scss" scoped>
.mouse-selection {
position: absolute;
background-color: rgba($themeColor, 0.25);
z-index: 200;
&.quadrant-1 {
transform-origin: 0 0;
transform: rotate(180deg);
}
&.quadrant-2 {
transform-origin: 50% 0;
transform: rotate(180deg);
}
&.quadrant-3 {
transform-origin: 0 50%;
transform: rotate(180deg);
}
&.quadrant-4 {
transform-origin: 0 0;
transform: rotate(0deg);
}
}
</style>

View File

@ -1,13 +1,180 @@
<template>
<div class="canvas">
<div
class="canvas"
ref="canvasRef"
@mousedown="$event => handleClickBlankArea($event)"
>
<div
class="viewport"
ref="viewportRef"
:style="{
width: viewportStyles.width + 'px',
height: viewportStyles.height + 'px',
left: viewportStyles.left + 'px',
top: viewportStyles.top + 'px',
transform: `scale(${viewportStyles.scale})`,
}"
>
<MouseSelection
v-if="mouseSelectionState.isShow"
:top="mouseSelectionState.top"
:left="mouseSelectionState.left"
:width="mouseSelectionState.width"
:height="mouseSelectionState.height"
:quadrant="mouseSelectionState.quadrant"
/>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { computed, defineComponent, onMounted, onUnmounted, reactive, ref } from 'vue'
import { useStore } from 'vuex'
import { State } from '@/store/state'
import { VIEWPORT_SIZE, VIEWPORT_ASPECT_RATIO } from '@/configs/canvas'
import MouseSelection from './MouseSelection.vue'
export default defineComponent({
name: 'canvas',
name: 'v-canvas',
components: {
MouseSelection,
},
setup() {
const viewportStyles = reactive({
width: VIEWPORT_SIZE,
height: VIEWPORT_SIZE * VIEWPORT_ASPECT_RATIO,
left: 0,
top: 0,
scale: 1,
})
const canvasRef = ref<Element | null>(null)
const canvasScale = ref(1)
const store = useStore<State>()
const editorAreaShowScale = computed(() => store.state.editorAreaShowScale)
const setViewportSize = () => {
if(!canvasRef.value) return
const canvasWidth = canvasRef.value.clientWidth
const canvasHeight = canvasRef.value.clientHeight
if(canvasHeight / canvasWidth > VIEWPORT_ASPECT_RATIO) {
const viewportActualWidth = canvasWidth * (editorAreaShowScale.value / 100)
canvasScale.value = viewportActualWidth / VIEWPORT_SIZE
viewportStyles.scale = canvasScale.value
viewportStyles.left = (canvasWidth - viewportActualWidth) / 2
viewportStyles.top = (canvasHeight - viewportActualWidth * VIEWPORT_ASPECT_RATIO) / 2
}
else {
const viewportActualHeight = canvasHeight * (editorAreaShowScale.value / 100)
canvasScale.value = viewportActualHeight / (VIEWPORT_SIZE * VIEWPORT_ASPECT_RATIO)
viewportStyles.scale = canvasScale.value
viewportStyles.left = (canvasWidth - viewportActualHeight / VIEWPORT_ASPECT_RATIO) / 2
viewportStyles.top = (canvasHeight - viewportActualHeight) / 2
}
}
const resizeObserver = new ResizeObserver(setViewportSize)
onMounted(() => {
if(canvasRef.value) resizeObserver.observe(canvasRef.value)
})
onUnmounted(() => {
if(canvasRef.value) resizeObserver.unobserve(canvasRef.value)
})
const viewportRef = ref<Element | null>(null)
const mouseSelectionState = reactive({
isShow: false,
top: 0,
left: 0,
width: 0,
height: 0,
quadrant: 1,
})
const updateMouseSelection = (e: MouseEvent) => {
if(!viewportRef.value) return
let isMouseDown = true
const viewportRect = viewportRef.value.getBoundingClientRect()
const minSelectionRange = 5
const startPageX = e.pageX
const startPageY = e.pageY
const left = (startPageX - viewportRect.x) / canvasScale.value
const top = (startPageY - viewportRect.y) / canvasScale.value
mouseSelectionState.isShow = false
mouseSelectionState.quadrant = 4
mouseSelectionState.top = top
mouseSelectionState.left = left
mouseSelectionState.width = 0
mouseSelectionState.height = 0
document.onmousemove = e => {
if(!isMouseDown) return
const currentPageX = e.pageX
const currentPageY = e.pageY
const offsetWidth = (currentPageX - startPageX) / canvasScale.value
const offsetHeight = (currentPageY - startPageY) / canvasScale.value
const width = Math.abs(offsetWidth)
const height = Math.abs(offsetHeight)
if( width < minSelectionRange || height < minSelectionRange ) return
let quadrant = 0
if( offsetWidth > 0 && offsetHeight > 0 ) quadrant = 4
else if( offsetWidth < 0 && offsetHeight < 0 ) quadrant = 1
else if( offsetWidth > 0 && offsetHeight < 0 ) quadrant = 2
else if( offsetWidth < 0 && offsetHeight > 0 ) quadrant = 3
mouseSelectionState.isShow = true
mouseSelectionState.quadrant = quadrant
mouseSelectionState.width = width
mouseSelectionState.height = height
}
document.onmouseup = () => {
document.onmousemove = null
document.onmouseup = null
isMouseDown = false
mouseSelectionState.isShow = false
}
}
const handleClickBlankArea = (e: MouseEvent) => {
updateMouseSelection(e)
}
return {
canvasRef,
viewportRef,
viewportStyles,
mouseSelectionState,
handleClickBlankArea,
}
},
})
</script>
</script>
<style lang="scss" scoped>
.canvas {
height: 100%;
user-select: none;
overflow: hidden;
background-color: #f9f9f9;
position: relative;
}
.viewport {
position: absolute;
transform-origin: 0 0;
background-color: #fff;
box-shadow: 0 0 20px 0 rgba(0, 0, 0, .1);
}
</style>

View File

@ -14,7 +14,6 @@ export default defineComponent({
<style lang="scss" scoped>
.canvas-tool {
height: 40px;
border-bottom: 1px solid #eee;
background-color: #fff;
}

View File

@ -3,9 +3,9 @@
<EditorHeader class="layout-header" />
<div class="layout-content">
<Thumbnails class="layout-content-left" />
<div class="layout-content-body">
<CanvasTool />
<Canvas />
<div class="layout-content-center">
<CanvasTool class="center-top" />
<Canvas class="center-body" />
</div>
<Toolbar class="layout-content-right" />
</div>
@ -36,22 +36,28 @@ export default defineComponent({
<style lang="scss" scoped>
.editor {
height: 100%;
display: flex;
flex-direction: column;
}
.layout-header {
height: 40px;
}
.layout-content {
flex: 1;
height: calc(100% - 40px);
display: flex;
}
.layout-content-left {
width: 180px;
width: 160px;
height: 100%;
flex-shrink: 0;
}
.layout-content-body {
flex: 1;
.layout-content-center {
width: calc(100% - 160px - 260px);
.center-top {
height: 40px;
}
.center-body {
height: calc(100% - 40px);
}
}
.layout-content-right {
width: 260px;

View File

@ -14,7 +14,8 @@
"baseUrl": ".",
"types": [
"webpack-env",
"jest"
"jest",
"resize-observer-browser"
],
"paths": {
"@/*": [