@logicflow/vue-node-registry 1.2.0-alpha.7 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -3
- package/es/registry.d.ts +14 -3
- package/es/registry.js +28 -5
- package/es/registry.js.map +1 -1
- package/es/view.js +18 -2
- package/es/view.js.map +1 -1
- package/lib/registry.d.ts +14 -3
- package/lib/registry.js +30 -6
- package/lib/registry.js.map +1 -1
- package/lib/view.js +17 -1
- package/lib/view.js.map +1 -1
- package/package.json +10 -5
- package/.turbo/turbo-build$colon$dev.log +0 -10
- package/.turbo/turbo-build.log +0 -34
- package/CHANGELOG.md +0 -272
- package/rollup.config.js +0 -52
- package/src/assets/arrow.svg +0 -10
- package/src/components/container.ts +0 -36
- package/src/components/titleBar.ts +0 -189
- package/src/index.less +0 -1
- package/src/index.ts +0 -4
- package/src/model.ts +0 -141
- package/src/registry.ts +0 -43
- package/src/style/index.less +0 -140
- package/src/style/raw.ts +0 -129
- package/src/teleport.ts +0 -155
- package/src/utils/size.ts +0 -22
- package/src/view.ts +0 -238
- package/stats.html +0 -4842
- package/tsconfig.json +0 -20
package/src/registry.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import LogicFlow from '@logicflow/core'
|
|
2
|
-
import { VueNodeView } from './view'
|
|
3
|
-
import { VueNodeModel } from './model'
|
|
4
|
-
|
|
5
|
-
import RegisterConfig = LogicFlow.RegisterConfig
|
|
6
|
-
|
|
7
|
-
export type VueNodeConfig = {
|
|
8
|
-
type: string
|
|
9
|
-
component: any
|
|
10
|
-
effect?: (keyof LogicFlow.PropertiesType)[]
|
|
11
|
-
} & Partial<RegisterConfig>
|
|
12
|
-
|
|
13
|
-
export const vueNodesMap: Record<
|
|
14
|
-
string,
|
|
15
|
-
{
|
|
16
|
-
component: any
|
|
17
|
-
effect?: (keyof LogicFlow.PropertiesType)[]
|
|
18
|
-
}
|
|
19
|
-
> = {}
|
|
20
|
-
|
|
21
|
-
export function register(config: VueNodeConfig, lf: LogicFlow) {
|
|
22
|
-
const {
|
|
23
|
-
type,
|
|
24
|
-
component,
|
|
25
|
-
effect,
|
|
26
|
-
view: CustomNodeView,
|
|
27
|
-
model: CustomNodeModel,
|
|
28
|
-
} = config
|
|
29
|
-
|
|
30
|
-
if (!type) {
|
|
31
|
-
throw new Error('You should specify type in config')
|
|
32
|
-
}
|
|
33
|
-
vueNodesMap[type] = {
|
|
34
|
-
component,
|
|
35
|
-
effect,
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
lf.register({
|
|
39
|
-
type,
|
|
40
|
-
view: CustomNodeView || VueNodeView,
|
|
41
|
-
model: CustomNodeModel || VueNodeModel,
|
|
42
|
-
})
|
|
43
|
-
}
|
package/src/style/index.less
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
.lf-vue-node-container {
|
|
2
|
-
position: relative;
|
|
3
|
-
display: flex;
|
|
4
|
-
flex-direction: column;
|
|
5
|
-
box-sizing: border-box;
|
|
6
|
-
padding: 6px;
|
|
7
|
-
color: #474747;
|
|
8
|
-
border-radius: 12px;
|
|
9
|
-
box-shadow: 0 0 10px #cad2e15f;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
.lf-vue-node-content-wrap {
|
|
13
|
-
display: flex;
|
|
14
|
-
flex: 1 1 auto;
|
|
15
|
-
justify-content: center;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
.lf-vue-node-title {
|
|
19
|
-
display: flex;
|
|
20
|
-
align-items: flex-start;
|
|
21
|
-
justify-content: space-between;
|
|
22
|
-
box-sizing: border-box;
|
|
23
|
-
margin-bottom: 4px;
|
|
24
|
-
padding: 0 8px;
|
|
25
|
-
backdrop-filter: saturate(180%) blur(4px);
|
|
26
|
-
|
|
27
|
-
&-expanded {
|
|
28
|
-
margin-bottom: 6px;
|
|
29
|
-
padding-bottom: 8px;
|
|
30
|
-
border-bottom: 1px solid #eaeaea;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
@supports not (backdrop-filter: blur(1px)) {
|
|
35
|
-
.lf-vue-node-title {
|
|
36
|
-
backdrop-filter: none;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
.lf-vue-node-title-left {
|
|
41
|
-
display: flex;
|
|
42
|
-
gap: 6px;
|
|
43
|
-
align-items: center;
|
|
44
|
-
min-width: 0;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
.lf-vue-node-title-icon {
|
|
48
|
-
display: inline-block;
|
|
49
|
-
width: 16px;
|
|
50
|
-
height: 16px;
|
|
51
|
-
color: #666;
|
|
52
|
-
font-style: normal;
|
|
53
|
-
line-height: 16px;
|
|
54
|
-
text-align: center;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
.lf-vue-node-title-text {
|
|
58
|
-
overflow: hidden;
|
|
59
|
-
color: #333;
|
|
60
|
-
font-weight: 500;
|
|
61
|
-
font-size: 14px;
|
|
62
|
-
white-space: nowrap;
|
|
63
|
-
text-overflow: ellipsis;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
.lf-vue-node-title-actions {
|
|
67
|
-
display: flex;
|
|
68
|
-
gap: 6px;
|
|
69
|
-
align-items: center;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
.lf-vue-node-title-expand,
|
|
73
|
-
.lf-vue-node-title-more {
|
|
74
|
-
display: inline-flex;
|
|
75
|
-
align-items: center;
|
|
76
|
-
justify-content: center;
|
|
77
|
-
width: 20px;
|
|
78
|
-
height: 20px;
|
|
79
|
-
padding: 2px;
|
|
80
|
-
background: transparent;
|
|
81
|
-
border: none;
|
|
82
|
-
border-radius: 4px;
|
|
83
|
-
cursor: pointer;
|
|
84
|
-
transition: background 0.15s ease;
|
|
85
|
-
appearance: none;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
.lf-vue-node-title-expand:hover,
|
|
89
|
-
.lf-vue-node-title-more:hover {
|
|
90
|
-
background: rgb(0 0 0 / 6%);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
.lf-vue-node-title-expand-icon {
|
|
94
|
-
color: #666;
|
|
95
|
-
font-style: normal;
|
|
96
|
-
transition: transform 0.3s ease;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
.lf-vue-node-title-more-icon {
|
|
100
|
-
color: #666;
|
|
101
|
-
font-style: normal;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
.lf-vue-node-title-tooltip {
|
|
105
|
-
position: absolute;
|
|
106
|
-
top: -50px;
|
|
107
|
-
right: -135px;
|
|
108
|
-
min-width: 120px;
|
|
109
|
-
max-width: 240px;
|
|
110
|
-
padding: 6px 8px;
|
|
111
|
-
background: #fff;
|
|
112
|
-
border: 1px solid rgb(0 0 0 / 10%);
|
|
113
|
-
border-radius: 6px;
|
|
114
|
-
box-shadow: 0 6px 20px rgb(0 0 0 / 12%);
|
|
115
|
-
transform: translateY(calc(100% + 4px));
|
|
116
|
-
transition:
|
|
117
|
-
opacity 0.15s ease,
|
|
118
|
-
transform 0.15s ease;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
.lf-vue-node-title-tooltip-list {
|
|
122
|
-
display: flex;
|
|
123
|
-
flex-direction: column;
|
|
124
|
-
gap: 4px;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
.lf-vue-node-title-tooltip-item {
|
|
128
|
-
display: flex;
|
|
129
|
-
align-items: center;
|
|
130
|
-
justify-content: flex-start;
|
|
131
|
-
padding: 6px;
|
|
132
|
-
color: #333;
|
|
133
|
-
font-size: 12px;
|
|
134
|
-
border-radius: 4px;
|
|
135
|
-
cursor: pointer;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
.lf-vue-node-title-tooltip-item:hover {
|
|
139
|
-
background: rgb(0 0 0 / 5%);
|
|
140
|
-
}
|
package/src/style/raw.ts
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
/* eslint-disable */
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Auto generated file, do not modify it!
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export const content = `.lf-vue-node-container {
|
|
8
|
-
position: relative;
|
|
9
|
-
display: flex;
|
|
10
|
-
flex-direction: column;
|
|
11
|
-
box-sizing: border-box;
|
|
12
|
-
padding: 6px;
|
|
13
|
-
color: #474747;
|
|
14
|
-
border-radius: 12px;
|
|
15
|
-
box-shadow: 0 0 10px #cad2e15f;
|
|
16
|
-
}
|
|
17
|
-
.lf-vue-node-content-wrap {
|
|
18
|
-
display: flex;
|
|
19
|
-
flex: 1 1 auto;
|
|
20
|
-
justify-content: center;
|
|
21
|
-
}
|
|
22
|
-
.lf-vue-node-title {
|
|
23
|
-
display: flex;
|
|
24
|
-
align-items: flex-start;
|
|
25
|
-
justify-content: space-between;
|
|
26
|
-
box-sizing: border-box;
|
|
27
|
-
margin-bottom: 4px;
|
|
28
|
-
padding: 0 8px;
|
|
29
|
-
backdrop-filter: saturate(180%) blur(4px);
|
|
30
|
-
}
|
|
31
|
-
.lf-vue-node-title-expanded {
|
|
32
|
-
margin-bottom: 6px;
|
|
33
|
-
padding-bottom: 8px;
|
|
34
|
-
border-bottom: 1px solid #eaeaea;
|
|
35
|
-
}
|
|
36
|
-
@supports not (backdrop-filter: blur(1px)) {
|
|
37
|
-
.lf-vue-node-title {
|
|
38
|
-
backdrop-filter: none;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
.lf-vue-node-title-left {
|
|
42
|
-
display: flex;
|
|
43
|
-
gap: 6px;
|
|
44
|
-
align-items: center;
|
|
45
|
-
min-width: 0;
|
|
46
|
-
}
|
|
47
|
-
.lf-vue-node-title-icon {
|
|
48
|
-
display: inline-block;
|
|
49
|
-
width: 16px;
|
|
50
|
-
height: 16px;
|
|
51
|
-
color: #666;
|
|
52
|
-
font-style: normal;
|
|
53
|
-
line-height: 16px;
|
|
54
|
-
text-align: center;
|
|
55
|
-
}
|
|
56
|
-
.lf-vue-node-title-text {
|
|
57
|
-
overflow: hidden;
|
|
58
|
-
color: #333;
|
|
59
|
-
font-weight: 500;
|
|
60
|
-
font-size: 14px;
|
|
61
|
-
white-space: nowrap;
|
|
62
|
-
text-overflow: ellipsis;
|
|
63
|
-
}
|
|
64
|
-
.lf-vue-node-title-actions {
|
|
65
|
-
display: flex;
|
|
66
|
-
gap: 6px;
|
|
67
|
-
align-items: center;
|
|
68
|
-
}
|
|
69
|
-
.lf-vue-node-title-expand,
|
|
70
|
-
.lf-vue-node-title-more {
|
|
71
|
-
display: inline-flex;
|
|
72
|
-
align-items: center;
|
|
73
|
-
justify-content: center;
|
|
74
|
-
width: 20px;
|
|
75
|
-
height: 20px;
|
|
76
|
-
padding: 2px;
|
|
77
|
-
background: transparent;
|
|
78
|
-
border: none;
|
|
79
|
-
border-radius: 4px;
|
|
80
|
-
cursor: pointer;
|
|
81
|
-
transition: background 0.15s ease;
|
|
82
|
-
appearance: none;
|
|
83
|
-
}
|
|
84
|
-
.lf-vue-node-title-expand:hover,
|
|
85
|
-
.lf-vue-node-title-more:hover {
|
|
86
|
-
background: rgba(0, 0, 0, 0.06);
|
|
87
|
-
}
|
|
88
|
-
.lf-vue-node-title-expand-icon {
|
|
89
|
-
color: #666;
|
|
90
|
-
font-style: normal;
|
|
91
|
-
transition: transform 0.3s ease;
|
|
92
|
-
}
|
|
93
|
-
.lf-vue-node-title-more-icon {
|
|
94
|
-
color: #666;
|
|
95
|
-
font-style: normal;
|
|
96
|
-
}
|
|
97
|
-
.lf-vue-node-title-tooltip {
|
|
98
|
-
position: absolute;
|
|
99
|
-
top: -50px;
|
|
100
|
-
right: -135px;
|
|
101
|
-
min-width: 120px;
|
|
102
|
-
max-width: 240px;
|
|
103
|
-
padding: 6px 8px;
|
|
104
|
-
background: #fff;
|
|
105
|
-
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
106
|
-
border-radius: 6px;
|
|
107
|
-
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.12);
|
|
108
|
-
transform: translateY(calc(100% + 4px));
|
|
109
|
-
transition: opacity 0.15s ease, transform 0.15s ease;
|
|
110
|
-
}
|
|
111
|
-
.lf-vue-node-title-tooltip-list {
|
|
112
|
-
display: flex;
|
|
113
|
-
flex-direction: column;
|
|
114
|
-
gap: 4px;
|
|
115
|
-
}
|
|
116
|
-
.lf-vue-node-title-tooltip-item {
|
|
117
|
-
display: flex;
|
|
118
|
-
align-items: center;
|
|
119
|
-
justify-content: flex-start;
|
|
120
|
-
padding: 6px;
|
|
121
|
-
color: #333;
|
|
122
|
-
font-size: 12px;
|
|
123
|
-
border-radius: 4px;
|
|
124
|
-
cursor: pointer;
|
|
125
|
-
}
|
|
126
|
-
.lf-vue-node-title-tooltip-item:hover {
|
|
127
|
-
background: rgba(0, 0, 0, 0.05);
|
|
128
|
-
}
|
|
129
|
-
`
|
package/src/teleport.ts
DELETED
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
import { BaseNodeModel, GraphModel } from '@logicflow/core'
|
|
2
|
-
import {
|
|
3
|
-
defineComponent,
|
|
4
|
-
h,
|
|
5
|
-
createApp,
|
|
6
|
-
reactive,
|
|
7
|
-
isVue3,
|
|
8
|
-
Teleport,
|
|
9
|
-
markRaw,
|
|
10
|
-
Fragment,
|
|
11
|
-
} from 'vue-demi'
|
|
12
|
-
|
|
13
|
-
let active = false
|
|
14
|
-
const appInstances = new Map<string, InstanceType<any>>()
|
|
15
|
-
const appNodesMap = new Map<string, any>() // 用于储存当前流程图节点id当节点都销毁时同时卸载vueApp实例
|
|
16
|
-
const items = reactive<{ [key: string]: any }>({})
|
|
17
|
-
|
|
18
|
-
export function connect(
|
|
19
|
-
id: string,
|
|
20
|
-
component: any,
|
|
21
|
-
container: HTMLDivElement,
|
|
22
|
-
node: BaseNodeModel,
|
|
23
|
-
graph: GraphModel,
|
|
24
|
-
) {
|
|
25
|
-
if (active) {
|
|
26
|
-
if (graph.isMiniMap) {
|
|
27
|
-
createTeleportContainer(container, graph.flowId)
|
|
28
|
-
}
|
|
29
|
-
items[id] = markRaw(
|
|
30
|
-
defineComponent({
|
|
31
|
-
render: () =>
|
|
32
|
-
h(Teleport, { to: container } as any, [
|
|
33
|
-
h(component, { node, graph }),
|
|
34
|
-
]),
|
|
35
|
-
provide: () => ({
|
|
36
|
-
getNode: () => node,
|
|
37
|
-
getGraph: () => graph,
|
|
38
|
-
}),
|
|
39
|
-
}),
|
|
40
|
-
)
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function disconnect(id: string, flowId: string) {
|
|
45
|
-
if (active) {
|
|
46
|
-
delete items[id]
|
|
47
|
-
if (appInstances.has(flowId)) {
|
|
48
|
-
const appNodeList = appNodesMap.get(flowId) || []
|
|
49
|
-
const index = appNodeList.indexOf(id)
|
|
50
|
-
if (index > -1) {
|
|
51
|
-
appNodeList.splice(index, 1)
|
|
52
|
-
if (appNodeList.length === 0) {
|
|
53
|
-
destroyTeleportContainer(flowId)
|
|
54
|
-
} else {
|
|
55
|
-
appNodesMap.set(flowId, appNodeList)
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function isActive() {
|
|
63
|
-
return active
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function getTeleport(): any {
|
|
67
|
-
if (!isVue3) {
|
|
68
|
-
throw new Error('teleport is only available in Vue3')
|
|
69
|
-
}
|
|
70
|
-
active = true
|
|
71
|
-
|
|
72
|
-
return defineComponent({
|
|
73
|
-
props: {
|
|
74
|
-
flowId: {
|
|
75
|
-
type: String,
|
|
76
|
-
required: true,
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
setup(props) {
|
|
80
|
-
return () => {
|
|
81
|
-
const children: Record<string, any>[] = []
|
|
82
|
-
Object.keys(items).forEach((id) => {
|
|
83
|
-
// https://github.com/didi/LogicFlow/issues/1768
|
|
84
|
-
// 多个不同的VueNodeView都会connect注册到items中,因此items存储了可能有多个flowId流程图的数据
|
|
85
|
-
// 当使用多个LogicFlow时,会创建多个flowId + 同时使用KeepAlive
|
|
86
|
-
// 每一次items改变,会触发不同flowId持有的setup()执行,由于每次setup()执行就是遍历items,因此存在多次重复渲染元素的问题
|
|
87
|
-
// 即items[0]会在Page1的setup()执行,items[0]也会在Page2的setup()执行,从而生成两个items[0]
|
|
88
|
-
|
|
89
|
-
// 比对当前界面显示的flowId,只更新items[当前页面flowId:nodeId]的数据
|
|
90
|
-
// 比如items[0]属于Page1的数据,那么Page2无论active=true/false,都无法执行items[0]
|
|
91
|
-
|
|
92
|
-
if (id.startsWith(props.flowId)) {
|
|
93
|
-
if (appInstances.has(props.flowId)) {
|
|
94
|
-
const appNodeList = appNodesMap.get(props.flowId) || []
|
|
95
|
-
if (!appNodeList.includes(id)) {
|
|
96
|
-
appNodeList.push(id)
|
|
97
|
-
appNodesMap.set(props.flowId, appNodeList)
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
children.push(items[id])
|
|
101
|
-
}
|
|
102
|
-
})
|
|
103
|
-
return h(
|
|
104
|
-
Fragment,
|
|
105
|
-
{},
|
|
106
|
-
children.map((item) => h(item)),
|
|
107
|
-
)
|
|
108
|
-
}
|
|
109
|
-
},
|
|
110
|
-
})
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* 创建并挂载 Teleport 容器组件
|
|
115
|
-
* @param container 目标容器元素
|
|
116
|
-
* @param flowId 当前流程图的唯一标识
|
|
117
|
-
*/
|
|
118
|
-
export function createTeleportContainer(
|
|
119
|
-
container: HTMLElement,
|
|
120
|
-
flowId: string | undefined,
|
|
121
|
-
): void {
|
|
122
|
-
if (!isVue3 || !flowId || !container || !active) return
|
|
123
|
-
|
|
124
|
-
// 获取 Teleport 组件
|
|
125
|
-
const TeleportContainer = getTeleport()
|
|
126
|
-
|
|
127
|
-
// 不重新创建 Teleport 容器组件
|
|
128
|
-
if (appInstances.has(flowId)) {
|
|
129
|
-
return
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// ✅ 1. 创建独立容器放到目标容器中
|
|
133
|
-
const mountPoint = document.createElement('div')
|
|
134
|
-
container.appendChild(mountPoint)
|
|
135
|
-
|
|
136
|
-
// ✅ 2. 创建并挂载 Vue 应用到新容器
|
|
137
|
-
const app = createApp(TeleportContainer, { flowId })
|
|
138
|
-
app.mount(mountPoint)
|
|
139
|
-
|
|
140
|
-
appInstances.set(flowId, app)
|
|
141
|
-
appNodesMap.set(flowId, [])
|
|
142
|
-
}
|
|
143
|
-
/**
|
|
144
|
-
* 卸载 Teleport 容器组件
|
|
145
|
-
* @param flowId 需要卸载流程图的唯一标识
|
|
146
|
-
*/
|
|
147
|
-
export function destroyTeleportContainer(flowId: string | undefined): void {
|
|
148
|
-
if (!isVue3 || !flowId || !active) return
|
|
149
|
-
const app = appInstances.get(flowId)
|
|
150
|
-
if (app) {
|
|
151
|
-
app.unmount()
|
|
152
|
-
appInstances.delete(flowId)
|
|
153
|
-
appNodesMap.delete(flowId)
|
|
154
|
-
}
|
|
155
|
-
}
|
package/src/utils/size.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
export function computeBaseHeight(
|
|
2
|
-
measuredHeight: number,
|
|
3
|
-
_showTitle?: boolean,
|
|
4
|
-
_titleHeight?: number,
|
|
5
|
-
) {
|
|
6
|
-
const extra = _showTitle
|
|
7
|
-
? typeof _titleHeight === 'number'
|
|
8
|
-
? _titleHeight
|
|
9
|
-
: 28
|
|
10
|
-
: 0
|
|
11
|
-
return Math.max(1, Math.round(measuredHeight) - extra)
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function shouldUpdateSize(
|
|
15
|
-
prevW?: number,
|
|
16
|
-
prevH?: number,
|
|
17
|
-
w?: number,
|
|
18
|
-
h?: number,
|
|
19
|
-
) {
|
|
20
|
-
if (!w || !h) return false
|
|
21
|
-
return !(prevW === Math.round(w) && prevH === Math.round(h))
|
|
22
|
-
}
|