-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
/
Copy pathSchemaTree.tsx
169 lines (147 loc) · 4.34 KB
/
SchemaTree.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
import React from 'react'
import { Tree, Icon } from 'antd'
import { ISchemaTreeProps } from '../utils/types'
import * as fp from 'lodash/fp'
import _ from 'lodash'
const TreeNode = Tree.TreeNode
export const SchemaTree: React.FC<ISchemaTreeProps> = ({
schema,
onChange,
onSelect
}) => {
const handleSelect = React.useCallback((path: string[]) => {
onSelect(path[0])
}, [])
const handleDrop = React.useCallback(
(info: any) => {
const sourcePath = info.dragNode.props.eventKey
const sourceKeys = sourcePath.split('.')
const sourceKey = sourceKeys[sourceKeys.length - 1]
const targetPath = info.node.props.eventKey
const sourceValue = fp.get(sourcePath, schema)
const targetValue =
targetPath === 'root' ? schema : fp.get(targetPath, schema)
if (!targetValue) {
return
}
if (info.dropToGap) {
// 拖拽到这个元素的同级
// info.dropPosition -1 表示上方同级,1 表示下方同级
} else {
// 拖拽到这个元素内部
if (targetValue.type === 'object') {
// 拖入到 object
if (
(targetPath === 'root' && sourcePath.split('.').length === 2) ||
(targetPath !== 'root' &&
fp.dropRight(2, sourcePath.split('.')).join('.') === targetPath)
) {
// 拖拽到直接父节点,等于不起作用
return
}
let newSchema = schema
// 增加新的 key
const newTargetValue = fp.set(
[
'properties',
getUniqueKeyFromObjectKeys(
sourceKey,
Object.keys(targetValue.properties || {})
)
],
sourceValue,
targetValue
)
newSchema =
targetPath === 'root'
? newTargetValue
: fp.set(targetPath, newTargetValue, newSchema)
// 删除旧的 key
newSchema = fp.unset(sourcePath, newSchema)
onChange(newSchema)
} else if (
targetValue.type === 'array' &&
(fp.get(['items'], targetValue) || []).length === 0
) {
// 拖入到 array
// array 只能拖入一个元素,因此 A 的子节点 B 无法再次拖入 A
let newSchema = schema
// 增加新的 key
const newTargetValue = fp.set(['items'], sourceValue, targetValue)
newSchema = fp.set(targetPath, newTargetValue, newSchema)
// 删除旧的 key
newSchema = fp.unset(sourcePath, newSchema)
onChange(newSchema)
}
}
},
[schema, onChange]
)
return (
<Tree
defaultExpandAll
showIcon
showLine
draggable
onSelect={handleSelect}
onDrop={handleDrop}
>
{TreeNodeBySchema({ schema, path: [] })}
</Tree>
)
}
const TreeNodeBySchema: React.FC<{
schema: any
path: string[]
}> = ({ schema, path }) => {
if (!schema) {
return null
}
const currentTreeLevelProps = {
title: path.length === 0 ? 'root' : path[path.length - 1],
key: path.length === 0 ? 'root' : path.join('.')
}
switch (schema.type) {
case 'object':
return (
<TreeNode icon={<Icon type="folder" />} {...currentTreeLevelProps}>
{schema.properties &&
Object.keys(schema.properties).map(key =>
TreeNodeBySchema({
schema: schema.properties[key],
path: path.concat('properties', key)
})
)}
</TreeNode>
)
case 'array':
return (
<TreeNode
icon={<Icon type="deployment-unit" />}
{...currentTreeLevelProps}
>
{schema.items &&
TreeNodeBySchema({
schema: schema.items,
path: path.concat('items')
})}
</TreeNode>
)
default:
}
return <TreeNode icon={<Icon type="file" />} {...currentTreeLevelProps} />
}
function getUniqueKeyFromObjectKeys(key: string, keys: string[], count = -1) {
if (count === -1) {
if (keys.includes(key)) {
return getUniqueKeyFromObjectKeys(key, keys, 0)
}
return key
}
const newKey = key + count
if (keys.includes(newKey)) {
return getUniqueKeyFromObjectKeys(key, keys, count + 1)
} else {
return newKey
}
}