@tak-ps/vue-tabler 4.1.1 → 4.2.0
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/CHANGELOG.md +4 -0
- package/components/None.vue +4 -3
- package/components/SchemaBuilder.vue +297 -0
- package/components/SchemaBuilderEdit.vue +154 -0
- package/lib.ts +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/components/None.vue
CHANGED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class='card'>
|
|
3
|
+
<div class='card-header'>
|
|
4
|
+
<h3
|
|
5
|
+
class='card-title'
|
|
6
|
+
v-text='title'
|
|
7
|
+
/>
|
|
8
|
+
|
|
9
|
+
<div class='ms-auto btn-list'>
|
|
10
|
+
<div class='dropdown'>
|
|
11
|
+
<div
|
|
12
|
+
id='dropdownMenuButton1'
|
|
13
|
+
class='dropdown-toggle'
|
|
14
|
+
type='button'
|
|
15
|
+
data-bs-toggle='dropdown'
|
|
16
|
+
aria-expanded='false'
|
|
17
|
+
>
|
|
18
|
+
<IconPlus
|
|
19
|
+
:size='32'
|
|
20
|
+
stroke='1'
|
|
21
|
+
class='cursor-pointer'
|
|
22
|
+
/>
|
|
23
|
+
</div>
|
|
24
|
+
<ul
|
|
25
|
+
class='dropdown-menu'
|
|
26
|
+
aria-labelledby='dropdownMenuButton1'
|
|
27
|
+
>
|
|
28
|
+
<div class='m-1 text-center'>
|
|
29
|
+
<div
|
|
30
|
+
class='opt cursor-pointer py-1'
|
|
31
|
+
@click='create({
|
|
32
|
+
"name": "",
|
|
33
|
+
"type": "string",
|
|
34
|
+
"required": false,
|
|
35
|
+
})'
|
|
36
|
+
>
|
|
37
|
+
String
|
|
38
|
+
</div>
|
|
39
|
+
<div
|
|
40
|
+
class='opt cursor-pointer py-1'
|
|
41
|
+
@click='create({
|
|
42
|
+
"name": "",
|
|
43
|
+
"type": "string",
|
|
44
|
+
"enum": [],
|
|
45
|
+
"required": false,
|
|
46
|
+
})'
|
|
47
|
+
>
|
|
48
|
+
Enum
|
|
49
|
+
</div>
|
|
50
|
+
<div
|
|
51
|
+
class='opt cursor-pointer py-1'
|
|
52
|
+
@click='create({
|
|
53
|
+
"name": "",
|
|
54
|
+
"type": "boolean",
|
|
55
|
+
"required": false,
|
|
56
|
+
})'
|
|
57
|
+
>
|
|
58
|
+
Boolean
|
|
59
|
+
</div>
|
|
60
|
+
<div
|
|
61
|
+
class='opt cursor-pointer py-1'
|
|
62
|
+
@click='create({
|
|
63
|
+
"name": "",
|
|
64
|
+
"type": "number",
|
|
65
|
+
"required": false,
|
|
66
|
+
})'
|
|
67
|
+
>
|
|
68
|
+
Number
|
|
69
|
+
</div>
|
|
70
|
+
<div
|
|
71
|
+
class='opt cursor-pointer py-1'
|
|
72
|
+
@click='create({
|
|
73
|
+
"name": "",
|
|
74
|
+
"type": "integer",
|
|
75
|
+
"required": false,
|
|
76
|
+
})'
|
|
77
|
+
>
|
|
78
|
+
Integer
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</ul>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
<div class='card-body'>
|
|
86
|
+
<TablerNone
|
|
87
|
+
v-if='!schema.length'
|
|
88
|
+
label='No Properties'
|
|
89
|
+
:create='false'
|
|
90
|
+
:compact='true'
|
|
91
|
+
/>
|
|
92
|
+
<template v-else>
|
|
93
|
+
<div
|
|
94
|
+
v-for='(prop, i) in schema'
|
|
95
|
+
:key='prop._id'
|
|
96
|
+
class='col-12 hover-edit cursor-pointer relative'
|
|
97
|
+
@click='editIdx = i; modal = prop'
|
|
98
|
+
>
|
|
99
|
+
<div class='delete-btn'>
|
|
100
|
+
<TablerDelete
|
|
101
|
+
displaytype='icon'
|
|
102
|
+
:size='24'
|
|
103
|
+
@delete='remove(i)'
|
|
104
|
+
/>
|
|
105
|
+
</div>
|
|
106
|
+
<div class='overlay' />
|
|
107
|
+
<TablerEnum
|
|
108
|
+
v-if='prop.enum'
|
|
109
|
+
:model-value='input[prop.name]'
|
|
110
|
+
:label='prop.name'
|
|
111
|
+
:disabled='true'
|
|
112
|
+
:required='prop.required || false'
|
|
113
|
+
:description='prop.description || ""'
|
|
114
|
+
:options='prop.enum'
|
|
115
|
+
/>
|
|
116
|
+
<TablerToggle
|
|
117
|
+
v-else-if='prop.type === "boolean"'
|
|
118
|
+
:model-value='input[prop.name]'
|
|
119
|
+
:label='prop.name'
|
|
120
|
+
:disabled='true'
|
|
121
|
+
:required='prop.required || false'
|
|
122
|
+
:description='prop.description || ""'
|
|
123
|
+
/>
|
|
124
|
+
<TablerInput
|
|
125
|
+
v-else
|
|
126
|
+
:model-value='input[prop.name]'
|
|
127
|
+
:label='prop.name'
|
|
128
|
+
:disabled='true'
|
|
129
|
+
:required='prop.required || false'
|
|
130
|
+
:description='prop.description || ""'
|
|
131
|
+
/>
|
|
132
|
+
</div>
|
|
133
|
+
</template>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
<BuilderEdit
|
|
137
|
+
v-if='modal'
|
|
138
|
+
:prop='modal'
|
|
139
|
+
@close='modal = null'
|
|
140
|
+
@done='save($event)'
|
|
141
|
+
/>
|
|
142
|
+
</div>
|
|
143
|
+
</template>
|
|
144
|
+
|
|
145
|
+
<script setup lang="ts">
|
|
146
|
+
import { ref, computed, onMounted, watch } from 'vue';
|
|
147
|
+
import {
|
|
148
|
+
IconPlus,
|
|
149
|
+
} from '@tabler/icons-vue';
|
|
150
|
+
import BuilderEdit from './SchemaBuilderEdit.vue';
|
|
151
|
+
import {
|
|
152
|
+
TablerNone,
|
|
153
|
+
TablerInput,
|
|
154
|
+
TablerEnum,
|
|
155
|
+
TablerToggle,
|
|
156
|
+
TablerDelete
|
|
157
|
+
} from '@tak-ps/vue-tabler';
|
|
158
|
+
|
|
159
|
+
interface SchemaProperty {
|
|
160
|
+
name: string;
|
|
161
|
+
type: string;
|
|
162
|
+
required?: boolean;
|
|
163
|
+
description?: string;
|
|
164
|
+
enum?: string[];
|
|
165
|
+
_id?: string;
|
|
166
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
167
|
+
[key: string]: any;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
interface JSONSchema {
|
|
171
|
+
type: string;
|
|
172
|
+
required: string[];
|
|
173
|
+
additionalProperties: boolean;
|
|
174
|
+
properties: Record<string, SchemaProperty>;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const props = withDefaults(defineProps<{
|
|
178
|
+
title?: string;
|
|
179
|
+
modelValue: {
|
|
180
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
181
|
+
properties: Record<string, any>;
|
|
182
|
+
required?: string[];
|
|
183
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
184
|
+
[key: string]: any;
|
|
185
|
+
};
|
|
186
|
+
}>(), {
|
|
187
|
+
title: 'JSON Schema Builder'
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const emit = defineEmits<{
|
|
191
|
+
(e: 'update:modelValue', value: JSONSchema): void;
|
|
192
|
+
}>();
|
|
193
|
+
|
|
194
|
+
const modal = ref<SchemaProperty | null>(null);
|
|
195
|
+
const editIdx = ref<number | null>(null);
|
|
196
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
197
|
+
const input = ref<Record<string, any>>({});
|
|
198
|
+
const schema = ref<SchemaProperty[]>([]);
|
|
199
|
+
|
|
200
|
+
const computedSchema = computed<JSONSchema>(() => {
|
|
201
|
+
const res: JSONSchema = {
|
|
202
|
+
type: 'object',
|
|
203
|
+
required: [],
|
|
204
|
+
additionalProperties: false,
|
|
205
|
+
properties: {}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
for (const prop of JSON.parse(JSON.stringify(schema.value))) {
|
|
209
|
+
const name = prop.name;
|
|
210
|
+
delete prop.name;
|
|
211
|
+
delete prop._id;
|
|
212
|
+
|
|
213
|
+
if (prop.required) res.required.push(name);
|
|
214
|
+
delete prop.required;
|
|
215
|
+
res.properties[name] = prop;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return res;
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
watch(computedSchema, (val) => {
|
|
222
|
+
emit('update:modelValue', val);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
onMounted(() => {
|
|
226
|
+
for (const prop in props.modelValue.properties) {
|
|
227
|
+
schema.value.push({
|
|
228
|
+
name: prop,
|
|
229
|
+
required: (props.modelValue.required || []).includes(prop),
|
|
230
|
+
...props.modelValue.properties[prop],
|
|
231
|
+
_id: Math.random().toString(36).substring(7)
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
function create(prop: SchemaProperty) {
|
|
237
|
+
prop._id = Math.random().toString(36).substring(7);
|
|
238
|
+
editIdx.value = null;
|
|
239
|
+
modal.value = prop;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function save(prop: SchemaProperty) {
|
|
243
|
+
if (editIdx.value !== null) {
|
|
244
|
+
schema.value[editIdx.value] = prop;
|
|
245
|
+
} else {
|
|
246
|
+
schema.value.push(prop);
|
|
247
|
+
}
|
|
248
|
+
modal.value = null;
|
|
249
|
+
editIdx.value = null;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function remove(i: number) {
|
|
253
|
+
schema.value.splice(i, 1);
|
|
254
|
+
}
|
|
255
|
+
</script>
|
|
256
|
+
|
|
257
|
+
<style>
|
|
258
|
+
.opt:hover {
|
|
259
|
+
font-weight: 900;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.hover-edit {
|
|
263
|
+
position: relative;
|
|
264
|
+
border: 1px solid transparent;
|
|
265
|
+
border-radius: 4px;
|
|
266
|
+
transition: all 0.2s;
|
|
267
|
+
padding: 12px;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.hover-edit:hover {
|
|
271
|
+
background-color: rgba(0, 0, 0, 0.02);
|
|
272
|
+
border-color: #f0f0f0;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.hover-edit .delete-btn {
|
|
276
|
+
opacity: 0;
|
|
277
|
+
transition: opacity 0.2s;
|
|
278
|
+
position: absolute;
|
|
279
|
+
top: 5px;
|
|
280
|
+
right: 5px;
|
|
281
|
+
z-index: 20;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.hover-edit:hover .delete-btn {
|
|
285
|
+
opacity: 1;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.hover-edit .overlay {
|
|
289
|
+
position: absolute;
|
|
290
|
+
top: 0;
|
|
291
|
+
left: 0;
|
|
292
|
+
width: 100%;
|
|
293
|
+
height: 100%;
|
|
294
|
+
z-index: 10;
|
|
295
|
+
cursor: pointer;
|
|
296
|
+
}
|
|
297
|
+
</style>
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<TablerModal>
|
|
3
|
+
<button
|
|
4
|
+
type='button'
|
|
5
|
+
class='btn-close'
|
|
6
|
+
aria-label='Close'
|
|
7
|
+
@click='$emit("close")'
|
|
8
|
+
/>
|
|
9
|
+
<div class='modal-status bg-yellow' />
|
|
10
|
+
<div class='modal-header'>
|
|
11
|
+
<div class='d-flex align-items-center my-2'>
|
|
12
|
+
<template v-if='edit.type === "string"'>
|
|
13
|
+
<IconAlphabetLatin
|
|
14
|
+
:size='32'
|
|
15
|
+
stroke='1'
|
|
16
|
+
/>
|
|
17
|
+
</template>
|
|
18
|
+
<template v-else-if='edit.type === "number"'>
|
|
19
|
+
<DecimalIcon
|
|
20
|
+
:size='32'
|
|
21
|
+
stroke='1'
|
|
22
|
+
/>
|
|
23
|
+
</template>
|
|
24
|
+
<template v-else-if='edit.type === "integer"'>
|
|
25
|
+
<Sort09Icon
|
|
26
|
+
:size='32'
|
|
27
|
+
stroke='1'
|
|
28
|
+
/>
|
|
29
|
+
</template>
|
|
30
|
+
<template v-else>
|
|
31
|
+
<BinaryIcon
|
|
32
|
+
:size='32'
|
|
33
|
+
stroke='1'
|
|
34
|
+
/>
|
|
35
|
+
</template>
|
|
36
|
+
<span
|
|
37
|
+
class='my-1 mx-2 strong'
|
|
38
|
+
v-text='edit.type'
|
|
39
|
+
/>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
<div class='modal-body text-center py-4'>
|
|
43
|
+
<div class='row g-2 d-flex'>
|
|
44
|
+
<TablerInput
|
|
45
|
+
v-model='edit.name'
|
|
46
|
+
label='Field Name'
|
|
47
|
+
:required='true'
|
|
48
|
+
/>
|
|
49
|
+
<TablerToggle
|
|
50
|
+
v-model='edit.required'
|
|
51
|
+
label='Required'
|
|
52
|
+
/>
|
|
53
|
+
<TablerInput
|
|
54
|
+
v-model='edit.description'
|
|
55
|
+
:rows='3'
|
|
56
|
+
label='Description'
|
|
57
|
+
/>
|
|
58
|
+
|
|
59
|
+
<template v-if='edit.type === "string" && edit.enum === undefined' />
|
|
60
|
+
<template v-else-if='edit.type === "string" && Array.isArray(edit.enum)'>
|
|
61
|
+
<div class='subheader mt-2'>
|
|
62
|
+
Enum Values
|
|
63
|
+
</div>
|
|
64
|
+
<div class='col-12 d-flex'>
|
|
65
|
+
<TablerInput
|
|
66
|
+
v-model='newEnum'
|
|
67
|
+
class='flex-grow-1'
|
|
68
|
+
placeholder='New Value'
|
|
69
|
+
@keyup.enter='addEnum'
|
|
70
|
+
/>
|
|
71
|
+
<button
|
|
72
|
+
class='btn btn-icon ms-2'
|
|
73
|
+
@click='addEnum'
|
|
74
|
+
>
|
|
75
|
+
<IconPlus
|
|
76
|
+
:size='20'
|
|
77
|
+
stroke='1'
|
|
78
|
+
/>
|
|
79
|
+
</button>
|
|
80
|
+
</div>
|
|
81
|
+
<div class='list-group list-group-flush mt-2'>
|
|
82
|
+
<div
|
|
83
|
+
v-for='(val, i) in edit.enum'
|
|
84
|
+
:key='i'
|
|
85
|
+
class='list-group-item d-flex justify-content-between align-items-center py-1'
|
|
86
|
+
>
|
|
87
|
+
<span v-text='val' />
|
|
88
|
+
<IconTrash
|
|
89
|
+
:size='16'
|
|
90
|
+
stroke='1'
|
|
91
|
+
class='cursor-pointer text-danger'
|
|
92
|
+
@click='edit.enum?.splice(i, 1)'
|
|
93
|
+
/>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
</template>
|
|
97
|
+
<template v-else-if='edit.type === "number"' />
|
|
98
|
+
<template v-else-if='edit.type === "integer"' />
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
<div class='modal-footer'>
|
|
102
|
+
<button
|
|
103
|
+
class='btn btn-primary'
|
|
104
|
+
:disabled='!edit.name.length'
|
|
105
|
+
@click='$emit("done", edit)'
|
|
106
|
+
>
|
|
107
|
+
Save
|
|
108
|
+
</button>
|
|
109
|
+
</div>
|
|
110
|
+
</TablerModal>
|
|
111
|
+
</template>
|
|
112
|
+
|
|
113
|
+
<script setup lang="ts">
|
|
114
|
+
import { ref } from 'vue';
|
|
115
|
+
import {
|
|
116
|
+
IconAlphabetLatin,
|
|
117
|
+
IconPlus,
|
|
118
|
+
IconTrash
|
|
119
|
+
} from '@tabler/icons-vue';
|
|
120
|
+
import {
|
|
121
|
+
TablerModal,
|
|
122
|
+
TablerInput,
|
|
123
|
+
TablerToggle
|
|
124
|
+
} from '@tak-ps/vue-tabler';
|
|
125
|
+
|
|
126
|
+
interface SchemaProperty {
|
|
127
|
+
name: string;
|
|
128
|
+
type: string;
|
|
129
|
+
required?: boolean;
|
|
130
|
+
description?: string;
|
|
131
|
+
enum?: string[];
|
|
132
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
133
|
+
[key: string]: any;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const props = defineProps<{
|
|
137
|
+
prop: SchemaProperty
|
|
138
|
+
}>();
|
|
139
|
+
|
|
140
|
+
defineEmits<{
|
|
141
|
+
(e: 'close'): void;
|
|
142
|
+
(e: 'done', value: SchemaProperty): void;
|
|
143
|
+
}>();
|
|
144
|
+
|
|
145
|
+
const edit = ref<SchemaProperty>(JSON.parse(JSON.stringify(props.prop)));
|
|
146
|
+
const newEnum = ref('');
|
|
147
|
+
|
|
148
|
+
function addEnum() {
|
|
149
|
+
if (!newEnum.value.trim()) return;
|
|
150
|
+
if (!edit.value.enum) edit.value.enum = [];
|
|
151
|
+
edit.value.enum.push(newEnum.value.trim());
|
|
152
|
+
newEnum.value = '';
|
|
153
|
+
}
|
|
154
|
+
</script>
|
package/lib.ts
CHANGED
|
@@ -30,3 +30,4 @@ export { default as TablerEpochRange } from './components/EpochRange.vue';
|
|
|
30
30
|
export { default as TablerMarkdown } from './components/Markdown.vue';
|
|
31
31
|
export { default as TablerDelete } from './components/Delete.vue';
|
|
32
32
|
export { default as TablerSchema } from './components/Schema.vue';
|
|
33
|
+
export { default as TablerSchemaBuilder } from './components/SchemaBuilder.vue';
|