Skip to content

Commit 1a5ab61

Browse files
committed
update how schemas are generated based on the data
1 parent d2041e8 commit 1a5ab61

File tree

4 files changed

+214
-51
lines changed

4 files changed

+214
-51
lines changed

packages/example/src/plugins/vuetify.ts

Lines changed: 17 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -132,38 +132,32 @@ function toBlueprint(value: string): Blueprint {
132132
return md1;
133133
}
134134

135-
function createVuetifyInstance(
136-
dark: boolean,
137-
blueprint: string,
138-
variant: string,
139-
iconset: string,
140-
locale: string,
141-
) {
142-
const defaults = variant
135+
function createVuetifyInstance(appStore: ReturnType<typeof useAppStore>) {
136+
const defaults = appStore.variant
143137
? {
144138
VField: {
145-
variant: variant,
139+
variant: appStore.variant,
146140
},
147141
VTextField: {
148-
variant: variant,
142+
variant: appStore.variant,
149143
},
150144
VCombobox: {
151-
variant: variant,
145+
variant: appStore.variant,
152146
},
153147
VSelect: {
154-
variant: variant,
148+
variant: appStore.variant,
155149
},
156150
VAutocomplete: {
157-
variant: variant,
151+
variant: appStore.variant,
158152
},
159153
VTextarea: {
160-
variant: variant,
154+
variant: appStore.variant,
161155
},
162156
VNumberInput: {
163-
variant: variant,
157+
variant: appStore.variant,
164158
},
165159
VDateInput: {
166-
variant: variant,
160+
variant: appStore.variant,
167161
},
168162
VCheckbox: { color: 'primary' },
169163
}
@@ -174,23 +168,23 @@ function createVuetifyInstance(
174168
return createVuetify({
175169
components,
176170
directives,
177-
blueprint: toBlueprint(blueprint),
171+
blueprint: toBlueprint(appStore.blueprint),
178172
locale: {
179-
locale: locale,
173+
locale: appStore.jsonforms.locale,
180174
fallback: 'en',
181175
messages: { en, bg, de },
182176
},
183177
icons: {
184-
defaultSet: iconset, // Set the default icon set
178+
defaultSet: appStore.iconset, // Set the default icon set
185179
sets: {
186180
mdi,
187181
fa,
188182
},
189-
aliases: toIconSetAliases(iconset),
183+
aliases: toIconSetAliases(appStore.iconset),
190184
},
191185
theme: {
192-
defaultTheme: dark ? 'dark' : 'light',
193-
themes: getCustomThemes(blueprint).reduce(
186+
defaultTheme: appStore.dark ? 'dark' : 'light',
187+
themes: getCustomThemes(appStore.blueprint).reduce(
194188
(acc: Record<string, ThemeDefinition>, current) => {
195189
acc[current.name] = current;
196190
return acc;
@@ -204,13 +198,7 @@ function createVuetifyInstance(
204198

205199
export function buildVuetify() {
206200
const appStore = useAppStore();
207-
const vuetify = createVuetifyInstance(
208-
appStore.dark,
209-
appStore.blueprint,
210-
appStore.variant,
211-
appStore.iconset,
212-
appStore.jsonforms.locale,
213-
);
201+
const vuetify = createVuetifyInstance(appStore);
214202

215203
watch(
216204
() => appStore.jsonforms.locale,

packages/jsonforms-vuetify-renderers/src/components/ResolvedJsonForms.vue

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import {
5050
} from '../core';
5151
import { resolveUISchemaValidations } from '../util';
5252
import type Ajv from 'ajv';
53+
import { generateJsonSchema } from './schema';
5354
5455
const props = defineProps<{
5556
state: JsonFormsProps;
@@ -86,16 +87,18 @@ watch(
8687
// if we do not update that then at the moment the core will
8788
// use the previously generated schema since it doesn't update the generated schema upon data changes
8889
// fixes: https://github.com/eclipsesource/jsonforms/pull/2478
89-
const generatorData = isObject(value) ? value : {};
90-
resolvedSchema.schema = Generate.jsonSchema(generatorData);
90+
resolvedSchema.schema = generateJsonSchema(value);
9191
if (!props.state.uischema) {
9292
// override the uischema as well so that we can specify some vuetify specific options for container
9393
resolvedUiSchema.value = Generate.uiSchema(
9494
resolvedSchema.schema,
95-
'VerticalLayout',
95+
undefined,
9696
undefined,
9797
resolvedSchema.schema,
98-
);
98+
) ?? {
99+
type: 'VerticalLayout',
100+
elements: [],
101+
};
99102
100103
if (resolvedUiSchema.value.type === 'VerticalLayout') {
101104
resolvedUiSchema.value.options = {
@@ -110,6 +113,30 @@ watch(
110113
};
111114
}
112115
}
116+
} else if (!props.state.uischema) {
117+
// override the uischema as well so that we can specify some vuetify specific options for container
118+
resolvedUiSchema.value = Generate.uiSchema(
119+
resolvedSchema.schema ?? props.state.schema,
120+
undefined,
121+
undefined,
122+
resolvedSchema.schema,
123+
) ?? {
124+
type: 'VerticalLayout',
125+
elements: [],
126+
};
127+
128+
if (resolvedUiSchema.value.type === 'VerticalLayout') {
129+
resolvedUiSchema.value.options = {
130+
vuetify: {
131+
'v-container': {
132+
fluid: true,
133+
},
134+
'v-row': {
135+
'no-gutters': true,
136+
},
137+
},
138+
};
139+
}
113140
}
114141
},
115142
);
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
// This file is copied over from the JsonForms core and modified until this is merged https://github.com/eclipsesource/jsonforms/pull/2478
2+
3+
import type { JsonSchema4 } from '@jsonforms/core';
4+
5+
const ADDITIONAL_PROPERTIES = 'additionalProperties';
6+
const REQUIRED_PROPERTIES = 'required';
7+
8+
type Properties = { [property: string]: JsonSchema4 };
9+
10+
const distinct = (
11+
properties: any[],
12+
discriminator: (item: any) => string,
13+
): JsonSchema4[] => {
14+
const known: { [property: string]: boolean } = {};
15+
16+
return properties.filter((item) => {
17+
const discriminatorValue = discriminator(item);
18+
if (Object.prototype.hasOwnProperty.call(known, discriminatorValue)) {
19+
return false;
20+
} else {
21+
known[discriminatorValue] = true;
22+
return true;
23+
}
24+
});
25+
};
26+
27+
class Gen {
28+
constructor(
29+
private findOption: (props: Properties) => (optionName: string) => any,
30+
) {}
31+
32+
// TODO fix @typescript-eslint/ban-types
33+
// eslint-disable-next-line @typescript-eslint/ban-types
34+
schemaObject = (data: Object): JsonSchema4 => {
35+
const props: Properties = this.properties(data);
36+
const schema: JsonSchema4 = {
37+
type: 'object',
38+
properties: props,
39+
additionalProperties: this.findOption(props)(ADDITIONAL_PROPERTIES),
40+
};
41+
const required = this.findOption(props)(REQUIRED_PROPERTIES);
42+
if (required.length > 0) {
43+
schema.required = required;
44+
}
45+
46+
return schema;
47+
};
48+
49+
properties = (data: any): Properties => {
50+
const emptyProps: Properties = {};
51+
52+
return Object.keys(data).reduce((acc: Properties, propName: string) => {
53+
acc[propName] = this.property(data[propName]);
54+
55+
return acc;
56+
}, emptyProps);
57+
};
58+
59+
property = (data: any): JsonSchema4 => {
60+
switch (typeof data) {
61+
case 'string':
62+
return { type: 'string' };
63+
case 'boolean':
64+
return { type: 'boolean' };
65+
case 'number':
66+
if (Number.isInteger(data)) {
67+
return { type: 'integer' };
68+
}
69+
70+
return { type: 'number' };
71+
case 'object':
72+
if (data == null) {
73+
return { type: 'null' };
74+
}
75+
76+
return this.schemaObjectOrArray(data);
77+
default:
78+
return {};
79+
}
80+
};
81+
82+
schemaObjectOrArray = (data: any): JsonSchema4 => {
83+
if (data instanceof Array) {
84+
return this.schemaArray(data as any[]);
85+
} else {
86+
return this.schemaObject(data);
87+
}
88+
};
89+
90+
schemaArray = (data: any[]): JsonSchema4 => {
91+
if (data.length > 0) {
92+
const allProperties: JsonSchema4[] = data.map(this.property);
93+
const uniqueProperties = distinct(allProperties, (prop) =>
94+
JSON.stringify(prop),
95+
);
96+
if (uniqueProperties.length === 1) {
97+
return {
98+
type: 'array',
99+
items: uniqueProperties[0],
100+
};
101+
} else {
102+
return {
103+
type: 'array',
104+
items: {
105+
oneOf: uniqueProperties,
106+
},
107+
};
108+
}
109+
} else {
110+
return {
111+
type: 'array',
112+
items: {},
113+
};
114+
}
115+
};
116+
}
117+
118+
/**
119+
* Generate a JSON schema based on the given data and any additional options.
120+
* @param {Object} instance the data to create a JSON schema for
121+
* @param {any} options any additional options that may alter the generated JSON schema
122+
* @returns {JsonSchema} the generated schema
123+
*/
124+
export const generateJsonSchema = (
125+
// TODO fix @typescript-eslint/ban-types
126+
// eslint-disable-next-line @typescript-eslint/ban-types
127+
instance: Object,
128+
options: any = {},
129+
): JsonSchema4 => {
130+
const findOption =
131+
(props: Properties) =>
132+
(optionName: string): any => {
133+
switch (optionName) {
134+
case ADDITIONAL_PROPERTIES:
135+
if (
136+
Object.prototype.hasOwnProperty.call(options, ADDITIONAL_PROPERTIES)
137+
) {
138+
return options[ADDITIONAL_PROPERTIES];
139+
}
140+
141+
return true;
142+
case REQUIRED_PROPERTIES:
143+
if (
144+
Object.prototype.hasOwnProperty.call(options, REQUIRED_PROPERTIES)
145+
) {
146+
return options[REQUIRED_PROPERTIES](props);
147+
}
148+
149+
return Object.keys(props);
150+
default:
151+
return;
152+
}
153+
};
154+
155+
const gen = new Gen(findOption);
156+
157+
return gen.property(instance);
158+
};

packages/jsonforms-vuetify-webcomponent/src/plugins/vuetify.ts

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -78,15 +78,10 @@ function toBlueprint(value: string): Blueprint {
7878
return md1;
7979
}
8080

81-
function createVuetifyInstance(
82-
dark: boolean,
83-
blueprint: string,
84-
iconset: string,
85-
locale: string,
86-
) {
81+
function createVuetifyInstance(appStore: ReturnType<typeof useAppStore>) {
8782
const theme = {
88-
defaultTheme: dark ? 'dark' : 'light',
89-
themes: getCustomThemes(blueprint).reduce(
83+
defaultTheme: appStore.dark ? 'dark' : 'light',
84+
themes: getCustomThemes(appStore.blueprint).reduce(
9085
(acc: Record<string, ThemeDefinition>, current) => {
9186
acc[current.name] = current;
9287
return acc;
@@ -98,32 +93,27 @@ function createVuetifyInstance(
9893
return createVuetify({
9994
components,
10095
directives,
101-
blueprint: toBlueprint(blueprint),
96+
blueprint: toBlueprint(appStore.blueprint),
10297
locale: {
103-
locale: locale,
98+
locale: appStore.locale,
10499
fallback: 'en',
105100
messages: { en, bg, de },
106101
},
107102
icons: {
108-
defaultSet: iconset, // Set the default icon set
103+
defaultSet: appStore.iconset, // Set the default icon set
109104
sets: {
110105
mdi,
111106
fa,
112107
},
113-
aliases: toIconSetAliases(iconset),
108+
aliases: toIconSetAliases(appStore.iconset),
114109
},
115110
theme: theme,
116111
});
117112
}
118113

119114
export function buildVuetify() {
120115
const appStore = useAppStore();
121-
const vuetify = createVuetifyInstance(
122-
appStore.dark,
123-
appStore.blueprint,
124-
appStore.iconset,
125-
appStore.locale,
126-
);
116+
const vuetify = createVuetifyInstance(appStore);
127117

128118
watch(
129119
() => appStore.locale,

0 commit comments

Comments
 (0)