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>
|
<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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
* 不支持 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']
|
formats: ['es']
|
||||||
},
|
},
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
external: [/@opentiny\/tiny-engine.*/, /@opentiny\/vue.*/]
|
external: ['vue', /@opentiny\/tiny-engine.*/, /@opentiny\/vue.*/]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue