@openremote/or-json-forms 1.8.0-snapshot.20250725074716 → 1.8.0-snapshot.20250725120000
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 +113 -113
- package/custom-elements.json +2 -2
- package/dist/umd/index.js +1105 -1105
- package/dist/umd/index.js.map +1 -1
- package/dist/umd/index.orbundle.js +1163 -1163
- package/dist/umd/index.orbundle.js.map +1 -1
- package/lib/base-element.js +57 -1
- package/lib/controls/control-array-element.js +277 -83
- package/lib/controls/control-base-element.js +48 -1
- package/lib/controls/control-input-element.js +157 -20
- package/lib/index.js +141 -7
- package/lib/layouts/layout-base-element.js +29 -1
- package/lib/layouts/layout-vertical-element.js +340 -128
- package/lib/standard-renderers.js +188 -12
- package/lib/styles.js +156 -148
- package/lib/util.js +291 -23
- package/package.json +4 -4
package/lib/util.js
CHANGED
|
@@ -1,23 +1,291 @@
|
|
|
1
|
-
import{composeWithUi
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
1
|
+
import { composeWithUi, createDefaultValue, deriveTypes, getAjv, getData, getRenderers, getSchema, hasShowRule, isVisible, Resolve, } from "@jsonforms/core";
|
|
2
|
+
import { DefaultColor5, Util } from "@openremote/core";
|
|
3
|
+
import { InputType } from "@openremote/or-mwc-components/or-mwc-input";
|
|
4
|
+
import { i18next } from "@openremote/or-translate";
|
|
5
|
+
import "@openremote/or-components/or-ace-editor";
|
|
6
|
+
import { html, unsafeCSS } from "lit";
|
|
7
|
+
import { createRef, ref } from 'lit/directives/ref.js';
|
|
8
|
+
import { unknownTemplate } from "./standard-renderers";
|
|
9
|
+
import { OrMwcDialog, showDialog } from "@openremote/or-mwc-components/or-mwc-dialog";
|
|
10
|
+
export function getTemplateFromProps(state, props) {
|
|
11
|
+
if (!state || !props) {
|
|
12
|
+
return html ``;
|
|
13
|
+
}
|
|
14
|
+
const renderers = props.renderers || getRenderers({ jsonforms: Object.assign({}, state) });
|
|
15
|
+
const schema = props.schema;
|
|
16
|
+
const uischema = props.uischema;
|
|
17
|
+
let template;
|
|
18
|
+
if (renderers && schema && uischema && state.core) {
|
|
19
|
+
const orderedRenderers = renderers.map(r => [r, r.tester(uischema, schema, { rootSchema: resolveSubSchemasRecursive(schema, state.core.schema), config: state.config })]).sort((a, b) => b[1] - a[1]);
|
|
20
|
+
const renderer = orderedRenderers && orderedRenderers.length > 0 ? orderedRenderers[0] : undefined;
|
|
21
|
+
if (renderer && renderer[1] !== -1) {
|
|
22
|
+
template = renderer[0].renderer(state, props);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
template = unknownTemplate();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return template;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* For a given anyOf schema array this will try and extract a common const property which can be used as a discriminator
|
|
32
|
+
* when creating instances
|
|
33
|
+
*/
|
|
34
|
+
export function getCombinatorInfos(schemas, rootSchema) {
|
|
35
|
+
return schemas.map(schema => {
|
|
36
|
+
let constProperty;
|
|
37
|
+
let constValue;
|
|
38
|
+
let creator;
|
|
39
|
+
const titleAndDescription = findSchemaTitleAndDescription(schema, rootSchema);
|
|
40
|
+
if (schema.$ref) {
|
|
41
|
+
schema = Resolve.schema(schema, '', rootSchema);
|
|
42
|
+
}
|
|
43
|
+
if (Array.isArray(schema.allOf)) {
|
|
44
|
+
schema = Resolve.schema(schema, "allOf", rootSchema);
|
|
45
|
+
}
|
|
46
|
+
if (deriveTypes(schema).every(type => type === "object")) {
|
|
47
|
+
const props = getSchemaObjectProperties(schema);
|
|
48
|
+
const constProp = props.find(([propName, propSchema]) => getSchemaConst(propSchema) !== undefined);
|
|
49
|
+
if (constProp) {
|
|
50
|
+
constProperty = constProp[0];
|
|
51
|
+
constValue = getSchemaConst(constProp[1]);
|
|
52
|
+
creator = () => {
|
|
53
|
+
const obj = {};
|
|
54
|
+
obj[constProp[0]] = getSchemaConst(constProp[1]);
|
|
55
|
+
return obj;
|
|
56
|
+
};
|
|
57
|
+
if (!titleAndDescription[0]) {
|
|
58
|
+
titleAndDescription[0] = getSchemaConst(constProp[1]);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
creator = () => createDefaultValue(schema, rootSchema);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
// Assume a primitive type that can be instantiated with default value creator
|
|
67
|
+
creator = () => createDefaultValue(schema, rootSchema);
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
title: titleAndDescription[0],
|
|
71
|
+
description: titleAndDescription[1],
|
|
72
|
+
defaultValueCreator: creator,
|
|
73
|
+
constProperty: constProperty,
|
|
74
|
+
constValue: constValue
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
export function getSchemaConst(schema) {
|
|
79
|
+
if (!schema) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (schema.const !== undefined) {
|
|
83
|
+
return schema.const;
|
|
84
|
+
}
|
|
85
|
+
if (Array.isArray(schema.enum) && schema.enum.length === 1) {
|
|
86
|
+
return schema.enum[0];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export function getSchemaPicker(rootSchema, resolvedSchema, path, keyword, label, selectedCallback) {
|
|
90
|
+
const combinatorInfos = getCombinatorInfos(resolvedSchema[keyword], rootSchema);
|
|
91
|
+
const options = combinatorInfos.map((combinatorInfo, index) => [index + "", combinatorInfo.title || i18next.t("schema.title.indexedItem", { index: index })]);
|
|
92
|
+
const pickerUpdater = (index) => {
|
|
93
|
+
const matchedInfo = combinatorInfos[index];
|
|
94
|
+
selectedCallback(matchedInfo);
|
|
95
|
+
};
|
|
96
|
+
const pickerLabel = label ? i18next.t("schema.anyOfPickerLabel", { label: label }) : i18next.t("type");
|
|
97
|
+
return html `
|
|
98
|
+
<or-mwc-input class="any-of-picker" .label="${pickerLabel}" .type="${InputType.SELECT}" .options="${options}" @or-mwc-input-changed="${(ev) => pickerUpdater(Number(ev.detail.value))}"></or-mwc-input>
|
|
99
|
+
`;
|
|
100
|
+
}
|
|
101
|
+
// TODO: use i18n translations from jsonforms/core
|
|
102
|
+
export function findSchemaTitleAndDescription(schema, rootSchema) {
|
|
103
|
+
let title;
|
|
104
|
+
if (schema.$ref) {
|
|
105
|
+
title = getLabelFromScopeOrRef(schema.$ref);
|
|
106
|
+
schema = Resolve.schema(schema, '', rootSchema);
|
|
107
|
+
}
|
|
108
|
+
if (schema.title) {
|
|
109
|
+
return [schema.title, schema.description];
|
|
110
|
+
}
|
|
111
|
+
if (schema.allOf) {
|
|
112
|
+
const resolvedSchema = Resolve.schema(schema, "allOf", rootSchema);
|
|
113
|
+
const titledSchema = resolvedSchema.allOf.find((allOfSchema) => {
|
|
114
|
+
return !!allOfSchema.title;
|
|
115
|
+
});
|
|
116
|
+
if (titledSchema) {
|
|
117
|
+
return [titledSchema.title, titledSchema.description];
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return [i18next.t("schema.title." + title, { defaultValue: Util.camelCaseToSentenceCase(title) }), undefined];
|
|
121
|
+
}
|
|
122
|
+
function getLabelFromScopeOrRef(scopeOrRef) {
|
|
123
|
+
return scopeOrRef.substr(scopeOrRef.lastIndexOf("/") + 1);
|
|
124
|
+
}
|
|
125
|
+
function getSchemaObjectProperties(schema) {
|
|
126
|
+
let props = [];
|
|
127
|
+
if (schema.allOf) {
|
|
128
|
+
props = schema.allOf.map(schema => schema.properties ? Object.entries(schema.properties) : []).flat();
|
|
129
|
+
}
|
|
130
|
+
else if (schema.properties) {
|
|
131
|
+
props = Object.entries(schema.properties);
|
|
132
|
+
}
|
|
133
|
+
return props;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Copied from eclipse source code to inject global definitions into the validating schema otherwise AJV will fail
|
|
137
|
+
* to compile the schema - not perfect but works for our cases
|
|
138
|
+
*/
|
|
139
|
+
export function mapStateToCombinatorRendererProps(state, ownProps, keyword) {
|
|
140
|
+
const { uischema } = ownProps;
|
|
141
|
+
const path = composeWithUi(uischema, ownProps.path);
|
|
142
|
+
const rootSchema = getSchema(state);
|
|
143
|
+
const resolvedSchema = Resolve.schema(ownProps.schema || rootSchema, uischema.scope, rootSchema);
|
|
144
|
+
const visible = ownProps.visible === undefined || hasShowRule(uischema)
|
|
145
|
+
? isVisible(uischema, getData(state), ownProps.path, getAjv(state))
|
|
146
|
+
: ownProps.visible;
|
|
147
|
+
const id = ownProps.id;
|
|
148
|
+
const data = Resolve.data(getData(state), path);
|
|
149
|
+
const ajv = state.jsonforms.core.ajv;
|
|
150
|
+
const schema = resolvedSchema || rootSchema;
|
|
151
|
+
const _schema = Resolve.schema(schema, keyword, rootSchema);
|
|
152
|
+
const structuralKeywords = [
|
|
153
|
+
'required',
|
|
154
|
+
'additionalProperties',
|
|
155
|
+
'type',
|
|
156
|
+
'enum',
|
|
157
|
+
'const'
|
|
158
|
+
];
|
|
159
|
+
const dataIsValid = (errors) => {
|
|
160
|
+
return (!errors ||
|
|
161
|
+
errors.length === 0 ||
|
|
162
|
+
!errors.find(e => structuralKeywords.indexOf(e.keyword) !== -1));
|
|
163
|
+
};
|
|
164
|
+
let indexOfFittingSchema = -1;
|
|
165
|
+
// TODO instead of compiling the combinator subschemas we can compile the original schema
|
|
166
|
+
// without the combinator alternatives and then revalidate and check the errors for the
|
|
167
|
+
// element
|
|
168
|
+
for (let i = 0; i < _schema.length; i++) {
|
|
169
|
+
try {
|
|
170
|
+
const schema = Object.assign({ definitions: rootSchema.definitions }, _schema[i]);
|
|
171
|
+
const valFn = ajv.compile(schema);
|
|
172
|
+
valFn(data);
|
|
173
|
+
if (dataIsValid(valFn.errors)) {
|
|
174
|
+
indexOfFittingSchema = i;
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
console.debug("Combinator subschema is not self contained, can't hand it over to AJV");
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
data,
|
|
184
|
+
path,
|
|
185
|
+
schema,
|
|
186
|
+
rootSchema,
|
|
187
|
+
visible,
|
|
188
|
+
id,
|
|
189
|
+
indexOfFittingSchema,
|
|
190
|
+
uischemas: state.jsonforms.uischemas,
|
|
191
|
+
uischema: uischema,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
export function getLabel(schema, rootSchema, uiElementLabel, uiElementScope) {
|
|
195
|
+
if (uiElementLabel) {
|
|
196
|
+
return uiElementLabel;
|
|
197
|
+
}
|
|
198
|
+
const titleAndDesc = findSchemaTitleAndDescription(schema, rootSchema);
|
|
199
|
+
if (titleAndDesc[0]) {
|
|
200
|
+
return titleAndDesc[0];
|
|
201
|
+
}
|
|
202
|
+
if (uiElementScope) {
|
|
203
|
+
return Util.camelCaseToSentenceCase(getLabelFromScopeOrRef(uiElementScope));
|
|
204
|
+
}
|
|
205
|
+
return undefined;
|
|
206
|
+
}
|
|
207
|
+
export function resolveSubSchemasRecursive(schema, rootSchema, keyword) {
|
|
208
|
+
const combinators = keyword ? [keyword] : ["allOf", "anyOf", "oneOf"];
|
|
209
|
+
if (schema.$ref) {
|
|
210
|
+
return resolveSubSchemasRecursive(Resolve.schema(rootSchema, schema.$ref, rootSchema), rootSchema);
|
|
211
|
+
}
|
|
212
|
+
combinators.forEach((combinator) => {
|
|
213
|
+
const schemas = schema[combinator];
|
|
214
|
+
if (schemas) {
|
|
215
|
+
schema[combinator] = schemas.map(subSchema => resolveSubSchemasRecursive(subSchema, rootSchema));
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
if (schema.items) {
|
|
219
|
+
if (Array.isArray(schema.items)) {
|
|
220
|
+
schema.items = schema.items.map((itemSchema) => resolveSubSchemasRecursive(itemSchema, rootSchema));
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
schema.items = resolveSubSchemasRecursive(schema.items, rootSchema);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (schema.properties) {
|
|
227
|
+
Object.keys(schema.properties).forEach((prop) => schema.properties[prop] = resolveSubSchemasRecursive(schema.properties[prop], rootSchema));
|
|
228
|
+
}
|
|
229
|
+
return schema;
|
|
230
|
+
}
|
|
231
|
+
export const controlWithoutLabel = (scope) => ({
|
|
232
|
+
type: 'Control',
|
|
233
|
+
scope: scope,
|
|
234
|
+
label: false
|
|
235
|
+
});
|
|
236
|
+
export const showJsonEditor = (title, value, updateCallback) => {
|
|
237
|
+
const editorRef = createRef();
|
|
238
|
+
const updateBtnRef = createRef();
|
|
239
|
+
const onEditorEdit = () => {
|
|
240
|
+
// Disable update button whilst edit in progress
|
|
241
|
+
updateBtnRef.value.disabled = true;
|
|
242
|
+
};
|
|
243
|
+
const onEditorChanged = (ev) => {
|
|
244
|
+
const valid = ev.detail.valid;
|
|
245
|
+
updateBtnRef.value.disabled = !valid;
|
|
246
|
+
};
|
|
247
|
+
const dialog = showDialog(new OrMwcDialog()
|
|
248
|
+
.setContent(html `
|
|
249
|
+
<or-ace-editor ${ref(editorRef)} @or-ace-editor-edit="${() => onEditorEdit()}" @or-ace-editor-changed="${(ev) => onEditorChanged(ev)}" .value="${value}"></or-ace-editor>
|
|
250
|
+
`)
|
|
251
|
+
.setActions([
|
|
252
|
+
{
|
|
253
|
+
actionName: "cancel",
|
|
254
|
+
content: "cancel"
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
default: true,
|
|
258
|
+
actionName: "update",
|
|
259
|
+
action: () => {
|
|
260
|
+
const editor = editorRef.value;
|
|
261
|
+
if (editor.validate()) {
|
|
262
|
+
const data = !!editor.getValue() ? JSON.parse(editor.getValue()) : undefined;
|
|
263
|
+
updateCallback(data);
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
content: html `<or-mwc-input ${ref(updateBtnRef)} disabled .type="${InputType.BUTTON}" label="update"></or-mwc-input>`
|
|
267
|
+
}
|
|
268
|
+
])
|
|
269
|
+
.setHeading(title)
|
|
270
|
+
.setDismissAction(null)
|
|
271
|
+
.setStyles(html `
|
|
272
|
+
<style>
|
|
273
|
+
.mdc-dialog__surface {
|
|
274
|
+
width: 1024px;
|
|
275
|
+
overflow-x: visible !important;
|
|
276
|
+
overflow-y: visible !important;
|
|
277
|
+
}
|
|
278
|
+
#dialog-content {
|
|
279
|
+
border-color: var(--or-app-color5, ${unsafeCSS(DefaultColor5)});
|
|
280
|
+
border-top-width: 1px;
|
|
281
|
+
border-top-style: solid;
|
|
282
|
+
border-bottom-width: 1px;
|
|
283
|
+
border-bottom-style: solid;
|
|
284
|
+
padding: 0;
|
|
285
|
+
overflow: visible;
|
|
286
|
+
height: 60vh;
|
|
287
|
+
}
|
|
288
|
+
</style>
|
|
289
|
+
`));
|
|
290
|
+
};
|
|
291
|
+
//# sourceMappingURL=util.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openremote/or-json-forms",
|
|
3
|
-
"version": "1.8.0-snapshot.
|
|
3
|
+
"version": "1.8.0-snapshot.20250725120000",
|
|
4
4
|
"description": "Renders a JSON schema and provides instance editing",
|
|
5
5
|
"customElements": "custom-elements.json",
|
|
6
6
|
"main": "dist/umd/index.bundle.js",
|
|
@@ -20,14 +20,14 @@
|
|
|
20
20
|
"license": "AGPL-3.0-or-later",
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@jsonforms/core": "^3.5.1",
|
|
23
|
-
"@openremote/or-components": "1.8.0-snapshot.
|
|
24
|
-
"@openremote/or-mwc-components": "1.8.0-snapshot.
|
|
23
|
+
"@openremote/or-components": "1.8.0-snapshot.20250725120000",
|
|
24
|
+
"@openremote/or-mwc-components": "1.8.0-snapshot.20250725120000",
|
|
25
25
|
"@polymer/polymer": "^3.3.1",
|
|
26
26
|
"ajv": "^8.8.2",
|
|
27
27
|
"lit": "^2.0.2"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@openremote/util": "1.8.0-snapshot.
|
|
30
|
+
"@openremote/util": "1.8.0-snapshot.20250725120000"
|
|
31
31
|
},
|
|
32
32
|
"publishConfig": {
|
|
33
33
|
"access": "public"
|