diff --git a/.gitignore b/.gitignore index faf7cc95a3..edb129357e 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,6 @@ *.sw* .run node_modules -ignore \ No newline at end of file +ignore +.kilo* +.claude* \ No newline at end of file diff --git a/client/web/compose/src/components/ModuleFields/Configurator/basic.vue b/client/web/compose/src/components/ModuleFields/Configurator/basic.vue index 89949ebf15..6ce0d554d3 100644 --- a/client/web/compose/src/components/ModuleFields/Configurator/basic.vue +++ b/client/web/compose/src/components/ModuleFields/Configurator/basic.vue @@ -30,6 +30,62 @@ {{ $t('defaultValue') }} + + {{ $t('globalField.makeGlobal') }} + + + + + + + + + + + + + + {{ $t('globalField.selector.load') }} + + + + +
0 + }, + + globalFieldOptions () { + return this.globalFields.map(gf => ({ + value: gf.config.globalField.fieldID, + text: gf.name || gf.config.globalField.fieldID, + })) + }, }, watch: { @@ -391,6 +474,84 @@ export default { this.initMocks() } }, + + handleGlobalMasterToggle (value) { + if (!this.field.config) { + this.$set(this.field, 'config', {}) + } + + if (value) { + this.$set(this.field.config, 'globalField', { + namespaceID: this.namespace.namespaceID, + fieldID: this.field.fieldID, + }) + } else { + this.$delete(this.field.config, 'globalField') + } + }, + + handleGlobalFieldSelect () { + const fieldID = this.selectedGlobalField + if (!fieldID) { + if (this.field.config && this.field.config.globalField) { + this.$delete(this.field.config, 'globalField') + } + return + } + + const ns = this.$store.getters['namespace/getByID'](this.namespace.namespaceID) + if (!ns) return + + const globalFields = ns.fields || [] + const selectedField = globalFields.find(gf => gf.config.globalField.fieldID === fieldID) + + if (!selectedField) { + return + } + + const localHint = this.field.options.hint + const localDescription = this.field.options.description + + this.field.isRequired = selectedField.isRequired !== undefined ? selectedField.isRequired : this.field.isRequired + this.field.isMulti = selectedField.isMulti !== undefined ? selectedField.isMulti : this.field.isMulti + this.field.defaultValue = (selectedField.defaultValue && selectedField.defaultValue.length) + ? [...selectedField.defaultValue] + : this.field.defaultValue + this.field.expressions = selectedField.expressions + ? { ...selectedField.expressions } + : this.field.expressions + + const globalOptions = selectedField.options || {} + this.field.options = { + ...globalOptions, + hint: localHint, + description: localDescription, + } + + if (selectedField.config) { + this.$set(this.field, 'config', { + ...selectedField.config, + globalField: { + namespaceID: this.namespace.namespaceID, + fieldID: selectedField.config.globalField.fieldID, + }, + }) + } else { + if (!this.field.config) { + this.$set(this.field, 'config', {}) + } + this.$set(this.field.config, 'globalField', { + namespaceID: this.namespace.namespaceID, + fieldID: selectedField.fieldID, + }) + } + + if (this.field.defaultValue.length) { + this.initMocks(this.field.defaultValue) + } + + this.showValueExpr = !!(this.field.expressions && this.field.expressions.value) + }, }, } diff --git a/client/web/compose/src/store/namespace.js b/client/web/compose/src/store/namespace.js index d95f44d9f0..70a7720920 100644 --- a/client/web/compose/src/store/namespace.js +++ b/client/web/compose/src/store/namespace.js @@ -37,6 +37,16 @@ export default function (ComposeAPI) { set (state) { return state.set }, + + getGlobalFieldsByKind (state, { getByID }) { + return (ID, kind) => { + const ns = getByID(ID) + if (!ns || !ns.fields) { + return [] + } + return ns.fields.filter(f => f.kind === kind) + } + }, }, actions: { diff --git a/client/web/compose/src/views/Admin/Modules/Edit.vue b/client/web/compose/src/views/Admin/Modules/Edit.vue index 0d7f123b8a..ed94d72bf8 100644 --- a/client/web/compose/src/views/Admin/Modules/Edit.vue +++ b/client/web/compose/src/views/Admin/Modules/Edit.vue @@ -619,6 +619,7 @@ export default { ...mapGetters({ pages: 'page/set', previousPage: 'ui/previousPage', + namespaces: 'namespace/set', }), title () { @@ -763,6 +764,7 @@ export default { createModule: 'module/create', deleteModule: 'module/delete', deletePage: 'page/delete', + updateNamespace: 'namespace/update', }), handleNewField () { @@ -785,6 +787,60 @@ export default { } }, + async updateNamespaceGlobalFields (savedModule, namespaceSource) { + const { fields = [] } = savedModule + const ns = namespaceSource || this.namespace + + const globalFields = fields.filter(f => { + return f.config && f.config.globalField + }) + + for (const field of globalFields) { + const globalFieldConfig = field.config.globalField + if (!globalFieldConfig.fieldID) { + console.error('Global field missing fieldID:', field.name) + throw new Error(this.$t('notification:module.globalField.invalid')) + } + } + + const existingGlobalFields = ns.fields || [] + + const fieldsMap = new Map() + existingGlobalFields.forEach(field => { + if (field.fieldID !== NoID) { + fieldsMap.set(field.fieldID, field) + } + }) + + globalFields.forEach(field => { + const globalFieldConfig = field.config.globalField + const fieldID = globalFieldConfig.fieldID + + const newGlobalField = { + fieldID, + name: field.label || field.name, + kind: field.kind, + options: field.options, + isRequired: field.isRequired, + isMulti: field.isMulti, + defaultValue: field.defaultValue ? [...field.defaultValue] : [], + expressions: field.expressions ? { ...field.expressions } : {}, + config: field.config ? { ...field.config } : {}, + } + fieldsMap.set(fieldID, newGlobalField) + }) + + const combinedFields = Array.from(fieldsMap.values()) + + return this.updateNamespace({ + ...ns, + fields: combinedFields, + }).then((updatedNs) => { + this.$store.dispatch('namespace/load', { force: true }) + return updatedNs.fields + }) + }, + onDiscoverySettingsSave (changes) { this.module.config = { ...this.module.config, ...changes } }, @@ -840,6 +896,22 @@ export default { module = await this.updateModule({ ...module, fields }) } + await Promise.all([ + this.$store.dispatch('namespace/load', { + namespaceID: this.namespace.namespaceID, + force: true, + }), + this.findModuleByID({ + ...module, + namespace: this.namespace, + force: true, + }), + ]) + + const currentNamespace = this.namespaces.find(n => n.namespaceID === this.namespace.namespaceID) + + await this.updateNamespaceGlobalFields(module, currentNamespace || this.namespace) + this.loading = true this.module = new compose.Module({ ...module }, this.namespace) @@ -862,7 +934,23 @@ export default { toggleProcessing(false) }) } else { - this.updateModule({ ...module, resourceTranslationLanguage }).then(module => { + this.updateModule({ ...module, resourceTranslationLanguage }).then(async module => { + await Promise.all([ + this.$store.dispatch('namespace/load', { + namespaceID: this.namespace.namespaceID, + force: true, + }), + this.findModuleByID({ + ...module, + namespace: this.namespace, + force: true, + }), + ]) + + const currentNamespace = this.namespaces.find(n => n.namespaceID === this.namespace.namespaceID) + + await this.updateNamespaceGlobalFields(module, currentNamespace || this.namespace) + this.module = new compose.Module({ ...module }, this.namespace) this.initialModuleState = this.module.clone() diff --git a/client/web/compose/src/views/Admin/Modules/List.vue b/client/web/compose/src/views/Admin/Modules/List.vue index f6b79411e1..6b7d030e96 100644 --- a/client/web/compose/src/views/Admin/Modules/List.vue +++ b/client/web/compose/src/views/Admin/Modules/List.vue @@ -1,4 +1,4 @@ -"