forked from opentiny/tiny-engine
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:
parent
3ae47c811b
commit
8ed61ca311
|
@ -1,11 +1,15 @@
|
|||
<template>
|
||||
<div draggable="true" class="drag-item" @dragstart="dragstart">
|
||||
<div draggable="true" class="drag-item" @dragstart="dragstart" @click="handleClick">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { utils } from '@opentiny/tiny-engine-utils'
|
||||
import { dragStart } from './container'
|
||||
|
||||
const { deepClone } = utils
|
||||
|
||||
export default {
|
||||
props: {
|
||||
data: Object
|
||||
|
@ -13,9 +17,8 @@ export default {
|
|||
emits: ['click'],
|
||||
setup(props, { emit }) {
|
||||
const dragstart = (e) => {
|
||||
if (props.data && e.button === 0) {
|
||||
const data = JSON.parse(JSON.stringify(props.data))
|
||||
emit('click', data)
|
||||
if (props.data) {
|
||||
const data = deepClone(props.data)
|
||||
dragStart(data)
|
||||
|
||||
// 设置拖拽鼠标样式和设置拖拽预览图
|
||||
|
@ -24,8 +27,18 @@ export default {
|
|||
e.dataTransfer.setDragImage(target, 10, 10)
|
||||
}
|
||||
}
|
||||
|
||||
const handleClick = () => {
|
||||
if (props.data) {
|
||||
const data = deepClone(props.data)
|
||||
|
||||
emit('click', data)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
dragstart
|
||||
dragstart,
|
||||
handleClick
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,8 @@
|
|||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "vite build"
|
||||
"build": "vite build",
|
||||
"test": "vitest"
|
||||
},
|
||||
"keywords": [],
|
||||
"repository": {
|
||||
|
@ -26,9 +27,11 @@
|
|||
"license": "MIT",
|
||||
"homepage": "https://opentiny.design/tiny-engine",
|
||||
"devDependencies": {
|
||||
"vite": "^4.3.7"
|
||||
"vite": "^4.3.7",
|
||||
"vitest": "^0.34.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@opentiny/vue-renderless": "~3.10.0"
|
||||
"@opentiny/vue-renderless": "~3.10.0",
|
||||
"vue": "^3.3.8"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
/**
|
||||
* Copyright (c) 2023 - present TinyEngine Authors.
|
||||
* Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||
*
|
||||
* 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,
|
||||
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
|
||||
*
|
||||
*/
|
||||
* Copyright (c) 2023 - present TinyEngine Authors.
|
||||
* Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||
*
|
||||
* 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,
|
||||
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
||||
* 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'
|
||||
|
||||
export const fun_ctor = Function
|
||||
|
@ -177,3 +178,149 @@ export function generateRandomLetters(length = 1) {
|
|||
}
|
||||
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
|
||||
* 不支持 Map、Set、Date、RegExp、ArrayBuffer 等变量类型
|
||||
* 不支持循环引用
|
||||
* @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 的深拷贝方法
|
||||
* 不支持 拷贝 prototype、function、DOM nodes、proxy(getter、setter)
|
||||
* 如果是 vue 的 ref 或者 reactive,会尝试拿到 raw value 然后进行深拷贝
|
||||
* @param {*} target value to be deep clone
|
||||
* @returns * deepCloned target
|
||||
*/
|
||||
export function deepClone(target) {
|
||||
return _deepClone(target, new WeakMap())
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
})
|
|
@ -26,7 +26,7 @@ export default defineConfig({
|
|||
formats: ['es']
|
||||
},
|
||||
rollupOptions: {
|
||||
external: [/@opentiny\/tiny-engine.*/, /@opentiny\/vue.*/]
|
||||
external: ['vue', /@opentiny\/tiny-engine.*/, /@opentiny\/vue.*/]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue