From f8c2cafa17643706b84172d8414370af12bb5a0f Mon Sep 17 00:00:00 2001 From: Krasimir Chobantonov Date: Sun, 29 Dec 2024 03:07:43 -0500 Subject: [PATCH 01/37] initial mixed renderer support --- .../examples/src/examples/mixed-object.ts | 85 ++++ packages/examples/src/examples/mixed.ts | 47 +++ packages/examples/src/index.ts | 4 + .../src/complex/MixedRenderer.entry.ts | 75 ++++ .../vue-vuetify/src/complex/MixedRenderer.vue | 394 ++++++++++++++++++ packages/vue-vuetify/src/complex/index.ts | 4 + 6 files changed, 609 insertions(+) create mode 100644 packages/examples/src/examples/mixed-object.ts create mode 100644 packages/examples/src/examples/mixed.ts create mode 100644 packages/vue-vuetify/src/complex/MixedRenderer.entry.ts create mode 100644 packages/vue-vuetify/src/complex/MixedRenderer.vue diff --git a/packages/examples/src/examples/mixed-object.ts b/packages/examples/src/examples/mixed-object.ts new file mode 100644 index 000000000..8e41e7971 --- /dev/null +++ b/packages/examples/src/examples/mixed-object.ts @@ -0,0 +1,85 @@ +/* + The MIT License + + Copyright (c) 2017-2019 EclipseSource Munich + https://github.com/eclipsesource/jsonforms + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ +import { registerExamples } from '../register'; + +export const schema = { + $schema: 'http://json-schema.org/draft-07/schema#', + title: 'Mixed Types Example', + type: 'object', + properties: { + mixedSimple: { + type: ['string', 'boolean', 'integer'], + description: 'This property can be a string, boolean, or integer.', + }, + nullableString: { + type: ['string', 'null'], + }, + mixed: { + type: [ + 'array', + 'boolean', + 'integer', + 'null', + 'number', + 'object', + 'string', + ], + }, + }, + required: ['mixedSimple'], +}; + +export const uischema = { + type: 'VerticalLayout', + elements: [ + { + type: 'Control', + scope: '#/properties/mixedSimple', + }, + { + type: 'Control', + scope: '#/properties/nullableString', + }, + { + type: 'Control', + scope: '#/properties/mixed', + }, + ], +}; + +const data = { + mixedSimple: 'String', + nullableString: null as any, +}; + +registerExamples([ + { + name: 'mixed-object', + label: 'Mixed Object', + data, + schema, + uischema, + }, +]); diff --git a/packages/examples/src/examples/mixed.ts b/packages/examples/src/examples/mixed.ts new file mode 100644 index 000000000..1de6590a1 --- /dev/null +++ b/packages/examples/src/examples/mixed.ts @@ -0,0 +1,47 @@ +/* + The MIT License + + Copyright (c) 2017-2019 EclipseSource Munich + https://github.com/eclipsesource/jsonforms + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ +import { registerExamples } from '../register'; + +export const schema = { + $schema: 'http://json-schema.org/draft-07/schema#', + type: ['string', 'boolean', 'integer', 'null'], +}; + +export const uischema = { + type: 'Control', + scope: '#/', +}; + +const data = undefined as any; + +registerExamples([ + { + name: 'mixed', + label: 'Mixed', + data, + schema, + uischema, + }, +]); diff --git a/packages/examples/src/index.ts b/packages/examples/src/index.ts index c50414362..750168278 100644 --- a/packages/examples/src/index.ts +++ b/packages/examples/src/index.ts @@ -75,6 +75,8 @@ import * as additionalErrors from './examples/additional-errors'; import * as multiEnumWithLabelAndDesc from './examples/enum-multi-with-label-and-desc'; import * as additionalProperties from './examples/additional-properties'; import * as login from './examples/login'; +import * as mixed from './examples/mixed'; +import * as mixedObject from './examples/mixed-object'; import * as string from './examples/string'; export * from './register'; export * from './example'; @@ -134,6 +136,8 @@ export { additionalErrors, additionalProperties, login, + mixed, + mixedObject, issue_1884, arrayWithDefaults, string, diff --git a/packages/vue-vuetify/src/complex/MixedRenderer.entry.ts b/packages/vue-vuetify/src/complex/MixedRenderer.entry.ts new file mode 100644 index 000000000..20e543057 --- /dev/null +++ b/packages/vue-vuetify/src/complex/MixedRenderer.entry.ts @@ -0,0 +1,75 @@ +import { + hasType, + isControl, + rankWith, + resolveSchema, + type JsonFormsRendererRegistryEntry, + type JsonSchema, + type Scopable, + type TesterContext, + type UISchemaElement, +} from '@jsonforms/core'; + +import mixedRenderer from './MixedRenderer.vue'; +import isEmpty from 'lodash/isEmpty'; + +export const isMixedSchema = ( + uischema: UISchemaElement & Scopable, + schema: JsonSchema, + context: TesterContext, +) => { + if (!schema || typeof schema !== 'object') { + return false; + } + + if (Array.isArray(schema.type)) { + return true; + } + if (schema.type === 'object') { + const schemaPath = uischema.scope; + if (schemaPath && !isEmpty(schemaPath)) { + let currentDataSchema = schema; + currentDataSchema = resolveSchema( + schema, + schemaPath, + context?.rootSchema, + ); + if (currentDataSchema === undefined) { + return false; + } + if (Array.isArray(currentDataSchema.type)) { + return true; + } + } + } + + return false; +}; + +export const isMixedControl = ( + uischema: UISchemaElement, + schema: JsonSchema, + context: TesterContext, +) => + isMixedSchema(uischema, schema, context) && + (isControl(uischema) || isDefaultGenUiSchema(uischema)); + +export const entry: JsonFormsRendererRegistryEntry = { + renderer: mixedRenderer, + tester: rankWith(20, isMixedControl), +}; + +function isDefaultGenUiSchema(uischema: UISchemaElement): boolean { + const elements = (uischema as any)?.elements; + let result = false; + if ( + (uischema.type === 'VerticalLayout' || uischema.type === 'Group') && + Array.isArray(elements) + ) { + if (elements.length == 1) { + // if the uischema is the default then take control + result = elements[0].scope === '#' && elements[0].type === 'Control'; + } + } + return result; +} diff --git a/packages/vue-vuetify/src/complex/MixedRenderer.vue b/packages/vue-vuetify/src/complex/MixedRenderer.vue new file mode 100644 index 000000000..156da4fc9 --- /dev/null +++ b/packages/vue-vuetify/src/complex/MixedRenderer.vue @@ -0,0 +1,394 @@ + + + + diff --git a/packages/vue-vuetify/src/complex/index.ts b/packages/vue-vuetify/src/complex/index.ts index 335567cd5..adeb84282 100644 --- a/packages/vue-vuetify/src/complex/index.ts +++ b/packages/vue-vuetify/src/complex/index.ts @@ -5,6 +5,7 @@ export { default as EnumArrayRenderer } from './EnumArrayRenderer.vue'; export { default as ObjectRenderer } from './ObjectRenderer.vue'; export { default as OneOfRenderer } from './OneOfRenderer.vue'; export { default as OneOfTabRenderer } from './OneOfTabRenderer.vue'; +export { default as MixedRenderer } from './MixedRenderer.vue'; import { entry as allOfRendererEntry } from './AllOfRenderer.entry'; import { entry as anyOfRendererEntry } from './AnyOfRenderer.entry'; @@ -13,6 +14,7 @@ import { entry as enumArrayRendererEntry } from './EnumArrayRenderer.entry'; import { entry as objectRendererEntry } from './ObjectRenderer.entry'; import { entry as oneOfRendererEntry } from './OneOfRenderer.entry'; import { entry as oneOfTabRendererEntry } from './OneOfTabRenderer.entry'; +import { entry as mixedRendererEntry } from './MixedRenderer.entry'; export const complexRenderers = [ allOfRendererEntry, @@ -22,6 +24,7 @@ export const complexRenderers = [ objectRendererEntry, oneOfRendererEntry, oneOfTabRendererEntry, + mixedRendererEntry, ]; export { @@ -32,4 +35,5 @@ export { objectRendererEntry, oneOfRendererEntry, oneOfTabRendererEntry, + mixedRendererEntry, }; From c078710b65efdf73b836c27cc5788892781639ce Mon Sep 17 00:00:00 2001 From: Krasimir Chobantonov Date: Sun, 29 Dec 2024 03:10:13 -0500 Subject: [PATCH 02/37] organize imports --- packages/vue-vuetify/src/complex/MixedRenderer.entry.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/vue-vuetify/src/complex/MixedRenderer.entry.ts b/packages/vue-vuetify/src/complex/MixedRenderer.entry.ts index 20e543057..3ef85f9a7 100644 --- a/packages/vue-vuetify/src/complex/MixedRenderer.entry.ts +++ b/packages/vue-vuetify/src/complex/MixedRenderer.entry.ts @@ -1,5 +1,4 @@ import { - hasType, isControl, rankWith, resolveSchema, @@ -10,8 +9,8 @@ import { type UISchemaElement, } from '@jsonforms/core'; -import mixedRenderer from './MixedRenderer.vue'; import isEmpty from 'lodash/isEmpty'; +import mixedRenderer from './MixedRenderer.vue'; export const isMixedSchema = ( uischema: UISchemaElement & Scopable, From ece92acddca6f7ba9fc7ccc54a087ef5dd9b87a0 Mon Sep 17 00:00:00 2001 From: Krasimir Chobantonov Date: Sun, 29 Dec 2024 03:14:44 -0500 Subject: [PATCH 03/37] handle correctly the clear where if the property that is cleared in not part of the object that has the property defined in properties then to not clear it - e.g. send undefined value --- .../src/complex/ObjectRenderer.vue | 15 +- .../components/AdditionalProperties.vue | 334 ++++++++++-------- .../AnyOfStringOrEnumControlRenderer.vue | 8 +- .../src/controls/EnumControlRenderer.vue | 5 +- .../src/controls/IntegerControlRenderer.vue | 8 +- .../controls/MultiStringControlRenderer.vue | 5 +- .../src/controls/NumberControlRenderer.vue | 5 +- .../src/controls/StringControlRenderer.vue | 5 +- .../controls/StringMaskControlRenderer.vue | 5 +- .../AutocompleteEnumControlRenderer.vue | 5 +- .../AutocompleteOneOfEnumControlRenderer.vue | 5 +- packages/vue-vuetify/src/util/composition.ts | 32 ++ 12 files changed, 257 insertions(+), 175 deletions(-) diff --git a/packages/vue-vuetify/src/complex/ObjectRenderer.vue b/packages/vue-vuetify/src/complex/ObjectRenderer.vue index cd191974b..58c554e6a 100644 --- a/packages/vue-vuetify/src/complex/ObjectRenderer.vue +++ b/packages/vue-vuetify/src/complex/ObjectRenderer.vue @@ -10,7 +10,7 @@ :cells="control.cells" /> @@ -20,11 +20,8 @@ import { Generate, findUISchema, - isObjectControl, - rankWith, type ControlElement, type GroupLayout, - type JsonFormsRendererRegistryEntry, type UISchemaElement, } from '@jsonforms/core'; import { @@ -51,6 +48,7 @@ const controlRenderer = defineComponent({ }, setup(props: RendererProps) { const control = useVuetifyControl(useJsonFormsControlWithDetail(props)); + const nested = useNested('object'); return { ...control, @@ -62,17 +60,14 @@ const controlRenderer = defineComponent({ hasAdditionalProperties(): boolean { return ( !isEmpty(this.control.schema.patternProperties) || - isObject(this.control.schema.additionalProperties) - // do not support - additionalProperties === true - since then the type should be any and we won't know what kind of renderer we should use for new properties + isObject(this.control.schema.additionalProperties) || + this.control.schema.additionalProperties === true ); }, showAdditionalProperties(): boolean { const showAdditionalProperties = this.control.uischema.options?.showAdditionalProperties; - return ( - showAdditionalProperties === undefined || - showAdditionalProperties === true - ); + return showAdditionalProperties === true; }, detailUiSchema(): UISchemaElement { const uiSchemaGenerator = () => { diff --git a/packages/vue-vuetify/src/complex/components/AdditionalProperties.vue b/packages/vue-vuetify/src/complex/components/AdditionalProperties.vue index fb970b3ed..94db318b3 100644 --- a/packages/vue-vuetify/src/complex/components/AdditionalProperties.vue +++ b/packages/vue-vuetify/src/complex/components/AdditionalProperties.vue @@ -1,103 +1,101 @@ From 81c81dbae77bc11136a347ce8a7677f5c7c81775 Mon Sep 17 00:00:00 2001 From: Krasimir Chobantonov Date: Sun, 29 Dec 2024 04:33:22 -0500 Subject: [PATCH 10/37] make sure that we use new date so that the proxyModel is correctly notified for the new value --- packages/vue-vuetify/src/controls/DateControlRenderer.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/vue-vuetify/src/controls/DateControlRenderer.vue b/packages/vue-vuetify/src/controls/DateControlRenderer.vue index 855ab3a09..9f56ec918 100644 --- a/packages/vue-vuetify/src/controls/DateControlRenderer.vue +++ b/packages/vue-vuetify/src/controls/DateControlRenderer.vue @@ -411,7 +411,7 @@ const controlRenderer = defineComponent({ proxyModel: Ref, ): void { if (this.showActions) { - const date = proxyModel.value ?? new Date(); + const date = new Date(proxyModel.value ?? new Date()); date.setFullYear(year); proxyModel.value = date; } else { @@ -434,7 +434,7 @@ const controlRenderer = defineComponent({ proxyModel: Ref, ): void { if (this.showActions) { - const date = proxyModel.value ?? new Date(); + const date = new Date(proxyModel.value ?? new Date()); date.setMonth(month); proxyModel.value = date; } else { From 3a4f2b96ffaa8ee72d14730c66450baa94dbd8a9 Mon Sep 17 00:00:00 2001 From: Krasimir Chobantonov Date: Sun, 29 Dec 2024 05:55:55 -0500 Subject: [PATCH 11/37] no need to check props?.schema?.properties --- packages/vue-vuetify/src/util/composition.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/vue-vuetify/src/util/composition.ts b/packages/vue-vuetify/src/util/composition.ts index f064fc92c..de1d7e7be 100644 --- a/packages/vue-vuetify/src/util/composition.ts +++ b/packages/vue-vuetify/src/util/composition.ts @@ -491,11 +491,7 @@ export const determineClearValue = ( ) => { const { uischema } = props; - if ( - !isScoped(uischema) || - props.schema?.type !== 'object' || - typeof props?.schema?.properties !== 'object' - ) { + if (!isScoped(uischema) || props.schema?.type !== 'object') { return defaultValue; } From 6b5dca8b8df303c3bd81f01a02189ea4a147d3a8 Mon Sep 17 00:00:00 2001 From: Krasimir Chobantonov Date: Sun, 29 Dec 2024 05:56:23 -0500 Subject: [PATCH 12/37] proper implementation for the optional translator --- .../src/controls/components/ValidationBadge.vue | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/vue-vuetify/src/controls/components/ValidationBadge.vue b/packages/vue-vuetify/src/controls/components/ValidationBadge.vue index 735b5a655..aed51d31d 100644 --- a/packages/vue-vuetify/src/controls/components/ValidationBadge.vue +++ b/packages/vue-vuetify/src/controls/components/ValidationBadge.vue @@ -17,7 +17,11 @@ -

{{ t('Validation Errors', 'Validation Errors') }}

+

+ {{ + t ? t('Validation Errors', 'Validation Errors') : 'Validation Errors' + }} +