fix(canvasShortCutPanel): add click event to quick insert component in… (#81)

* fix(canvasShotCutPanel): add click event to quick insert component in shortcutpanel

* fix(canvasShortPanel): add deepclone utils

* feat(utils): add deepClone method

* feat(unit-test): add unit test for deepclone function
This commit is contained in:
chilingling 2023-11-29 23:56:13 -08:00 committed by GitHub
parent 3ae47c811b
commit 8ed61ca311
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 333 additions and 19 deletions

View File

@ -1,11 +1,15 @@
<template> <template>
<div draggable="true" class="drag-item" @dragstart="dragstart"> <div draggable="true" class="drag-item" @dragstart="dragstart" @click="handleClick">
<slot></slot> <slot></slot>
</div> </div>
</template> </template>
<script> <script>
import { utils } from '@opentiny/tiny-engine-utils'
import { dragStart } from './container' import { dragStart } from './container'
const { deepClone } = utils
export default { export default {
props: { props: {
data: Object data: Object
@ -13,9 +17,8 @@ export default {
emits: ['click'], emits: ['click'],
setup(props, { emit }) { setup(props, { emit }) {
const dragstart = (e) => { const dragstart = (e) => {
if (props.data && e.button === 0) { if (props.data) {
const data = JSON.parse(JSON.stringify(props.data)) const data = deepClone(props.data)
emit('click', data)
dragStart(data) dragStart(data)
// //
@ -24,8 +27,18 @@ export default {
e.dataTransfer.setDragImage(target, 10, 10) e.dataTransfer.setDragImage(target, 10, 10)
} }
} }
const handleClick = () => {
if (props.data) {
const data = deepClone(props.data)
emit('click', data)
}
}
return { return {
dragstart dragstart,
handleClick
} }
} }
} }

View File

@ -11,7 +11,8 @@
"dist" "dist"
], ],
"scripts": { "scripts": {
"build": "vite build" "build": "vite build",
"test": "vitest"
}, },
"keywords": [], "keywords": [],
"repository": { "repository": {
@ -26,9 +27,11 @@
"license": "MIT", "license": "MIT",
"homepage": "https://opentiny.design/tiny-engine", "homepage": "https://opentiny.design/tiny-engine",
"devDependencies": { "devDependencies": {
"vite": "^4.3.7" "vite": "^4.3.7",
"vitest": "^0.34.6"
}, },
"dependencies": { "dependencies": {
"@opentiny/vue-renderless": "~3.10.0" "@opentiny/vue-renderless": "~3.10.0",
"vue": "^3.3.8"
} }
} }

View File

@ -1,15 +1,16 @@
/** /**
* Copyright (c) 2023 - present TinyEngine Authors. * Copyright (c) 2023 - present TinyEngine Authors.
* Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd. * Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd.
* *
* Use of this source code is governed by an MIT-style license. * Use of this source code is governed by an MIT-style license.
* *
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
* *
*/ */
import { isRef, isProxy, unref, toRaw } from 'vue'
import { isObject, isArray } from '@opentiny/vue-renderless/grid/static' import { isObject, isArray } from '@opentiny/vue-renderless/grid/static'
export const fun_ctor = Function export const fun_ctor = Function
@ -177,3 +178,149 @@ export function generateRandomLetters(length = 1) {
} }
return result return result
} }
export function getRawValue(target) {
let res = target
if (isRef(res)) {
res = unref(res)
}
if (isProxy(res)) {
return toRaw(res)
}
return res
}
export function getType(val) {
let type = typeof val
if (type !== 'object') {
return type
}
return Object.prototype.toString.call(val).replace(/\[object (.*)\]/, '$1')
}
function cloneArray(target, map, _deepClone) {
let res = []
map.set(target, res)
target.forEach((item, index) => {
res[index] = _deepClone(item, map)
})
return res
}
function cloneMap(target, map, _deepClone) {
let res = new Map()
map.set(target, res)
target.forEach((value, key) => {
res.set(key, _deepClone(value, map))
})
return res
}
function cloneSet(target, map, _deepClone) {
let res = new Set()
map.set(target, res)
target.forEach((value) => {
res.add(_deepClone(value, map))
})
return res
}
function cloneObject(target, map, _deepClone) {
const res = {}
map.set(target, res)
Object.entries(target).forEach(([key, value]) => {
res[key] = _deepClone(value, map)
})
return res
}
export function naiveDeepClone(target) {
try {
return structuredClone(target)
} catch (error) {
// target is no serializable
}
}
/**
* 使用 JSON.stringify 进行 deep clone
* 不支持 MapSetDateRegExpArrayBuffer 等变量类型
* 不支持循环引用
* @param {*} target target to be copy
* @param {*} callback target copyed
*/
export function jsonDeepClone(target, callback) {
try {
JSON.parse(JSON.stringify(target))
} catch (error) {
if (typeof callback === 'function') {
callback()
}
}
}
const copyMethodMap = {
Array: cloneArray,
Map: cloneMap,
Set: cloneSet,
Object: cloneObject
}
function _deepClone(target, map) {
if (map.has(target)) {
return map.get(target)
}
const copyTarget = getRawValue(target)
const basicType = ['undefined', 'number', 'string', 'boolean', 'function', 'bigint', 'symbol', 'Null']
let type = getType(copyTarget)
if (basicType.includes(type)) {
return target
}
let res = naiveDeepClone(copyTarget)
if (res) {
map.set(target, res)
return res
}
if (copyMethodMap[type]) {
res = copyMethodMap[type](target, map, _deepClone)
return res
}
return copyTarget
}
/**
* 优先使用 structuredClone 的深拷贝方法
* 不支持 拷贝 prototypefunctionDOM nodesproxy(gettersetter)
* 如果是 vue ref 或者 reactive会尝试拿到 raw value 然后进行深拷贝
* @param {*} target value to be deep clone
* @returns * deepCloned target
*/
export function deepClone(target) {
return _deepClone(target, new WeakMap())
}

View File

@ -0,0 +1,151 @@
import { ref, reactive } from 'vue'
import { expect, test } from 'vitest'
import { deepClone } from '../src/utils'
test('should be clone primitive value', () => {
expect(deepClone(null)).toBe(null)
expect(deepClone(undefined)).toBe(undefined)
expect(deepClone('a')).toBe('a')
expect(deepClone(1)).toBe(1)
expect(deepClone(false)).toBe(false)
expect(deepClone(1n)).toBe(1n)
})
test('should be clone simple object', () => {
const map = new Map()
map.set('a', 1)
map.set('b', [1, { b: 2 }])
const set = new Set()
set.add(1)
set.add('2')
const obj = {
a: 1,
b: '2',
c: true,
d: false,
e: 0,
f: undefined,
g: null,
arr: ['a', '', 0, false, { c: 'ccc' }],
arrLike: { 0: 'a', length: 1 },
reg: /word/gim,
date: new Date(),
map,
set
}
const newObj = deepClone(obj)
expect(newObj).toStrictEqual(obj)
expect(newObj !== obj).toBe(true)
expect(newObj.arr[4] !== obj.arr[4]).toBe(true)
})
test('should be clone map', () => {
const map = new Map()
map.set('a', 1)
map.set('b', [1, { b: 2 }])
const newMap = deepClone(map)
expect(map).toStrictEqual(newMap)
expect(newMap.get('a')).toBe(1)
expect(newMap.get('b') !== map.get('b')).toBe(true)
expect(newMap.get('b')).toStrictEqual(map.get('b'))
})
test('should be clone set', () => {
const set = new Set()
set.add(1)
set.add('2')
const newSet = deepClone(set)
expect(newSet.size).toBe(2)
expect(newSet.has(1)).toBe(true)
expect(newSet.has('2')).toBe(true)
})
test('should be clone with circular reference', () => {
const obj = {
foo: { b: { c: { d: {} } } },
bar: {}
}
obj.foo.b.c.d = obj
obj.bar.b = obj.foo.b
const newObj = deepClone(obj)
expect(newObj.bar.b === newObj.foo.b && newObj === newObj.foo.b.c.d && newObj !== obj).toBe(true)
})
test('should be clone with `index` and `input` array properties', () => {
const arr = /c/.exec('abcde')
const clonedArr = deepClone(arr)
expect(clonedArr.index).toBe(2)
expect(clonedArr.input).toBe('abcde')
})
test('should be clone with ref value', () => {
const map = new Map()
map.set('a', 1)
map.set('b', [1, { b: 2 }])
const set = new Set()
set.add(1)
set.add('2')
const obj = {
a: 1,
b: '2',
c: true,
d: false,
e: 0,
f: undefined,
g: null,
arr: ['a', '', 0, false, { c: 'ccc' }],
arrLike: { 0: 'a', length: 1 },
reg: /word/gim,
date: new Date(),
map,
set
}
const objRef = ref(obj)
const newObj = deepClone(objRef)
expect(newObj).toStrictEqual(obj)
expect(newObj !== obj && newObj !== objRef.value).toBe(true)
expect(newObj.arr[4] !== objRef.value.arr[4] && newObj.arr[4].c === objRef.value.arr[4].c).toBe(true)
})
test('should be clone with reactive with inner refs', () => {
const origin = {
a: 'a',
b: 2,
c: 'c',
d: ['aaa', 'ad']
}
const obj = reactive({
a: 'a',
b: 2,
c: ref('c'),
d: ['aaa', ref('ad')]
})
const newObj = deepClone(obj)
expect(newObj).toStrictEqual(newObj)
expect(newObj.d !== origin.d).toBe(true)
})

View File

@ -26,7 +26,7 @@ export default defineConfig({
formats: ['es'] formats: ['es']
}, },
rollupOptions: { rollupOptions: {
external: [/@opentiny\/tiny-engine.*/, /@opentiny\/vue.*/] external: ['vue', /@opentiny\/tiny-engine.*/, /@opentiny\/vue.*/]
} }
} }
}) })