@conduction/nextcloud-vue 0.1.0-beta.4 → 0.1.0-beta.6
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/dist/nextcloud-vue.cjs +67614 -0
- package/dist/nextcloud-vue.cjs.js +9559 -8983
- package/dist/nextcloud-vue.cjs.js.map +1 -1
- package/dist/nextcloud-vue.cjs.map +1 -0
- package/dist/nextcloud-vue.css +1231 -1231
- package/dist/nextcloud-vue.esm.js +9559 -8983
- package/dist/nextcloud-vue.esm.js.map +1 -1
- package/package.json +14 -5
- package/src/components/CnActionsBar/CnActionsBar.vue +235 -235
- package/src/components/CnAdvancedFormDialog/CnAdvancedFormDialog.vue +579 -579
- package/src/components/CnAdvancedFormDialog/CnDataTab.vue +217 -217
- package/src/components/CnAdvancedFormDialog/CnMetadataTab.vue +121 -121
- package/src/components/CnAdvancedFormDialog/CnPropertiesTab.vue +418 -418
- package/src/components/CnAdvancedFormDialog/CnPropertyValueCell.vue +247 -247
- package/src/components/CnCardGrid/CnCardGrid.vue +152 -152
- package/src/components/CnCellRenderer/CnCellRenderer.vue +132 -132
- package/src/components/CnChartWidget/CnChartWidget.vue +320 -320
- package/src/components/CnConfigurationCard/CnConfigurationCard.vue +77 -77
- package/src/components/CnCopyDialog/CnCopyDialog.vue +250 -250
- package/src/components/CnDashboardGrid/CnDashboardGrid.vue +225 -225
- package/src/components/CnDashboardPage/CnDashboardPage.vue +390 -390
- package/src/components/CnDataTable/CnDataTable.vue +349 -349
- package/src/components/CnDeleteDialog/CnDeleteDialog.vue +170 -170
- package/src/components/CnDetailCard/CnDetailCard.vue +214 -214
- package/src/components/CnDetailPage/CnDetailPage.vue +285 -281
- package/src/components/CnFacetSidebar/CnFacetSidebar.vue +231 -231
- package/src/components/CnFilterBar/CnFilterBar.vue +152 -152
- package/src/components/CnFormDialog/CnFormDialog.vue +302 -11
- package/src/components/CnIcon/CnIcon.vue +89 -89
- package/src/components/CnIndexPage/CnIndexPage.vue +884 -874
- package/src/components/CnIndexSidebar/CnIndexSidebar.vue +503 -503
- package/src/components/CnItemCard/CnItemCard.vue +132 -132
- package/src/components/CnKpiGrid/CnKpiGrid.vue +89 -89
- package/src/components/CnMassActionBar/CnMassActionBar.vue +160 -160
- package/src/components/CnMassCopyDialog/CnMassCopyDialog.vue +320 -320
- package/src/components/CnMassDeleteDialog/CnMassDeleteDialog.vue +238 -238
- package/src/components/CnMassExportDialog/CnMassExportDialog.vue +190 -190
- package/src/components/CnMassImportDialog/CnMassImportDialog.vue +491 -491
- package/src/components/CnNoteCard/CnNoteCard.vue +149 -149
- package/src/components/CnNotesCard/CnNotesCard.vue +413 -413
- package/src/components/CnObjectCard/CnObjectCard.vue +292 -292
- package/src/components/CnObjectCard/eslint-setup.md +235 -0
- package/src/components/CnObjectCard/package.json-or.json +132 -0
- package/src/components/CnObjectSidebar/CnObjectSidebar.vue +876 -876
- package/src/components/CnPageHeader/CnPageHeader.vue +57 -57
- package/src/components/CnPagination/CnPagination.vue +252 -252
- package/src/components/CnRegisterMapping/CnRegisterMapping.vue +792 -792
- package/src/components/CnRowActions/CnRowActions.vue +95 -73
- package/src/components/CnSchemaFormDialog/CnSchemaConfigurationTab.vue +226 -226
- package/src/components/CnSchemaFormDialog/CnSchemaFormDialog.vue +787 -787
- package/src/components/CnSchemaFormDialog/CnSchemaPropertiesTab.vue +305 -305
- package/src/components/CnSchemaFormDialog/CnSchemaPropertyActions.vue +1398 -1398
- package/src/components/CnSchemaFormDialog/CnSchemaSecurityTab.vue +236 -236
- package/src/components/CnSettingsCard/CnSettingsCard.vue +92 -92
- package/src/components/CnSettingsSection/CnSettingsSection.vue +266 -266
- package/src/components/CnStatsBlock/CnStatsBlock.vue +420 -420
- package/src/components/CnStatusBadge/CnStatusBadge.vue +77 -77
- package/src/components/CnTabbedFormDialog/CnTabbedFormDialog.vue +540 -540
- package/src/components/CnTasksCard/CnTasksCard.vue +373 -373
- package/src/components/CnTileWidget/CnTileWidget.vue +159 -159
- package/src/components/CnTimelineStages/CnTimelineStages.vue +292 -292
- package/src/components/CnUserActionMenu/CnUserActionMenu.vue +435 -435
- package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +312 -312
- package/src/components/CnWidgetRenderer/CnWidgetRenderer.vue +180 -180
- package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +211 -211
- package/src/index.js +1 -1
- package/src/types/notification.d.ts +13 -13
- package/src/types/organisation.d.ts +15 -15
- package/src/types/schema.d.ts +13 -13
- package/src/types/task.d.ts +6 -6
- package/src/utils/headers.js +5 -3
|
@@ -1,787 +1,787 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<CnTabbedFormDialog
|
|
3
|
-
ref="dialog"
|
|
4
|
-
:tabs="dialogTabs"
|
|
5
|
-
:item="item"
|
|
6
|
-
:dialog-title="dialogTitle"
|
|
7
|
-
entity-name="Schema"
|
|
8
|
-
:size="size"
|
|
9
|
-
:disable-save="!schemaItem.title"
|
|
10
|
-
:success-text="resolvedSuccessText"
|
|
11
|
-
:cancel-label="cancelLabel"
|
|
12
|
-
:close-label="closeLabel"
|
|
13
|
-
:confirm-label="confirmLabel"
|
|
14
|
-
@confirm="handleConfirm"
|
|
15
|
-
@close="$emit('close')">
|
|
16
|
-
<!-- Metadata Display -->
|
|
17
|
-
<template #above-tabs="{ loading: dialogLoading }">
|
|
18
|
-
<div class="cn-schema-form__detail-grid">
|
|
19
|
-
<div v-if="schemaItem.id"
|
|
20
|
-
class="cn-schema-form__detail-item cn-schema-form__id-card">
|
|
21
|
-
<div class="cn-schema-form__id-card-header">
|
|
22
|
-
<span class="cn-schema-form__detail-label">ID / UUID:</span>
|
|
23
|
-
<NcButton class="cn-schema-form__copy-button"
|
|
24
|
-
@click="copyToClipboard(schemaItem.uuid || schemaItem.id)">
|
|
25
|
-
<template #icon>
|
|
26
|
-
<Check v-if="isCopied" :size="20" />
|
|
27
|
-
<ContentCopy v-else :size="20" />
|
|
28
|
-
</template>
|
|
29
|
-
{{ isCopied ? 'Copied' : 'Copy' }}
|
|
30
|
-
</NcButton>
|
|
31
|
-
</div>
|
|
32
|
-
<span class="cn-schema-form__detail-value">{{ schemaItem.id }}</span>
|
|
33
|
-
<span v-if="schemaItem.uuid && schemaItem.uuid !== schemaItem.id"
|
|
34
|
-
class="cn-schema-form__detail-value cn-schema-form__uuid-value">{{ schemaItem.uuid }}</span>
|
|
35
|
-
</div>
|
|
36
|
-
<div class="cn-schema-form__detail-item cn-schema-form__title-with-badge">
|
|
37
|
-
<NcTextField :disabled="dialogLoading"
|
|
38
|
-
label="Title *"
|
|
39
|
-
:value.sync="schemaItem.title" />
|
|
40
|
-
<span v-if="schemaItem.allOf && schemaItem.allOf.length > 0"
|
|
41
|
-
class="cn-schema-form__statusPill cn-schema-form__statusPill--success">
|
|
42
|
-
allOf
|
|
43
|
-
</span>
|
|
44
|
-
<span v-if="schemaItem.oneOf && schemaItem.oneOf.length > 0"
|
|
45
|
-
class="cn-schema-form__statusPill cn-schema-form__statusPill--info">
|
|
46
|
-
oneOf
|
|
47
|
-
</span>
|
|
48
|
-
<span v-if="schemaItem.anyOf && schemaItem.anyOf.length > 0"
|
|
49
|
-
class="cn-schema-form__statusPill cn-schema-form__statusPill--info">
|
|
50
|
-
anyOf
|
|
51
|
-
</span>
|
|
52
|
-
</div>
|
|
53
|
-
<div v-if="schemaItem.created" class="cn-schema-form__detail-item">
|
|
54
|
-
<span class="cn-schema-form__detail-label">Created:</span>
|
|
55
|
-
<span class="cn-schema-form__detail-value">{{ new Date(schemaItem.created).toLocaleString() }}</span>
|
|
56
|
-
</div>
|
|
57
|
-
<div v-if="schemaItem.updated" class="cn-schema-form__detail-item">
|
|
58
|
-
<span class="cn-schema-form__detail-label">Updated:</span>
|
|
59
|
-
<span class="cn-schema-form__detail-value">{{ new Date(schemaItem.updated).toLocaleString() }}</span>
|
|
60
|
-
</div>
|
|
61
|
-
<div class="cn-schema-form__detail-item">
|
|
62
|
-
<span class="cn-schema-form__detail-label">Version:</span>
|
|
63
|
-
<span class="cn-schema-form__detail-value">{{ schemaItem.version || 'Not set' }}</span>
|
|
64
|
-
</div>
|
|
65
|
-
<div class="cn-schema-form__detail-item">
|
|
66
|
-
<span class="cn-schema-form__detail-label">Owner:</span>
|
|
67
|
-
<span class="cn-schema-form__detail-value">{{ schemaItem.owner || 'Not set' }}</span>
|
|
68
|
-
</div>
|
|
69
|
-
</div>
|
|
70
|
-
</template>
|
|
71
|
-
|
|
72
|
-
<!-- Properties Tab -->
|
|
73
|
-
<template #tab-properties="{ loading: dialogLoading }">
|
|
74
|
-
<CnSchemaPropertiesTab
|
|
75
|
-
:schema-item="schemaItem"
|
|
76
|
-
:loading="dialogLoading"
|
|
77
|
-
:selected-property="selectedProperty"
|
|
78
|
-
:properties-modified="propertiesModified"
|
|
79
|
-
:original-properties="originalProperties"
|
|
80
|
-
:type-options-for-select="typeOptionsForSelect"
|
|
81
|
-
:available-schemas="availableSchemas"
|
|
82
|
-
:available-registers="availableRegisters"
|
|
83
|
-
:available-tags-options="availableTagsOptions"
|
|
84
|
-
:user-groups="userGroups"
|
|
85
|
-
:sorted-user-groups="sortedUserGroups"
|
|
86
|
-
:loading-groups="loadingGroups"
|
|
87
|
-
@add-property="addProperty"
|
|
88
|
-
@update:selected-property="selectedProperty = $event"
|
|
89
|
-
@update:property-key="updatePropertyKey($event.oldKey, $event.newKey)"
|
|
90
|
-
@copy-property="copyProperty"
|
|
91
|
-
@delete-property="deleteProperty" />
|
|
92
|
-
</template>
|
|
93
|
-
|
|
94
|
-
<!-- Configuration Tab -->
|
|
95
|
-
<template #tab-configuration="{ loading: dialogLoading }">
|
|
96
|
-
<CnSchemaConfigurationTab
|
|
97
|
-
:schema-item="schemaItem"
|
|
98
|
-
:loading="dialogLoading"
|
|
99
|
-
:available-schemas="availableSchemas"
|
|
100
|
-
:property-options="propertyOptions"
|
|
101
|
-
:all-of-schema-names="allOfSchemaNames" />
|
|
102
|
-
</template>
|
|
103
|
-
|
|
104
|
-
<!-- Security Tab -->
|
|
105
|
-
<template #tab-security>
|
|
106
|
-
<CnSchemaSecurityTab
|
|
107
|
-
:schema-item="schemaItem"
|
|
108
|
-
:user-groups="userGroups"
|
|
109
|
-
:sorted-user-groups="sortedUserGroups"
|
|
110
|
-
:loading-groups="loadingGroups"
|
|
111
|
-
:has-any-permissions="hasAnyPermissions"
|
|
112
|
-
:is-restrictive-schema="isRestrictiveSchema" />
|
|
113
|
-
</template>
|
|
114
|
-
|
|
115
|
-
<!-- Optional Action Buttons (edit mode only) -->
|
|
116
|
-
<template #actions-right="{ loading: dialogLoading, isCreateMode, result: dialogResult }">
|
|
117
|
-
<template v-if="!isCreateMode && dialogResult === null">
|
|
118
|
-
<NcButton
|
|
119
|
-
v-if="showExtendSchema"
|
|
120
|
-
:disabled="dialogLoading"
|
|
121
|
-
@click="$emit('extend-schema')">
|
|
122
|
-
<template #icon>
|
|
123
|
-
<CallSplit :size="20" />
|
|
124
|
-
</template>
|
|
125
|
-
{{ extendSchemaLabel }}
|
|
126
|
-
</NcButton>
|
|
127
|
-
<NcButton
|
|
128
|
-
v-if="showAnalyzeProperties"
|
|
129
|
-
:disabled="dialogLoading"
|
|
130
|
-
@click="$emit('analyze-properties')">
|
|
131
|
-
<template #icon>
|
|
132
|
-
<DatabaseSearch :size="20" />
|
|
133
|
-
</template>
|
|
134
|
-
{{ analyzePropertiesLabel }}
|
|
135
|
-
</NcButton>
|
|
136
|
-
<NcButton
|
|
137
|
-
v-if="showValidateObjects"
|
|
138
|
-
:disabled="dialogLoading"
|
|
139
|
-
@click="$emit('validate-objects')">
|
|
140
|
-
<template #icon>
|
|
141
|
-
<CheckCircle :size="20" />
|
|
142
|
-
</template>
|
|
143
|
-
{{ validateObjectsLabel }}
|
|
144
|
-
</NcButton>
|
|
145
|
-
<NcButton
|
|
146
|
-
v-if="showDeleteObjects"
|
|
147
|
-
v-tooltip="objectCount > 0 ? deleteObjectsTooltip : noDeleteObjectsTooltip"
|
|
148
|
-
:disabled="dialogLoading || objectCount === 0"
|
|
149
|
-
@click="$emit('delete-objects')">
|
|
150
|
-
<template #icon>
|
|
151
|
-
<DeleteSweep :size="20" />
|
|
152
|
-
</template>
|
|
153
|
-
{{ deleteObjectsLabel }}
|
|
154
|
-
</NcButton>
|
|
155
|
-
<NcButton
|
|
156
|
-
v-if="showPublishObjects"
|
|
157
|
-
v-tooltip="objectCount > 0 ? publishObjectsTooltip : noPublishObjectsTooltip"
|
|
158
|
-
:disabled="dialogLoading || objectCount === 0"
|
|
159
|
-
@click="$emit('publish-objects')">
|
|
160
|
-
<template #icon>
|
|
161
|
-
<Upload :size="20" />
|
|
162
|
-
</template>
|
|
163
|
-
{{ publishObjectsLabel }}
|
|
164
|
-
</NcButton>
|
|
165
|
-
<NcButton
|
|
166
|
-
v-if="showDelete"
|
|
167
|
-
v-tooltip="objectCount > 0 ? cannotDeleteTooltip : ''"
|
|
168
|
-
:disabled="dialogLoading || objectCount > 0"
|
|
169
|
-
type="error"
|
|
170
|
-
@click="$emit('delete-schema')">
|
|
171
|
-
<template #icon>
|
|
172
|
-
<TrashCanOutline :size="20" />
|
|
173
|
-
</template>
|
|
174
|
-
{{ deleteLabel }}
|
|
175
|
-
</NcButton>
|
|
176
|
-
</template>
|
|
177
|
-
</template>
|
|
178
|
-
</CnTabbedFormDialog>
|
|
179
|
-
</template>
|
|
180
|
-
|
|
181
|
-
<script>
|
|
182
|
-
import {
|
|
183
|
-
NcButton,
|
|
184
|
-
NcTextField,
|
|
185
|
-
} from '@nextcloud/vue'
|
|
186
|
-
|
|
187
|
-
import CnTabbedFormDialog from '../CnTabbedFormDialog/CnTabbedFormDialog.vue'
|
|
188
|
-
import CnSchemaPropertiesTab from './CnSchemaPropertiesTab.vue'
|
|
189
|
-
import CnSchemaConfigurationTab from './CnSchemaConfigurationTab.vue'
|
|
190
|
-
import CnSchemaSecurityTab from './CnSchemaSecurityTab.vue'
|
|
191
|
-
|
|
192
|
-
import ContentCopy from 'vue-material-design-icons/ContentCopy.vue'
|
|
193
|
-
import Check from 'vue-material-design-icons/Check.vue'
|
|
194
|
-
import TrashCanOutline from 'vue-material-design-icons/TrashCanOutline.vue'
|
|
195
|
-
import CallSplit from 'vue-material-design-icons/CallSplit.vue'
|
|
196
|
-
import DatabaseSearch from 'vue-material-design-icons/DatabaseSearch.vue'
|
|
197
|
-
import CheckCircle from 'vue-material-design-icons/CheckCircle.vue'
|
|
198
|
-
import DeleteSweep from 'vue-material-design-icons/DeleteSweep.vue'
|
|
199
|
-
import Upload from 'vue-material-design-icons/Upload.vue'
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* CnSchemaFormDialog — Generic JSON Schema editor dialog.
|
|
203
|
-
*
|
|
204
|
-
* Provides a full-featured form for creating and editing JSON Schemas with
|
|
205
|
-
* properties table, configuration, and security (RBAC) tabs. Uses CnTabbedFormDialog.
|
|
206
|
-
*
|
|
207
|
-
* The dialog does NOT perform saves — it emits a `confirm` event with the schema data.
|
|
208
|
-
* The parent performs the actual API call and calls `setResult()` via a ref.
|
|
209
|
-
*
|
|
210
|
-
* @event confirm Emitted when the user confirms. Payload: cleaned schema data object.
|
|
211
|
-
* @event close Emitted when the dialog should be closed.
|
|
212
|
-
* @event extend-schema Emitted when the Extend Schema button is clicked.
|
|
213
|
-
* @event analyze-properties Emitted when the Analyze Properties button is clicked.
|
|
214
|
-
* @event validate-objects Emitted when the Validate Objects button is clicked.
|
|
215
|
-
* @event delete-objects Emitted when the Delete Objects button is clicked.
|
|
216
|
-
* @event publish-objects Emitted when the Publish Objects button is clicked.
|
|
217
|
-
* @event delete-schema Emitted when the Delete button is clicked.
|
|
218
|
-
*/
|
|
219
|
-
export default {
|
|
220
|
-
name: 'CnSchemaFormDialog',
|
|
221
|
-
components: {
|
|
222
|
-
NcTextField,
|
|
223
|
-
NcButton,
|
|
224
|
-
CnTabbedFormDialog,
|
|
225
|
-
CnSchemaPropertiesTab,
|
|
226
|
-
CnSchemaConfigurationTab,
|
|
227
|
-
CnSchemaSecurityTab,
|
|
228
|
-
// Icons
|
|
229
|
-
ContentCopy,
|
|
230
|
-
Check,
|
|
231
|
-
TrashCanOutline,
|
|
232
|
-
CallSplit,
|
|
233
|
-
DatabaseSearch,
|
|
234
|
-
CheckCircle,
|
|
235
|
-
DeleteSweep,
|
|
236
|
-
Upload,
|
|
237
|
-
},
|
|
238
|
-
props: {
|
|
239
|
-
/** Existing schema item for edit mode. Pass null for create mode. */
|
|
240
|
-
item: { type: Object, default: null },
|
|
241
|
-
/** Dialog title. Defaults to "Create Schema" or "Edit Schema". */
|
|
242
|
-
dialogTitle: { type: String, default: '' },
|
|
243
|
-
/** NcDialog size */
|
|
244
|
-
size: { type: String, default: 'large' },
|
|
245
|
-
/** Available schemas for references and composition. Array of { id, title, description, reference } */
|
|
246
|
-
availableSchemas: { type: Array, default: () => [] },
|
|
247
|
-
/** Available registers. Array of { id, label } */
|
|
248
|
-
availableRegisters: { type: Array, default: () => [] },
|
|
249
|
-
/** User groups for RBAC. Array of { id, displayname } */
|
|
250
|
-
userGroups: { type: Array, default: () => [] },
|
|
251
|
-
/** Available tags for file property configuration */
|
|
252
|
-
availableTags: { type: Array, default: () => [] },
|
|
253
|
-
/** Whether user groups are still loading */
|
|
254
|
-
loadingGroups: { type: Boolean, default: false },
|
|
255
|
-
/** Number of objects attached to this schema (used for action button disable logic) */
|
|
256
|
-
objectCount: { type: Number, default: 0 },
|
|
257
|
-
// Optional action button visibility
|
|
258
|
-
/** Show "Extend Schema" button */
|
|
259
|
-
showExtendSchema: { type: Boolean, default: false },
|
|
260
|
-
/** Show "Analyze Properties" button */
|
|
261
|
-
showAnalyzeProperties: { type: Boolean, default: false },
|
|
262
|
-
/** Show "Validate Objects" button */
|
|
263
|
-
showValidateObjects: { type: Boolean, default: false },
|
|
264
|
-
/** Show "Delete Objects" button */
|
|
265
|
-
showDeleteObjects: { type: Boolean, default: false },
|
|
266
|
-
/** Show "Publish Objects" button */
|
|
267
|
-
showPublishObjects: { type: Boolean, default: false },
|
|
268
|
-
/** Show "Delete" button */
|
|
269
|
-
showDelete: { type: Boolean, default: false },
|
|
270
|
-
// Labels (pre-translated strings with English defaults)
|
|
271
|
-
cancelLabel: { type: String, default: 'Cancel' },
|
|
272
|
-
closeLabel: { type: String, default: 'Close' },
|
|
273
|
-
/** Confirm button label. Defaults to "Create" or "Save". */
|
|
274
|
-
confirmLabel: { type: String, default: '' },
|
|
275
|
-
/** Success message. Defaults to "Schema saved successfully." */
|
|
276
|
-
successText: { type: String, default: '' },
|
|
277
|
-
extendSchemaLabel: { type: String, default: 'Extend Schema' },
|
|
278
|
-
analyzePropertiesLabel: { type: String, default: 'Analyze Properties' },
|
|
279
|
-
validateObjectsLabel: { type: String, default: 'Validate Objects' },
|
|
280
|
-
deleteObjectsLabel: { type: String, default: 'Delete Objects' },
|
|
281
|
-
publishObjectsLabel: { type: String, default: 'Publish Objects' },
|
|
282
|
-
deleteLabel: { type: String, default: 'Delete' },
|
|
283
|
-
deleteObjectsTooltip: { type: String, default: 'Delete all objects in this schema' },
|
|
284
|
-
publishObjectsTooltip: { type: String, default: 'Publish all objects in this schema' },
|
|
285
|
-
/** Tooltip for the Delete Objects button when no objects exist */
|
|
286
|
-
noDeleteObjectsTooltip: { type: String, default: 'No objects to delete' },
|
|
287
|
-
/** Tooltip for the Publish Objects button when no objects exist */
|
|
288
|
-
noPublishObjectsTooltip: { type: String, default: 'No objects to publish' },
|
|
289
|
-
cannotDeleteTooltip: { type: String, default: 'Cannot delete: objects are still attached' },
|
|
290
|
-
},
|
|
291
|
-
data() {
|
|
292
|
-
return {
|
|
293
|
-
isCopied: false,
|
|
294
|
-
selectedProperty: null,
|
|
295
|
-
propertiesModified: false,
|
|
296
|
-
originalProperties: null,
|
|
297
|
-
schemaItem: {
|
|
298
|
-
title: '',
|
|
299
|
-
version: '0.0.0',
|
|
300
|
-
description: '',
|
|
301
|
-
summary: '',
|
|
302
|
-
slug: '',
|
|
303
|
-
properties: {},
|
|
304
|
-
configuration: {
|
|
305
|
-
objectNameField: '',
|
|
306
|
-
objectDescriptionField: '',
|
|
307
|
-
objectImageField: '',
|
|
308
|
-
objectSummaryField: '',
|
|
309
|
-
allowFiles: false,
|
|
310
|
-
allowedTags: [],
|
|
311
|
-
autoPublish: false,
|
|
312
|
-
},
|
|
313
|
-
authorization: {},
|
|
314
|
-
hardValidation: false,
|
|
315
|
-
immutable: false,
|
|
316
|
-
searchable: true,
|
|
317
|
-
maxDepth: 0,
|
|
318
|
-
},
|
|
319
|
-
}
|
|
320
|
-
},
|
|
321
|
-
computed: {
|
|
322
|
-
/**
|
|
323
|
-
* Tab definitions for CnTabbedFormDialog.
|
|
324
|
-
*
|
|
325
|
-
* @return {Array} Tab configuration
|
|
326
|
-
*/
|
|
327
|
-
dialogTabs() {
|
|
328
|
-
return [
|
|
329
|
-
{ id: 'properties', title: 'Properties' },
|
|
330
|
-
{ id: 'configuration', title: 'Configuration' },
|
|
331
|
-
{ id: 'security', title: 'Security' },
|
|
332
|
-
]
|
|
333
|
-
},
|
|
334
|
-
sortedUserGroups() {
|
|
335
|
-
return this.userGroups
|
|
336
|
-
.filter(group => group.id !== 'admin' && group.id !== 'public')
|
|
337
|
-
.sort((a, b) => {
|
|
338
|
-
const nameA = a.displayname || a.id
|
|
339
|
-
const nameB = b.displayname || b.id
|
|
340
|
-
return nameA.localeCompare(nameB)
|
|
341
|
-
})
|
|
342
|
-
},
|
|
343
|
-
hasAnyPermissions() {
|
|
344
|
-
const auth = this.schemaItem.authorization || {}
|
|
345
|
-
return Object.keys(auth).some(action =>
|
|
346
|
-
Array.isArray(auth[action]) && auth[action].length > 0,
|
|
347
|
-
)
|
|
348
|
-
},
|
|
349
|
-
isRestrictiveSchema() {
|
|
350
|
-
const auth = this.schemaItem.authorization || {}
|
|
351
|
-
const actions = ['create', 'read', 'update', 'delete']
|
|
352
|
-
return actions.some(action =>
|
|
353
|
-
Array.isArray(auth[action]) && auth[action].length > 0
|
|
354
|
-
&& !auth[action].includes('public'),
|
|
355
|
-
)
|
|
356
|
-
},
|
|
357
|
-
typeOptionsForSelect() {
|
|
358
|
-
return [
|
|
359
|
-
{ id: 'string', label: 'String' },
|
|
360
|
-
{ id: 'number', label: 'Number' },
|
|
361
|
-
{ id: 'integer', label: 'Integer' },
|
|
362
|
-
{ id: 'boolean', label: 'Boolean' },
|
|
363
|
-
{ id: 'array', label: 'Array' },
|
|
364
|
-
{ id: 'object', label: 'Object' },
|
|
365
|
-
{ id: 'dictionary', label: 'Dictionary' },
|
|
366
|
-
{ id: 'file', label: 'File' },
|
|
367
|
-
{ id: 'oneOf', label: 'One Of' },
|
|
368
|
-
]
|
|
369
|
-
},
|
|
370
|
-
propertyOptions() {
|
|
371
|
-
const properties = this.schemaItem.properties || {}
|
|
372
|
-
return ['', ...Object.keys(properties)]
|
|
373
|
-
},
|
|
374
|
-
availableTagsOptions() {
|
|
375
|
-
return this.availableTags.map(tag => ({
|
|
376
|
-
id: tag,
|
|
377
|
-
label: tag,
|
|
378
|
-
}))
|
|
379
|
-
},
|
|
380
|
-
/**
|
|
381
|
-
* Resolved success text for backwards compatibility (includes trailing period).
|
|
382
|
-
*
|
|
383
|
-
* @return {string}
|
|
384
|
-
*/
|
|
385
|
-
resolvedSuccessText() {
|
|
386
|
-
if (this.successText) return this.successText
|
|
387
|
-
return 'Schema saved successfully.'
|
|
388
|
-
},
|
|
389
|
-
allOfSchemaNames() {
|
|
390
|
-
if (!this.schemaItem.allOf || !Array.isArray(this.schemaItem.allOf) || this.schemaItem.allOf.length === 0) {
|
|
391
|
-
return []
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
return this.schemaItem.allOf
|
|
395
|
-
.map(ref => {
|
|
396
|
-
const schemaId = typeof ref === 'object' ? ref.id : ref
|
|
397
|
-
const schema = this.availableSchemas.find(s => s.id === schemaId)
|
|
398
|
-
return schema ? (schema.title || `Schema ${schema.id}`) : schemaId
|
|
399
|
-
})
|
|
400
|
-
.filter(name => name)
|
|
401
|
-
},
|
|
402
|
-
},
|
|
403
|
-
watch: {
|
|
404
|
-
item: {
|
|
405
|
-
immediate: true,
|
|
406
|
-
handler() {
|
|
407
|
-
this.initializeSchemaItem()
|
|
408
|
-
},
|
|
409
|
-
},
|
|
410
|
-
'schemaItem.properties': {
|
|
411
|
-
handler(newProperties) {
|
|
412
|
-
if (newProperties) {
|
|
413
|
-
Object.keys(newProperties).forEach(key => {
|
|
414
|
-
const property = newProperties[key]
|
|
415
|
-
if (property) {
|
|
416
|
-
// Initialize nested objects if they don't exist
|
|
417
|
-
if (property.type === 'array' && !property.items) {
|
|
418
|
-
this.$set(this.schemaItem.properties[key], 'items', { type: 'string' })
|
|
419
|
-
}
|
|
420
|
-
if (property.type === 'object' && !property.objectConfiguration) {
|
|
421
|
-
this.$set(this.schemaItem.properties[key], 'objectConfiguration', { handling: 'nested-object' })
|
|
422
|
-
}
|
|
423
|
-
if (property.type === 'array' && property.items && property.items.type === 'object' && !property.items.objectConfiguration) {
|
|
424
|
-
this.$set(this.schemaItem.properties[key].items, 'objectConfiguration', { handling: 'nested-object' })
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
// Convert property type from object to string
|
|
428
|
-
if (property.type && typeof property.type === 'object' && property.type.id) {
|
|
429
|
-
this.$set(this.schemaItem.properties[key], 'type', property.type.id)
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
// Convert property format from object to string
|
|
433
|
-
if (property.format && typeof property.format === 'object' && property.format.id) {
|
|
434
|
-
this.$set(this.schemaItem.properties[key], 'format', property.format.id)
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
// Convert array item type from object to string
|
|
438
|
-
if (property.items && property.items.type && typeof property.items.type === 'object' && property.items.type.id) {
|
|
439
|
-
this.$set(this.schemaItem.properties[key].items, 'type', property.items.type.id)
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
// Convert object handling from object to string
|
|
443
|
-
if (property.objectConfiguration && property.objectConfiguration.handling
|
|
444
|
-
&& typeof property.objectConfiguration.handling === 'object' && property.objectConfiguration.handling.id) {
|
|
445
|
-
this.$set(this.schemaItem.properties[key].objectConfiguration, 'handling', property.objectConfiguration.handling.id)
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
// Convert register from object to ID
|
|
449
|
-
if (property.objectConfiguration && property.objectConfiguration.register
|
|
450
|
-
&& typeof property.objectConfiguration.register === 'object' && property.objectConfiguration.register.id) {
|
|
451
|
-
this.$set(this.schemaItem.properties[key].objectConfiguration, 'register', property.objectConfiguration.register.id)
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
// Convert array item object handling from object to string
|
|
455
|
-
if (property.items && property.items.objectConfiguration && property.items.objectConfiguration.handling
|
|
456
|
-
&& typeof property.items.objectConfiguration.handling === 'object' && property.items.objectConfiguration.handling.id) {
|
|
457
|
-
this.$set(this.schemaItem.properties[key].items.objectConfiguration, 'handling', property.items.objectConfiguration.handling.id)
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
// Convert array item register from object to ID
|
|
461
|
-
if (property.items && property.items.objectConfiguration && property.items.objectConfiguration.register
|
|
462
|
-
&& typeof property.items.objectConfiguration.register === 'object' && property.items.objectConfiguration.register.id) {
|
|
463
|
-
this.$set(this.schemaItem.properties[key].items.objectConfiguration, 'register', property.items.objectConfiguration.register.id)
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
// Ensure $ref is always a string
|
|
467
|
-
this.ensureRefIsString(this.schemaItem.properties, key)
|
|
468
|
-
|
|
469
|
-
// Ensure inversedBy is always a string for regular properties
|
|
470
|
-
if (property.inversedBy && typeof property.inversedBy === 'object' && property.inversedBy.id) {
|
|
471
|
-
this.$set(this.schemaItem.properties[key], 'inversedBy', property.inversedBy.id)
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
// Ensure inversedBy is always a string for array items
|
|
475
|
-
if (property.items && property.items.inversedBy && typeof property.items.inversedBy === 'object' && property.items.inversedBy.id) {
|
|
476
|
-
this.$set(this.schemaItem.properties[key].items, 'inversedBy', property.items.inversedBy.id)
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
})
|
|
480
|
-
}
|
|
481
|
-
this.checkPropertiesModified()
|
|
482
|
-
},
|
|
483
|
-
deep: true,
|
|
484
|
-
},
|
|
485
|
-
},
|
|
486
|
-
methods: {
|
|
487
|
-
findSchemaBySlug(schemaSlug) {
|
|
488
|
-
if (!schemaSlug) return undefined
|
|
489
|
-
return this.availableSchemas.find(schema =>
|
|
490
|
-
(schema.slug && schema.slug.toLowerCase() === schemaSlug.toLowerCase())
|
|
491
|
-
|| schema.id === schemaSlug
|
|
492
|
-
|| schema.title === schemaSlug,
|
|
493
|
-
)
|
|
494
|
-
},
|
|
495
|
-
|
|
496
|
-
ensureRefIsString(obj, key) {
|
|
497
|
-
if (!obj || !key) return
|
|
498
|
-
|
|
499
|
-
if (obj[key] && typeof obj[key].$ref === 'object' && obj[key].$ref !== null) {
|
|
500
|
-
if (obj[key].$ref.id) {
|
|
501
|
-
obj[key].$ref = obj[key].$ref.id
|
|
502
|
-
} else {
|
|
503
|
-
obj[key].$ref = ''
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
if (obj[key] && obj[key].items && typeof obj[key].items.$ref === 'object' && obj[key].items.$ref !== null) {
|
|
508
|
-
if (obj[key].items.$ref.id) {
|
|
509
|
-
obj[key].items.$ref = obj[key].items.$ref.id
|
|
510
|
-
} else {
|
|
511
|
-
obj[key].items.$ref = ''
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
},
|
|
515
|
-
|
|
516
|
-
initializeSchemaItem() {
|
|
517
|
-
// Reset CnTabbedFormDialog state if available (not yet mounted on first call)
|
|
518
|
-
if (this.$refs.dialog) {
|
|
519
|
-
this.$refs.dialog.resetDialog()
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
if (this.item && this.item.id) {
|
|
523
|
-
this.schemaItem = {
|
|
524
|
-
...this.schemaItem,
|
|
525
|
-
...JSON.parse(JSON.stringify(this.item)),
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
// Ensure configuration object exists
|
|
529
|
-
if (!this.schemaItem.configuration) {
|
|
530
|
-
this.schemaItem.configuration = {
|
|
531
|
-
objectNameField: '',
|
|
532
|
-
objectDescriptionField: '',
|
|
533
|
-
objectImageField: '',
|
|
534
|
-
objectSummaryField: '',
|
|
535
|
-
allowFiles: false,
|
|
536
|
-
allowedTags: [],
|
|
537
|
-
}
|
|
538
|
-
} else {
|
|
539
|
-
if (!this.schemaItem.configuration.objectNameField) {
|
|
540
|
-
this.schemaItem.configuration.objectNameField = ''
|
|
541
|
-
}
|
|
542
|
-
if (!this.schemaItem.configuration.objectDescriptionField) {
|
|
543
|
-
this.schemaItem.configuration.objectDescriptionField = ''
|
|
544
|
-
}
|
|
545
|
-
if (!this.schemaItem.configuration.objectImageField) {
|
|
546
|
-
this.schemaItem.configuration.objectImageField = ''
|
|
547
|
-
}
|
|
548
|
-
if (!this.schemaItem.configuration.objectSummaryField) {
|
|
549
|
-
this.schemaItem.configuration.objectSummaryField = ''
|
|
550
|
-
}
|
|
551
|
-
if (this.schemaItem.configuration.allowFiles === undefined) {
|
|
552
|
-
this.schemaItem.configuration.allowFiles = false
|
|
553
|
-
}
|
|
554
|
-
if (!this.schemaItem.configuration.allowedTags) {
|
|
555
|
-
this.schemaItem.configuration.allowedTags = []
|
|
556
|
-
}
|
|
557
|
-
if (this.schemaItem.configuration.autoPublish === undefined) {
|
|
558
|
-
this.schemaItem.configuration.autoPublish = false
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
// Ensure authorization object exists
|
|
563
|
-
if (!this.schemaItem.authorization) {
|
|
564
|
-
this.schemaItem.authorization = {}
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
// Ensure existing properties have facetable set to false by default
|
|
568
|
-
Object.keys(this.schemaItem.properties || {}).forEach(key => {
|
|
569
|
-
if (this.schemaItem.properties[key].facetable === undefined) {
|
|
570
|
-
this.$set(this.schemaItem.properties[key], 'facetable', false)
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
if (this.schemaItem.properties[key].enum && Array.isArray(this.schemaItem.properties[key].enum)) {
|
|
574
|
-
this.$set(this.schemaItem.properties[key], 'enum', [...this.schemaItem.properties[key].enum])
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
const property = this.schemaItem.properties[key]
|
|
578
|
-
if (property.type === 'array' && property.items && property.items.type === 'object' && !property.items.objectConfiguration) {
|
|
579
|
-
this.$set(this.schemaItem.properties[key].items, 'objectConfiguration', { handling: 'nested-object' })
|
|
580
|
-
}
|
|
581
|
-
})
|
|
582
|
-
|
|
583
|
-
// Ensure all $ref values are strings and migrate old structure
|
|
584
|
-
Object.keys(this.schemaItem.properties || {}).forEach(key => {
|
|
585
|
-
this.ensureRefIsString(this.schemaItem.properties, key)
|
|
586
|
-
this.migratePropertyToNewStructure(key)
|
|
587
|
-
})
|
|
588
|
-
|
|
589
|
-
this.originalProperties = JSON.parse(JSON.stringify(this.schemaItem.properties || {}))
|
|
590
|
-
} else {
|
|
591
|
-
this.schemaItem.configuration = {
|
|
592
|
-
objectNameField: '',
|
|
593
|
-
objectDescriptionField: '',
|
|
594
|
-
objectImageField: '',
|
|
595
|
-
objectSummaryField: '',
|
|
596
|
-
allowFiles: false,
|
|
597
|
-
allowedTags: [],
|
|
598
|
-
autoPublish: false,
|
|
599
|
-
}
|
|
600
|
-
this.originalProperties = {}
|
|
601
|
-
}
|
|
602
|
-
this.propertiesModified = false
|
|
603
|
-
},
|
|
604
|
-
|
|
605
|
-
checkPropertiesModified() {
|
|
606
|
-
if (!this.originalProperties) return false
|
|
607
|
-
|
|
608
|
-
const currentProperties = JSON.stringify(this.schemaItem.properties || {})
|
|
609
|
-
const originalProperties = JSON.stringify(this.originalProperties)
|
|
610
|
-
|
|
611
|
-
this.propertiesModified = currentProperties !== originalProperties
|
|
612
|
-
},
|
|
613
|
-
|
|
614
|
-
async copyToClipboard(text) {
|
|
615
|
-
try {
|
|
616
|
-
await navigator.clipboard.writeText(text)
|
|
617
|
-
this.isCopied = true
|
|
618
|
-
setTimeout(() => { this.isCopied = false }, 2000)
|
|
619
|
-
} catch (err) {
|
|
620
|
-
console.error('Failed to copy text:', err)
|
|
621
|
-
}
|
|
622
|
-
},
|
|
623
|
-
|
|
624
|
-
addProperty() {
|
|
625
|
-
let newPropertyName = 'new'
|
|
626
|
-
let counter = 1
|
|
627
|
-
|
|
628
|
-
while (this.schemaItem.properties[newPropertyName]) {
|
|
629
|
-
counter++
|
|
630
|
-
newPropertyName = `new_${counter}`
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
this.$set(this.schemaItem.properties, newPropertyName, {
|
|
634
|
-
type: 'string',
|
|
635
|
-
format: '',
|
|
636
|
-
title: newPropertyName,
|
|
637
|
-
description: '',
|
|
638
|
-
facetable: false,
|
|
639
|
-
})
|
|
640
|
-
|
|
641
|
-
this.checkPropertiesModified()
|
|
642
|
-
this.selectedProperty = newPropertyName
|
|
643
|
-
},
|
|
644
|
-
|
|
645
|
-
updatePropertyKey(oldKey, newKey) {
|
|
646
|
-
if (!newKey || newKey === oldKey) return
|
|
647
|
-
if (this.schemaItem.properties[newKey] && newKey !== oldKey) return
|
|
648
|
-
|
|
649
|
-
const propertyData = { ...this.schemaItem.properties[oldKey] }
|
|
650
|
-
|
|
651
|
-
this.$set(this.schemaItem.properties, newKey, propertyData)
|
|
652
|
-
this.$delete(this.schemaItem.properties, oldKey)
|
|
653
|
-
|
|
654
|
-
this.selectedProperty = newKey
|
|
655
|
-
this.checkPropertiesModified()
|
|
656
|
-
},
|
|
657
|
-
|
|
658
|
-
deleteProperty(key) {
|
|
659
|
-
this.$delete(this.schemaItem.properties, key)
|
|
660
|
-
|
|
661
|
-
if (this.selectedProperty === key) {
|
|
662
|
-
this.selectedProperty = null
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
this.checkPropertiesModified()
|
|
666
|
-
},
|
|
667
|
-
|
|
668
|
-
copyProperty(key) {
|
|
669
|
-
if (this.schemaItem.properties[key]) {
|
|
670
|
-
const originalProperty = JSON.parse(JSON.stringify(this.schemaItem.properties[key]))
|
|
671
|
-
|
|
672
|
-
let newPropertyName = `${key}_copy`
|
|
673
|
-
let counter = 1
|
|
674
|
-
|
|
675
|
-
while (this.schemaItem.properties[newPropertyName]) {
|
|
676
|
-
counter++
|
|
677
|
-
newPropertyName = `${key}_copy_${counter}`
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
const originalTitle = originalProperty.title || key
|
|
681
|
-
this.$set(this.schemaItem.properties, newPropertyName, {
|
|
682
|
-
...originalProperty,
|
|
683
|
-
title: `${originalTitle} (copy)`,
|
|
684
|
-
})
|
|
685
|
-
|
|
686
|
-
this.checkPropertiesModified()
|
|
687
|
-
this.selectedProperty = newPropertyName
|
|
688
|
-
}
|
|
689
|
-
},
|
|
690
|
-
|
|
691
|
-
/**
|
|
692
|
-
* Handle confirm from CnTabbedFormDialog.
|
|
693
|
-
* Cleans schema data and emits confirm with the cleaned payload.
|
|
694
|
-
*/
|
|
695
|
-
handleConfirm() {
|
|
696
|
-
const cleanedSchemaItem = JSON.parse(JSON.stringify(this.schemaItem))
|
|
697
|
-
Object.keys(cleanedSchemaItem.properties || {}).forEach(key => {
|
|
698
|
-
this.ensureRefIsString(cleanedSchemaItem.properties, key)
|
|
699
|
-
|
|
700
|
-
if (cleanedSchemaItem.properties[key].register
|
|
701
|
-
&& cleanedSchemaItem.properties[key].objectConfiguration
|
|
702
|
-
&& cleanedSchemaItem.properties[key].objectConfiguration.register) {
|
|
703
|
-
delete cleanedSchemaItem.properties[key].register
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
if (cleanedSchemaItem.properties[key].items
|
|
707
|
-
&& cleanedSchemaItem.properties[key].items.register
|
|
708
|
-
&& cleanedSchemaItem.properties[key].items.objectConfiguration
|
|
709
|
-
&& cleanedSchemaItem.properties[key].items.objectConfiguration.register) {
|
|
710
|
-
delete cleanedSchemaItem.properties[key].items.register
|
|
711
|
-
}
|
|
712
|
-
})
|
|
713
|
-
|
|
714
|
-
this.$emit('confirm', cleanedSchemaItem)
|
|
715
|
-
},
|
|
716
|
-
|
|
717
|
-
/**
|
|
718
|
-
* Set the result of the save operation. Call this from the parent
|
|
719
|
-
* after the API call completes.
|
|
720
|
-
*
|
|
721
|
-
* @param {{ success?: boolean, error?: string }} resultData - result data
|
|
722
|
-
* @public
|
|
723
|
-
*/
|
|
724
|
-
setResult(resultData) {
|
|
725
|
-
this.$refs.dialog.setResult(resultData)
|
|
726
|
-
if (resultData.success) {
|
|
727
|
-
this.originalProperties = JSON.parse(JSON.stringify(this.schemaItem.properties || {}))
|
|
728
|
-
this.propertiesModified = false
|
|
729
|
-
}
|
|
730
|
-
},
|
|
731
|
-
|
|
732
|
-
migratePropertyToNewStructure(key) {
|
|
733
|
-
if (!this.schemaItem.properties[key]) return
|
|
734
|
-
|
|
735
|
-
const property = this.schemaItem.properties[key]
|
|
736
|
-
|
|
737
|
-
if (property.$ref && property.register && !property.objectConfiguration?.register) {
|
|
738
|
-
if (!property.objectConfiguration) {
|
|
739
|
-
this.$set(this.schemaItem.properties[key], 'objectConfiguration', { handling: 'related-object' })
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
const registerId = typeof property.register === 'object' && property.register.id
|
|
743
|
-
? property.register.id
|
|
744
|
-
: property.register
|
|
745
|
-
|
|
746
|
-
this.$set(this.schemaItem.properties[key].objectConfiguration, 'register', registerId)
|
|
747
|
-
|
|
748
|
-
if (property.$ref) {
|
|
749
|
-
let schemaSlug = property.$ref
|
|
750
|
-
if (schemaSlug.includes('/')) {
|
|
751
|
-
schemaSlug = schemaSlug.substring(schemaSlug.lastIndexOf('/') + 1)
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
const referencedSchema = this.findSchemaBySlug(schemaSlug)
|
|
755
|
-
if (referencedSchema) {
|
|
756
|
-
this.$set(this.schemaItem.properties[key].objectConfiguration, 'schema', referencedSchema.id)
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
if (property.items && property.items.$ref && property.items.register && !property.items.objectConfiguration?.register) {
|
|
762
|
-
if (!property.items.objectConfiguration) {
|
|
763
|
-
this.$set(this.schemaItem.properties[key].items, 'objectConfiguration', { handling: 'related-object' })
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
const registerId = typeof property.items.register === 'object' && property.items.register.id
|
|
767
|
-
? property.items.register.id
|
|
768
|
-
: property.items.register
|
|
769
|
-
|
|
770
|
-
this.$set(this.schemaItem.properties[key].items.objectConfiguration, 'register', registerId)
|
|
771
|
-
|
|
772
|
-
if (property.items.$ref) {
|
|
773
|
-
let schemaSlug = property.items.$ref
|
|
774
|
-
if (schemaSlug.includes('/')) {
|
|
775
|
-
schemaSlug = schemaSlug.substring(schemaSlug.lastIndexOf('/') + 1)
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
const referencedSchema = this.findSchemaBySlug(schemaSlug)
|
|
779
|
-
if (referencedSchema) {
|
|
780
|
-
this.$set(this.schemaItem.properties[key].items.objectConfiguration, 'schema', referencedSchema.id)
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
},
|
|
785
|
-
},
|
|
786
|
-
}
|
|
787
|
-
</script>
|
|
1
|
+
<template>
|
|
2
|
+
<CnTabbedFormDialog
|
|
3
|
+
ref="dialog"
|
|
4
|
+
:tabs="dialogTabs"
|
|
5
|
+
:item="item"
|
|
6
|
+
:dialog-title="dialogTitle"
|
|
7
|
+
entity-name="Schema"
|
|
8
|
+
:size="size"
|
|
9
|
+
:disable-save="!schemaItem.title"
|
|
10
|
+
:success-text="resolvedSuccessText"
|
|
11
|
+
:cancel-label="cancelLabel"
|
|
12
|
+
:close-label="closeLabel"
|
|
13
|
+
:confirm-label="confirmLabel"
|
|
14
|
+
@confirm="handleConfirm"
|
|
15
|
+
@close="$emit('close')">
|
|
16
|
+
<!-- Metadata Display -->
|
|
17
|
+
<template #above-tabs="{ loading: dialogLoading }">
|
|
18
|
+
<div class="cn-schema-form__detail-grid">
|
|
19
|
+
<div v-if="schemaItem.id"
|
|
20
|
+
class="cn-schema-form__detail-item cn-schema-form__id-card">
|
|
21
|
+
<div class="cn-schema-form__id-card-header">
|
|
22
|
+
<span class="cn-schema-form__detail-label">ID / UUID:</span>
|
|
23
|
+
<NcButton class="cn-schema-form__copy-button"
|
|
24
|
+
@click="copyToClipboard(schemaItem.uuid || schemaItem.id)">
|
|
25
|
+
<template #icon>
|
|
26
|
+
<Check v-if="isCopied" :size="20" />
|
|
27
|
+
<ContentCopy v-else :size="20" />
|
|
28
|
+
</template>
|
|
29
|
+
{{ isCopied ? 'Copied' : 'Copy' }}
|
|
30
|
+
</NcButton>
|
|
31
|
+
</div>
|
|
32
|
+
<span class="cn-schema-form__detail-value">{{ schemaItem.id }}</span>
|
|
33
|
+
<span v-if="schemaItem.uuid && schemaItem.uuid !== schemaItem.id"
|
|
34
|
+
class="cn-schema-form__detail-value cn-schema-form__uuid-value">{{ schemaItem.uuid }}</span>
|
|
35
|
+
</div>
|
|
36
|
+
<div class="cn-schema-form__detail-item cn-schema-form__title-with-badge">
|
|
37
|
+
<NcTextField :disabled="dialogLoading"
|
|
38
|
+
label="Title *"
|
|
39
|
+
:value.sync="schemaItem.title" />
|
|
40
|
+
<span v-if="schemaItem.allOf && schemaItem.allOf.length > 0"
|
|
41
|
+
class="cn-schema-form__statusPill cn-schema-form__statusPill--success">
|
|
42
|
+
allOf
|
|
43
|
+
</span>
|
|
44
|
+
<span v-if="schemaItem.oneOf && schemaItem.oneOf.length > 0"
|
|
45
|
+
class="cn-schema-form__statusPill cn-schema-form__statusPill--info">
|
|
46
|
+
oneOf
|
|
47
|
+
</span>
|
|
48
|
+
<span v-if="schemaItem.anyOf && schemaItem.anyOf.length > 0"
|
|
49
|
+
class="cn-schema-form__statusPill cn-schema-form__statusPill--info">
|
|
50
|
+
anyOf
|
|
51
|
+
</span>
|
|
52
|
+
</div>
|
|
53
|
+
<div v-if="schemaItem.created" class="cn-schema-form__detail-item">
|
|
54
|
+
<span class="cn-schema-form__detail-label">Created:</span>
|
|
55
|
+
<span class="cn-schema-form__detail-value">{{ new Date(schemaItem.created).toLocaleString() }}</span>
|
|
56
|
+
</div>
|
|
57
|
+
<div v-if="schemaItem.updated" class="cn-schema-form__detail-item">
|
|
58
|
+
<span class="cn-schema-form__detail-label">Updated:</span>
|
|
59
|
+
<span class="cn-schema-form__detail-value">{{ new Date(schemaItem.updated).toLocaleString() }}</span>
|
|
60
|
+
</div>
|
|
61
|
+
<div class="cn-schema-form__detail-item">
|
|
62
|
+
<span class="cn-schema-form__detail-label">Version:</span>
|
|
63
|
+
<span class="cn-schema-form__detail-value">{{ schemaItem.version || 'Not set' }}</span>
|
|
64
|
+
</div>
|
|
65
|
+
<div class="cn-schema-form__detail-item">
|
|
66
|
+
<span class="cn-schema-form__detail-label">Owner:</span>
|
|
67
|
+
<span class="cn-schema-form__detail-value">{{ schemaItem.owner || 'Not set' }}</span>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
</template>
|
|
71
|
+
|
|
72
|
+
<!-- Properties Tab -->
|
|
73
|
+
<template #tab-properties="{ loading: dialogLoading }">
|
|
74
|
+
<CnSchemaPropertiesTab
|
|
75
|
+
:schema-item="schemaItem"
|
|
76
|
+
:loading="dialogLoading"
|
|
77
|
+
:selected-property="selectedProperty"
|
|
78
|
+
:properties-modified="propertiesModified"
|
|
79
|
+
:original-properties="originalProperties"
|
|
80
|
+
:type-options-for-select="typeOptionsForSelect"
|
|
81
|
+
:available-schemas="availableSchemas"
|
|
82
|
+
:available-registers="availableRegisters"
|
|
83
|
+
:available-tags-options="availableTagsOptions"
|
|
84
|
+
:user-groups="userGroups"
|
|
85
|
+
:sorted-user-groups="sortedUserGroups"
|
|
86
|
+
:loading-groups="loadingGroups"
|
|
87
|
+
@add-property="addProperty"
|
|
88
|
+
@update:selected-property="selectedProperty = $event"
|
|
89
|
+
@update:property-key="updatePropertyKey($event.oldKey, $event.newKey)"
|
|
90
|
+
@copy-property="copyProperty"
|
|
91
|
+
@delete-property="deleteProperty" />
|
|
92
|
+
</template>
|
|
93
|
+
|
|
94
|
+
<!-- Configuration Tab -->
|
|
95
|
+
<template #tab-configuration="{ loading: dialogLoading }">
|
|
96
|
+
<CnSchemaConfigurationTab
|
|
97
|
+
:schema-item="schemaItem"
|
|
98
|
+
:loading="dialogLoading"
|
|
99
|
+
:available-schemas="availableSchemas"
|
|
100
|
+
:property-options="propertyOptions"
|
|
101
|
+
:all-of-schema-names="allOfSchemaNames" />
|
|
102
|
+
</template>
|
|
103
|
+
|
|
104
|
+
<!-- Security Tab -->
|
|
105
|
+
<template #tab-security>
|
|
106
|
+
<CnSchemaSecurityTab
|
|
107
|
+
:schema-item="schemaItem"
|
|
108
|
+
:user-groups="userGroups"
|
|
109
|
+
:sorted-user-groups="sortedUserGroups"
|
|
110
|
+
:loading-groups="loadingGroups"
|
|
111
|
+
:has-any-permissions="hasAnyPermissions"
|
|
112
|
+
:is-restrictive-schema="isRestrictiveSchema" />
|
|
113
|
+
</template>
|
|
114
|
+
|
|
115
|
+
<!-- Optional Action Buttons (edit mode only) -->
|
|
116
|
+
<template #actions-right="{ loading: dialogLoading, isCreateMode, result: dialogResult }">
|
|
117
|
+
<template v-if="!isCreateMode && dialogResult === null">
|
|
118
|
+
<NcButton
|
|
119
|
+
v-if="showExtendSchema"
|
|
120
|
+
:disabled="dialogLoading"
|
|
121
|
+
@click="$emit('extend-schema')">
|
|
122
|
+
<template #icon>
|
|
123
|
+
<CallSplit :size="20" />
|
|
124
|
+
</template>
|
|
125
|
+
{{ extendSchemaLabel }}
|
|
126
|
+
</NcButton>
|
|
127
|
+
<NcButton
|
|
128
|
+
v-if="showAnalyzeProperties"
|
|
129
|
+
:disabled="dialogLoading"
|
|
130
|
+
@click="$emit('analyze-properties')">
|
|
131
|
+
<template #icon>
|
|
132
|
+
<DatabaseSearch :size="20" />
|
|
133
|
+
</template>
|
|
134
|
+
{{ analyzePropertiesLabel }}
|
|
135
|
+
</NcButton>
|
|
136
|
+
<NcButton
|
|
137
|
+
v-if="showValidateObjects"
|
|
138
|
+
:disabled="dialogLoading"
|
|
139
|
+
@click="$emit('validate-objects')">
|
|
140
|
+
<template #icon>
|
|
141
|
+
<CheckCircle :size="20" />
|
|
142
|
+
</template>
|
|
143
|
+
{{ validateObjectsLabel }}
|
|
144
|
+
</NcButton>
|
|
145
|
+
<NcButton
|
|
146
|
+
v-if="showDeleteObjects"
|
|
147
|
+
v-tooltip="objectCount > 0 ? deleteObjectsTooltip : noDeleteObjectsTooltip"
|
|
148
|
+
:disabled="dialogLoading || objectCount === 0"
|
|
149
|
+
@click="$emit('delete-objects')">
|
|
150
|
+
<template #icon>
|
|
151
|
+
<DeleteSweep :size="20" />
|
|
152
|
+
</template>
|
|
153
|
+
{{ deleteObjectsLabel }}
|
|
154
|
+
</NcButton>
|
|
155
|
+
<NcButton
|
|
156
|
+
v-if="showPublishObjects"
|
|
157
|
+
v-tooltip="objectCount > 0 ? publishObjectsTooltip : noPublishObjectsTooltip"
|
|
158
|
+
:disabled="dialogLoading || objectCount === 0"
|
|
159
|
+
@click="$emit('publish-objects')">
|
|
160
|
+
<template #icon>
|
|
161
|
+
<Upload :size="20" />
|
|
162
|
+
</template>
|
|
163
|
+
{{ publishObjectsLabel }}
|
|
164
|
+
</NcButton>
|
|
165
|
+
<NcButton
|
|
166
|
+
v-if="showDelete"
|
|
167
|
+
v-tooltip="objectCount > 0 ? cannotDeleteTooltip : ''"
|
|
168
|
+
:disabled="dialogLoading || objectCount > 0"
|
|
169
|
+
type="error"
|
|
170
|
+
@click="$emit('delete-schema')">
|
|
171
|
+
<template #icon>
|
|
172
|
+
<TrashCanOutline :size="20" />
|
|
173
|
+
</template>
|
|
174
|
+
{{ deleteLabel }}
|
|
175
|
+
</NcButton>
|
|
176
|
+
</template>
|
|
177
|
+
</template>
|
|
178
|
+
</CnTabbedFormDialog>
|
|
179
|
+
</template>
|
|
180
|
+
|
|
181
|
+
<script>
|
|
182
|
+
import {
|
|
183
|
+
NcButton,
|
|
184
|
+
NcTextField,
|
|
185
|
+
} from '@nextcloud/vue'
|
|
186
|
+
|
|
187
|
+
import CnTabbedFormDialog from '../CnTabbedFormDialog/CnTabbedFormDialog.vue'
|
|
188
|
+
import CnSchemaPropertiesTab from './CnSchemaPropertiesTab.vue'
|
|
189
|
+
import CnSchemaConfigurationTab from './CnSchemaConfigurationTab.vue'
|
|
190
|
+
import CnSchemaSecurityTab from './CnSchemaSecurityTab.vue'
|
|
191
|
+
|
|
192
|
+
import ContentCopy from 'vue-material-design-icons/ContentCopy.vue'
|
|
193
|
+
import Check from 'vue-material-design-icons/Check.vue'
|
|
194
|
+
import TrashCanOutline from 'vue-material-design-icons/TrashCanOutline.vue'
|
|
195
|
+
import CallSplit from 'vue-material-design-icons/CallSplit.vue'
|
|
196
|
+
import DatabaseSearch from 'vue-material-design-icons/DatabaseSearch.vue'
|
|
197
|
+
import CheckCircle from 'vue-material-design-icons/CheckCircle.vue'
|
|
198
|
+
import DeleteSweep from 'vue-material-design-icons/DeleteSweep.vue'
|
|
199
|
+
import Upload from 'vue-material-design-icons/Upload.vue'
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* CnSchemaFormDialog — Generic JSON Schema editor dialog.
|
|
203
|
+
*
|
|
204
|
+
* Provides a full-featured form for creating and editing JSON Schemas with
|
|
205
|
+
* properties table, configuration, and security (RBAC) tabs. Uses CnTabbedFormDialog.
|
|
206
|
+
*
|
|
207
|
+
* The dialog does NOT perform saves — it emits a `confirm` event with the schema data.
|
|
208
|
+
* The parent performs the actual API call and calls `setResult()` via a ref.
|
|
209
|
+
*
|
|
210
|
+
* @event confirm Emitted when the user confirms. Payload: cleaned schema data object.
|
|
211
|
+
* @event close Emitted when the dialog should be closed.
|
|
212
|
+
* @event extend-schema Emitted when the Extend Schema button is clicked.
|
|
213
|
+
* @event analyze-properties Emitted when the Analyze Properties button is clicked.
|
|
214
|
+
* @event validate-objects Emitted when the Validate Objects button is clicked.
|
|
215
|
+
* @event delete-objects Emitted when the Delete Objects button is clicked.
|
|
216
|
+
* @event publish-objects Emitted when the Publish Objects button is clicked.
|
|
217
|
+
* @event delete-schema Emitted when the Delete button is clicked.
|
|
218
|
+
*/
|
|
219
|
+
export default {
|
|
220
|
+
name: 'CnSchemaFormDialog',
|
|
221
|
+
components: {
|
|
222
|
+
NcTextField,
|
|
223
|
+
NcButton,
|
|
224
|
+
CnTabbedFormDialog,
|
|
225
|
+
CnSchemaPropertiesTab,
|
|
226
|
+
CnSchemaConfigurationTab,
|
|
227
|
+
CnSchemaSecurityTab,
|
|
228
|
+
// Icons
|
|
229
|
+
ContentCopy,
|
|
230
|
+
Check,
|
|
231
|
+
TrashCanOutline,
|
|
232
|
+
CallSplit,
|
|
233
|
+
DatabaseSearch,
|
|
234
|
+
CheckCircle,
|
|
235
|
+
DeleteSweep,
|
|
236
|
+
Upload,
|
|
237
|
+
},
|
|
238
|
+
props: {
|
|
239
|
+
/** Existing schema item for edit mode. Pass null for create mode. */
|
|
240
|
+
item: { type: Object, default: null },
|
|
241
|
+
/** Dialog title. Defaults to "Create Schema" or "Edit Schema". */
|
|
242
|
+
dialogTitle: { type: String, default: '' },
|
|
243
|
+
/** NcDialog size */
|
|
244
|
+
size: { type: String, default: 'large' },
|
|
245
|
+
/** Available schemas for references and composition. Array of { id, title, description, reference } */
|
|
246
|
+
availableSchemas: { type: Array, default: () => [] },
|
|
247
|
+
/** Available registers. Array of { id, label } */
|
|
248
|
+
availableRegisters: { type: Array, default: () => [] },
|
|
249
|
+
/** User groups for RBAC. Array of { id, displayname } */
|
|
250
|
+
userGroups: { type: Array, default: () => [] },
|
|
251
|
+
/** Available tags for file property configuration */
|
|
252
|
+
availableTags: { type: Array, default: () => [] },
|
|
253
|
+
/** Whether user groups are still loading */
|
|
254
|
+
loadingGroups: { type: Boolean, default: false },
|
|
255
|
+
/** Number of objects attached to this schema (used for action button disable logic) */
|
|
256
|
+
objectCount: { type: Number, default: 0 },
|
|
257
|
+
// Optional action button visibility
|
|
258
|
+
/** Show "Extend Schema" button */
|
|
259
|
+
showExtendSchema: { type: Boolean, default: false },
|
|
260
|
+
/** Show "Analyze Properties" button */
|
|
261
|
+
showAnalyzeProperties: { type: Boolean, default: false },
|
|
262
|
+
/** Show "Validate Objects" button */
|
|
263
|
+
showValidateObjects: { type: Boolean, default: false },
|
|
264
|
+
/** Show "Delete Objects" button */
|
|
265
|
+
showDeleteObjects: { type: Boolean, default: false },
|
|
266
|
+
/** Show "Publish Objects" button */
|
|
267
|
+
showPublishObjects: { type: Boolean, default: false },
|
|
268
|
+
/** Show "Delete" button */
|
|
269
|
+
showDelete: { type: Boolean, default: false },
|
|
270
|
+
// Labels (pre-translated strings with English defaults)
|
|
271
|
+
cancelLabel: { type: String, default: 'Cancel' },
|
|
272
|
+
closeLabel: { type: String, default: 'Close' },
|
|
273
|
+
/** Confirm button label. Defaults to "Create" or "Save". */
|
|
274
|
+
confirmLabel: { type: String, default: '' },
|
|
275
|
+
/** Success message. Defaults to "Schema saved successfully." */
|
|
276
|
+
successText: { type: String, default: '' },
|
|
277
|
+
extendSchemaLabel: { type: String, default: 'Extend Schema' },
|
|
278
|
+
analyzePropertiesLabel: { type: String, default: 'Analyze Properties' },
|
|
279
|
+
validateObjectsLabel: { type: String, default: 'Validate Objects' },
|
|
280
|
+
deleteObjectsLabel: { type: String, default: 'Delete Objects' },
|
|
281
|
+
publishObjectsLabel: { type: String, default: 'Publish Objects' },
|
|
282
|
+
deleteLabel: { type: String, default: 'Delete' },
|
|
283
|
+
deleteObjectsTooltip: { type: String, default: 'Delete all objects in this schema' },
|
|
284
|
+
publishObjectsTooltip: { type: String, default: 'Publish all objects in this schema' },
|
|
285
|
+
/** Tooltip for the Delete Objects button when no objects exist */
|
|
286
|
+
noDeleteObjectsTooltip: { type: String, default: 'No objects to delete' },
|
|
287
|
+
/** Tooltip for the Publish Objects button when no objects exist */
|
|
288
|
+
noPublishObjectsTooltip: { type: String, default: 'No objects to publish' },
|
|
289
|
+
cannotDeleteTooltip: { type: String, default: 'Cannot delete: objects are still attached' },
|
|
290
|
+
},
|
|
291
|
+
data() {
|
|
292
|
+
return {
|
|
293
|
+
isCopied: false,
|
|
294
|
+
selectedProperty: null,
|
|
295
|
+
propertiesModified: false,
|
|
296
|
+
originalProperties: null,
|
|
297
|
+
schemaItem: {
|
|
298
|
+
title: '',
|
|
299
|
+
version: '0.0.0',
|
|
300
|
+
description: '',
|
|
301
|
+
summary: '',
|
|
302
|
+
slug: '',
|
|
303
|
+
properties: {},
|
|
304
|
+
configuration: {
|
|
305
|
+
objectNameField: '',
|
|
306
|
+
objectDescriptionField: '',
|
|
307
|
+
objectImageField: '',
|
|
308
|
+
objectSummaryField: '',
|
|
309
|
+
allowFiles: false,
|
|
310
|
+
allowedTags: [],
|
|
311
|
+
autoPublish: false,
|
|
312
|
+
},
|
|
313
|
+
authorization: {},
|
|
314
|
+
hardValidation: false,
|
|
315
|
+
immutable: false,
|
|
316
|
+
searchable: true,
|
|
317
|
+
maxDepth: 0,
|
|
318
|
+
},
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
computed: {
|
|
322
|
+
/**
|
|
323
|
+
* Tab definitions for CnTabbedFormDialog.
|
|
324
|
+
*
|
|
325
|
+
* @return {Array} Tab configuration
|
|
326
|
+
*/
|
|
327
|
+
dialogTabs() {
|
|
328
|
+
return [
|
|
329
|
+
{ id: 'properties', title: 'Properties' },
|
|
330
|
+
{ id: 'configuration', title: 'Configuration' },
|
|
331
|
+
{ id: 'security', title: 'Security' },
|
|
332
|
+
]
|
|
333
|
+
},
|
|
334
|
+
sortedUserGroups() {
|
|
335
|
+
return this.userGroups
|
|
336
|
+
.filter(group => group.id !== 'admin' && group.id !== 'public')
|
|
337
|
+
.sort((a, b) => {
|
|
338
|
+
const nameA = a.displayname || a.id
|
|
339
|
+
const nameB = b.displayname || b.id
|
|
340
|
+
return nameA.localeCompare(nameB)
|
|
341
|
+
})
|
|
342
|
+
},
|
|
343
|
+
hasAnyPermissions() {
|
|
344
|
+
const auth = this.schemaItem.authorization || {}
|
|
345
|
+
return Object.keys(auth).some(action =>
|
|
346
|
+
Array.isArray(auth[action]) && auth[action].length > 0,
|
|
347
|
+
)
|
|
348
|
+
},
|
|
349
|
+
isRestrictiveSchema() {
|
|
350
|
+
const auth = this.schemaItem.authorization || {}
|
|
351
|
+
const actions = ['create', 'read', 'update', 'delete']
|
|
352
|
+
return actions.some(action =>
|
|
353
|
+
Array.isArray(auth[action]) && auth[action].length > 0
|
|
354
|
+
&& !auth[action].includes('public'),
|
|
355
|
+
)
|
|
356
|
+
},
|
|
357
|
+
typeOptionsForSelect() {
|
|
358
|
+
return [
|
|
359
|
+
{ id: 'string', label: 'String' },
|
|
360
|
+
{ id: 'number', label: 'Number' },
|
|
361
|
+
{ id: 'integer', label: 'Integer' },
|
|
362
|
+
{ id: 'boolean', label: 'Boolean' },
|
|
363
|
+
{ id: 'array', label: 'Array' },
|
|
364
|
+
{ id: 'object', label: 'Object' },
|
|
365
|
+
{ id: 'dictionary', label: 'Dictionary' },
|
|
366
|
+
{ id: 'file', label: 'File' },
|
|
367
|
+
{ id: 'oneOf', label: 'One Of' },
|
|
368
|
+
]
|
|
369
|
+
},
|
|
370
|
+
propertyOptions() {
|
|
371
|
+
const properties = this.schemaItem.properties || {}
|
|
372
|
+
return ['', ...Object.keys(properties)]
|
|
373
|
+
},
|
|
374
|
+
availableTagsOptions() {
|
|
375
|
+
return this.availableTags.map(tag => ({
|
|
376
|
+
id: tag,
|
|
377
|
+
label: tag,
|
|
378
|
+
}))
|
|
379
|
+
},
|
|
380
|
+
/**
|
|
381
|
+
* Resolved success text for backwards compatibility (includes trailing period).
|
|
382
|
+
*
|
|
383
|
+
* @return {string}
|
|
384
|
+
*/
|
|
385
|
+
resolvedSuccessText() {
|
|
386
|
+
if (this.successText) return this.successText
|
|
387
|
+
return 'Schema saved successfully.'
|
|
388
|
+
},
|
|
389
|
+
allOfSchemaNames() {
|
|
390
|
+
if (!this.schemaItem.allOf || !Array.isArray(this.schemaItem.allOf) || this.schemaItem.allOf.length === 0) {
|
|
391
|
+
return []
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return this.schemaItem.allOf
|
|
395
|
+
.map(ref => {
|
|
396
|
+
const schemaId = typeof ref === 'object' ? ref.id : ref
|
|
397
|
+
const schema = this.availableSchemas.find(s => s.id === schemaId)
|
|
398
|
+
return schema ? (schema.title || `Schema ${schema.id}`) : schemaId
|
|
399
|
+
})
|
|
400
|
+
.filter(name => name)
|
|
401
|
+
},
|
|
402
|
+
},
|
|
403
|
+
watch: {
|
|
404
|
+
item: {
|
|
405
|
+
immediate: true,
|
|
406
|
+
handler() {
|
|
407
|
+
this.initializeSchemaItem()
|
|
408
|
+
},
|
|
409
|
+
},
|
|
410
|
+
'schemaItem.properties': {
|
|
411
|
+
handler(newProperties) {
|
|
412
|
+
if (newProperties) {
|
|
413
|
+
Object.keys(newProperties).forEach(key => {
|
|
414
|
+
const property = newProperties[key]
|
|
415
|
+
if (property) {
|
|
416
|
+
// Initialize nested objects if they don't exist
|
|
417
|
+
if (property.type === 'array' && !property.items) {
|
|
418
|
+
this.$set(this.schemaItem.properties[key], 'items', { type: 'string' })
|
|
419
|
+
}
|
|
420
|
+
if (property.type === 'object' && !property.objectConfiguration) {
|
|
421
|
+
this.$set(this.schemaItem.properties[key], 'objectConfiguration', { handling: 'nested-object' })
|
|
422
|
+
}
|
|
423
|
+
if (property.type === 'array' && property.items && property.items.type === 'object' && !property.items.objectConfiguration) {
|
|
424
|
+
this.$set(this.schemaItem.properties[key].items, 'objectConfiguration', { handling: 'nested-object' })
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Convert property type from object to string
|
|
428
|
+
if (property.type && typeof property.type === 'object' && property.type.id) {
|
|
429
|
+
this.$set(this.schemaItem.properties[key], 'type', property.type.id)
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Convert property format from object to string
|
|
433
|
+
if (property.format && typeof property.format === 'object' && property.format.id) {
|
|
434
|
+
this.$set(this.schemaItem.properties[key], 'format', property.format.id)
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Convert array item type from object to string
|
|
438
|
+
if (property.items && property.items.type && typeof property.items.type === 'object' && property.items.type.id) {
|
|
439
|
+
this.$set(this.schemaItem.properties[key].items, 'type', property.items.type.id)
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Convert object handling from object to string
|
|
443
|
+
if (property.objectConfiguration && property.objectConfiguration.handling
|
|
444
|
+
&& typeof property.objectConfiguration.handling === 'object' && property.objectConfiguration.handling.id) {
|
|
445
|
+
this.$set(this.schemaItem.properties[key].objectConfiguration, 'handling', property.objectConfiguration.handling.id)
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Convert register from object to ID
|
|
449
|
+
if (property.objectConfiguration && property.objectConfiguration.register
|
|
450
|
+
&& typeof property.objectConfiguration.register === 'object' && property.objectConfiguration.register.id) {
|
|
451
|
+
this.$set(this.schemaItem.properties[key].objectConfiguration, 'register', property.objectConfiguration.register.id)
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Convert array item object handling from object to string
|
|
455
|
+
if (property.items && property.items.objectConfiguration && property.items.objectConfiguration.handling
|
|
456
|
+
&& typeof property.items.objectConfiguration.handling === 'object' && property.items.objectConfiguration.handling.id) {
|
|
457
|
+
this.$set(this.schemaItem.properties[key].items.objectConfiguration, 'handling', property.items.objectConfiguration.handling.id)
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Convert array item register from object to ID
|
|
461
|
+
if (property.items && property.items.objectConfiguration && property.items.objectConfiguration.register
|
|
462
|
+
&& typeof property.items.objectConfiguration.register === 'object' && property.items.objectConfiguration.register.id) {
|
|
463
|
+
this.$set(this.schemaItem.properties[key].items.objectConfiguration, 'register', property.items.objectConfiguration.register.id)
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Ensure $ref is always a string
|
|
467
|
+
this.ensureRefIsString(this.schemaItem.properties, key)
|
|
468
|
+
|
|
469
|
+
// Ensure inversedBy is always a string for regular properties
|
|
470
|
+
if (property.inversedBy && typeof property.inversedBy === 'object' && property.inversedBy.id) {
|
|
471
|
+
this.$set(this.schemaItem.properties[key], 'inversedBy', property.inversedBy.id)
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Ensure inversedBy is always a string for array items
|
|
475
|
+
if (property.items && property.items.inversedBy && typeof property.items.inversedBy === 'object' && property.items.inversedBy.id) {
|
|
476
|
+
this.$set(this.schemaItem.properties[key].items, 'inversedBy', property.items.inversedBy.id)
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
})
|
|
480
|
+
}
|
|
481
|
+
this.checkPropertiesModified()
|
|
482
|
+
},
|
|
483
|
+
deep: true,
|
|
484
|
+
},
|
|
485
|
+
},
|
|
486
|
+
methods: {
|
|
487
|
+
findSchemaBySlug(schemaSlug) {
|
|
488
|
+
if (!schemaSlug) return undefined
|
|
489
|
+
return this.availableSchemas.find(schema =>
|
|
490
|
+
(schema.slug && schema.slug.toLowerCase() === schemaSlug.toLowerCase())
|
|
491
|
+
|| schema.id === schemaSlug
|
|
492
|
+
|| schema.title === schemaSlug,
|
|
493
|
+
)
|
|
494
|
+
},
|
|
495
|
+
|
|
496
|
+
ensureRefIsString(obj, key) {
|
|
497
|
+
if (!obj || !key) return
|
|
498
|
+
|
|
499
|
+
if (obj[key] && typeof obj[key].$ref === 'object' && obj[key].$ref !== null) {
|
|
500
|
+
if (obj[key].$ref.id) {
|
|
501
|
+
obj[key].$ref = obj[key].$ref.id
|
|
502
|
+
} else {
|
|
503
|
+
obj[key].$ref = ''
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (obj[key] && obj[key].items && typeof obj[key].items.$ref === 'object' && obj[key].items.$ref !== null) {
|
|
508
|
+
if (obj[key].items.$ref.id) {
|
|
509
|
+
obj[key].items.$ref = obj[key].items.$ref.id
|
|
510
|
+
} else {
|
|
511
|
+
obj[key].items.$ref = ''
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
},
|
|
515
|
+
|
|
516
|
+
initializeSchemaItem() {
|
|
517
|
+
// Reset CnTabbedFormDialog state if available (not yet mounted on first call)
|
|
518
|
+
if (this.$refs.dialog) {
|
|
519
|
+
this.$refs.dialog.resetDialog()
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if (this.item && this.item.id) {
|
|
523
|
+
this.schemaItem = {
|
|
524
|
+
...this.schemaItem,
|
|
525
|
+
...JSON.parse(JSON.stringify(this.item)),
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Ensure configuration object exists
|
|
529
|
+
if (!this.schemaItem.configuration) {
|
|
530
|
+
this.schemaItem.configuration = {
|
|
531
|
+
objectNameField: '',
|
|
532
|
+
objectDescriptionField: '',
|
|
533
|
+
objectImageField: '',
|
|
534
|
+
objectSummaryField: '',
|
|
535
|
+
allowFiles: false,
|
|
536
|
+
allowedTags: [],
|
|
537
|
+
}
|
|
538
|
+
} else {
|
|
539
|
+
if (!this.schemaItem.configuration.objectNameField) {
|
|
540
|
+
this.schemaItem.configuration.objectNameField = ''
|
|
541
|
+
}
|
|
542
|
+
if (!this.schemaItem.configuration.objectDescriptionField) {
|
|
543
|
+
this.schemaItem.configuration.objectDescriptionField = ''
|
|
544
|
+
}
|
|
545
|
+
if (!this.schemaItem.configuration.objectImageField) {
|
|
546
|
+
this.schemaItem.configuration.objectImageField = ''
|
|
547
|
+
}
|
|
548
|
+
if (!this.schemaItem.configuration.objectSummaryField) {
|
|
549
|
+
this.schemaItem.configuration.objectSummaryField = ''
|
|
550
|
+
}
|
|
551
|
+
if (this.schemaItem.configuration.allowFiles === undefined) {
|
|
552
|
+
this.schemaItem.configuration.allowFiles = false
|
|
553
|
+
}
|
|
554
|
+
if (!this.schemaItem.configuration.allowedTags) {
|
|
555
|
+
this.schemaItem.configuration.allowedTags = []
|
|
556
|
+
}
|
|
557
|
+
if (this.schemaItem.configuration.autoPublish === undefined) {
|
|
558
|
+
this.schemaItem.configuration.autoPublish = false
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Ensure authorization object exists
|
|
563
|
+
if (!this.schemaItem.authorization) {
|
|
564
|
+
this.schemaItem.authorization = {}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// Ensure existing properties have facetable set to false by default
|
|
568
|
+
Object.keys(this.schemaItem.properties || {}).forEach(key => {
|
|
569
|
+
if (this.schemaItem.properties[key].facetable === undefined) {
|
|
570
|
+
this.$set(this.schemaItem.properties[key], 'facetable', false)
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
if (this.schemaItem.properties[key].enum && Array.isArray(this.schemaItem.properties[key].enum)) {
|
|
574
|
+
this.$set(this.schemaItem.properties[key], 'enum', [...this.schemaItem.properties[key].enum])
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
const property = this.schemaItem.properties[key]
|
|
578
|
+
if (property.type === 'array' && property.items && property.items.type === 'object' && !property.items.objectConfiguration) {
|
|
579
|
+
this.$set(this.schemaItem.properties[key].items, 'objectConfiguration', { handling: 'nested-object' })
|
|
580
|
+
}
|
|
581
|
+
})
|
|
582
|
+
|
|
583
|
+
// Ensure all $ref values are strings and migrate old structure
|
|
584
|
+
Object.keys(this.schemaItem.properties || {}).forEach(key => {
|
|
585
|
+
this.ensureRefIsString(this.schemaItem.properties, key)
|
|
586
|
+
this.migratePropertyToNewStructure(key)
|
|
587
|
+
})
|
|
588
|
+
|
|
589
|
+
this.originalProperties = JSON.parse(JSON.stringify(this.schemaItem.properties || {}))
|
|
590
|
+
} else {
|
|
591
|
+
this.schemaItem.configuration = {
|
|
592
|
+
objectNameField: '',
|
|
593
|
+
objectDescriptionField: '',
|
|
594
|
+
objectImageField: '',
|
|
595
|
+
objectSummaryField: '',
|
|
596
|
+
allowFiles: false,
|
|
597
|
+
allowedTags: [],
|
|
598
|
+
autoPublish: false,
|
|
599
|
+
}
|
|
600
|
+
this.originalProperties = {}
|
|
601
|
+
}
|
|
602
|
+
this.propertiesModified = false
|
|
603
|
+
},
|
|
604
|
+
|
|
605
|
+
checkPropertiesModified() {
|
|
606
|
+
if (!this.originalProperties) return false
|
|
607
|
+
|
|
608
|
+
const currentProperties = JSON.stringify(this.schemaItem.properties || {})
|
|
609
|
+
const originalProperties = JSON.stringify(this.originalProperties)
|
|
610
|
+
|
|
611
|
+
this.propertiesModified = currentProperties !== originalProperties
|
|
612
|
+
},
|
|
613
|
+
|
|
614
|
+
async copyToClipboard(text) {
|
|
615
|
+
try {
|
|
616
|
+
await navigator.clipboard.writeText(text)
|
|
617
|
+
this.isCopied = true
|
|
618
|
+
setTimeout(() => { this.isCopied = false }, 2000)
|
|
619
|
+
} catch (err) {
|
|
620
|
+
console.error('Failed to copy text:', err)
|
|
621
|
+
}
|
|
622
|
+
},
|
|
623
|
+
|
|
624
|
+
addProperty() {
|
|
625
|
+
let newPropertyName = 'new'
|
|
626
|
+
let counter = 1
|
|
627
|
+
|
|
628
|
+
while (this.schemaItem.properties[newPropertyName]) {
|
|
629
|
+
counter++
|
|
630
|
+
newPropertyName = `new_${counter}`
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
this.$set(this.schemaItem.properties, newPropertyName, {
|
|
634
|
+
type: 'string',
|
|
635
|
+
format: '',
|
|
636
|
+
title: newPropertyName,
|
|
637
|
+
description: '',
|
|
638
|
+
facetable: false,
|
|
639
|
+
})
|
|
640
|
+
|
|
641
|
+
this.checkPropertiesModified()
|
|
642
|
+
this.selectedProperty = newPropertyName
|
|
643
|
+
},
|
|
644
|
+
|
|
645
|
+
updatePropertyKey(oldKey, newKey) {
|
|
646
|
+
if (!newKey || newKey === oldKey) return
|
|
647
|
+
if (this.schemaItem.properties[newKey] && newKey !== oldKey) return
|
|
648
|
+
|
|
649
|
+
const propertyData = { ...this.schemaItem.properties[oldKey] }
|
|
650
|
+
|
|
651
|
+
this.$set(this.schemaItem.properties, newKey, propertyData)
|
|
652
|
+
this.$delete(this.schemaItem.properties, oldKey)
|
|
653
|
+
|
|
654
|
+
this.selectedProperty = newKey
|
|
655
|
+
this.checkPropertiesModified()
|
|
656
|
+
},
|
|
657
|
+
|
|
658
|
+
deleteProperty(key) {
|
|
659
|
+
this.$delete(this.schemaItem.properties, key)
|
|
660
|
+
|
|
661
|
+
if (this.selectedProperty === key) {
|
|
662
|
+
this.selectedProperty = null
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
this.checkPropertiesModified()
|
|
666
|
+
},
|
|
667
|
+
|
|
668
|
+
copyProperty(key) {
|
|
669
|
+
if (this.schemaItem.properties[key]) {
|
|
670
|
+
const originalProperty = JSON.parse(JSON.stringify(this.schemaItem.properties[key]))
|
|
671
|
+
|
|
672
|
+
let newPropertyName = `${key}_copy`
|
|
673
|
+
let counter = 1
|
|
674
|
+
|
|
675
|
+
while (this.schemaItem.properties[newPropertyName]) {
|
|
676
|
+
counter++
|
|
677
|
+
newPropertyName = `${key}_copy_${counter}`
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
const originalTitle = originalProperty.title || key
|
|
681
|
+
this.$set(this.schemaItem.properties, newPropertyName, {
|
|
682
|
+
...originalProperty,
|
|
683
|
+
title: `${originalTitle} (copy)`,
|
|
684
|
+
})
|
|
685
|
+
|
|
686
|
+
this.checkPropertiesModified()
|
|
687
|
+
this.selectedProperty = newPropertyName
|
|
688
|
+
}
|
|
689
|
+
},
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* Handle confirm from CnTabbedFormDialog.
|
|
693
|
+
* Cleans schema data and emits confirm with the cleaned payload.
|
|
694
|
+
*/
|
|
695
|
+
handleConfirm() {
|
|
696
|
+
const cleanedSchemaItem = JSON.parse(JSON.stringify(this.schemaItem))
|
|
697
|
+
Object.keys(cleanedSchemaItem.properties || {}).forEach(key => {
|
|
698
|
+
this.ensureRefIsString(cleanedSchemaItem.properties, key)
|
|
699
|
+
|
|
700
|
+
if (cleanedSchemaItem.properties[key].register
|
|
701
|
+
&& cleanedSchemaItem.properties[key].objectConfiguration
|
|
702
|
+
&& cleanedSchemaItem.properties[key].objectConfiguration.register) {
|
|
703
|
+
delete cleanedSchemaItem.properties[key].register
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
if (cleanedSchemaItem.properties[key].items
|
|
707
|
+
&& cleanedSchemaItem.properties[key].items.register
|
|
708
|
+
&& cleanedSchemaItem.properties[key].items.objectConfiguration
|
|
709
|
+
&& cleanedSchemaItem.properties[key].items.objectConfiguration.register) {
|
|
710
|
+
delete cleanedSchemaItem.properties[key].items.register
|
|
711
|
+
}
|
|
712
|
+
})
|
|
713
|
+
|
|
714
|
+
this.$emit('confirm', cleanedSchemaItem)
|
|
715
|
+
},
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* Set the result of the save operation. Call this from the parent
|
|
719
|
+
* after the API call completes.
|
|
720
|
+
*
|
|
721
|
+
* @param {{ success?: boolean, error?: string }} resultData - result data
|
|
722
|
+
* @public
|
|
723
|
+
*/
|
|
724
|
+
setResult(resultData) {
|
|
725
|
+
this.$refs.dialog.setResult(resultData)
|
|
726
|
+
if (resultData.success) {
|
|
727
|
+
this.originalProperties = JSON.parse(JSON.stringify(this.schemaItem.properties || {}))
|
|
728
|
+
this.propertiesModified = false
|
|
729
|
+
}
|
|
730
|
+
},
|
|
731
|
+
|
|
732
|
+
migratePropertyToNewStructure(key) {
|
|
733
|
+
if (!this.schemaItem.properties[key]) return
|
|
734
|
+
|
|
735
|
+
const property = this.schemaItem.properties[key]
|
|
736
|
+
|
|
737
|
+
if (property.$ref && property.register && !property.objectConfiguration?.register) {
|
|
738
|
+
if (!property.objectConfiguration) {
|
|
739
|
+
this.$set(this.schemaItem.properties[key], 'objectConfiguration', { handling: 'related-object' })
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
const registerId = typeof property.register === 'object' && property.register.id
|
|
743
|
+
? property.register.id
|
|
744
|
+
: property.register
|
|
745
|
+
|
|
746
|
+
this.$set(this.schemaItem.properties[key].objectConfiguration, 'register', registerId)
|
|
747
|
+
|
|
748
|
+
if (property.$ref) {
|
|
749
|
+
let schemaSlug = property.$ref
|
|
750
|
+
if (schemaSlug.includes('/')) {
|
|
751
|
+
schemaSlug = schemaSlug.substring(schemaSlug.lastIndexOf('/') + 1)
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
const referencedSchema = this.findSchemaBySlug(schemaSlug)
|
|
755
|
+
if (referencedSchema) {
|
|
756
|
+
this.$set(this.schemaItem.properties[key].objectConfiguration, 'schema', referencedSchema.id)
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
if (property.items && property.items.$ref && property.items.register && !property.items.objectConfiguration?.register) {
|
|
762
|
+
if (!property.items.objectConfiguration) {
|
|
763
|
+
this.$set(this.schemaItem.properties[key].items, 'objectConfiguration', { handling: 'related-object' })
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
const registerId = typeof property.items.register === 'object' && property.items.register.id
|
|
767
|
+
? property.items.register.id
|
|
768
|
+
: property.items.register
|
|
769
|
+
|
|
770
|
+
this.$set(this.schemaItem.properties[key].items.objectConfiguration, 'register', registerId)
|
|
771
|
+
|
|
772
|
+
if (property.items.$ref) {
|
|
773
|
+
let schemaSlug = property.items.$ref
|
|
774
|
+
if (schemaSlug.includes('/')) {
|
|
775
|
+
schemaSlug = schemaSlug.substring(schemaSlug.lastIndexOf('/') + 1)
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
const referencedSchema = this.findSchemaBySlug(schemaSlug)
|
|
779
|
+
if (referencedSchema) {
|
|
780
|
+
this.$set(this.schemaItem.properties[key].items.objectConfiguration, 'schema', referencedSchema.id)
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
},
|
|
785
|
+
},
|
|
786
|
+
}
|
|
787
|
+
</script>
|