diff --git a/Gruntfile.js b/Gruntfile.js index 96ace8aafa..87c89e0e52 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -205,6 +205,15 @@ module.exports = function (grunt) { ], dest: paths.adminJsLib }, + { + expand: true, + cwd: 'node_modules/vue-i18n/dist', + src: [ + // not using runtime 'cos we need the messages compiler + 'vue-i18n.global.js' + ], + dest: paths.adminJsLib + }, ], }, }, diff --git a/modules/admin/app/assets/css/admin/_admin.scss b/modules/admin/app/assets/css/admin/_admin.scss index d1a96563e5..05f1991331 100644 --- a/modules/admin/app/assets/css/admin/_admin.scss +++ b/modules/admin/app/assets/css/admin/_admin.scss @@ -80,11 +80,11 @@ body > .admin-content > .flash { background-color: $gray-100; } } +} - .markdown-helper { - font-style: italic; - cursor: help; - } +.markdown-helper { + font-style: italic; + cursor: pointer; } .inline-remove > a:hover { @@ -92,6 +92,24 @@ body > .admin-content > .flash { cursor: pointer; } +.form-legend { + font-size: $font-size-base; + + dl { + display: flex; + flex-wrap: wrap; + + dt { + @include make-col(3); + font-weight: bold; + } + + dd { + @include make-col(9); + } + } +} + .address-form, .concept-description-form { background-color: $gray-100; @@ -127,8 +145,11 @@ body > .admin-content > .flash { padding-top: $margin-md; } -.add-address { +.add-address, +.add-concept +{ float: right; + margin-bottom: $margin-md; } .admin-help-notice { @@ -315,12 +336,18 @@ pre.code-format { margin-bottom: $margin-md; } -.add-description { - margin-bottom: $margin-md; - @include clearfix(); - a { - float: right; +.control-elements { + .add-inline-element:not(.btn) { + display: block; + background-color: $gray-100; + border-bottom: 1px solid darken($gray-100, 5%); + padding: 0 $margin-xs; + color: $gray-700; + } + .add-inline-element:hover:not(.btn) { + background-color: darken($gray-100, 5%); } + } .inline-element-block-controls { @@ -436,6 +463,20 @@ pre.code-format { } } +// Metadata validation +.metadata-validation { + summary { + font-size: $font-size-lg; + @extend .alert, .alert-warning; + } + .validation-errors { + @extend .alert, .alert-danger; + } + .validation-warnings { + @extend .alert, .alert-warning; + } +} + // Vue Single-page applications %expanding-row { display: flex; diff --git a/modules/admin/app/assets/css/dmeditor.scss b/modules/admin/app/assets/css/dmeditor.scss index 179e9a1ada..addeafbddb 100644 --- a/modules/admin/app/assets/css/dmeditor.scss +++ b/modules/admin/app/assets/css/dmeditor.scss @@ -12,3 +12,30 @@ $active-table-row: #e7f1ff; overflow: auto; flex-basis: 0; } + +.section { + background-color: $gray-100; + font-weight: bold; +} + +.fm-list td.fm-actions { + width: 5rem; +} + +.fm-see-also a + a { + display: inline-block; +} + +.fm-usage { + white-space: nowrap; + +} + +.fm-list td:last-child a { + margin-left: $margin-xs; +} + +#delete-metadata { + margin-right: auto; +} + diff --git a/modules/admin/app/assets/js/admin.js b/modules/admin/app/assets/js/admin.js index 350c6c4d38..c8cb3b104f 100644 --- a/modules/admin/app/assets/js/admin.js +++ b/modules/admin/app/assets/js/admin.js @@ -188,7 +188,7 @@ jQuery(function ($) { event.preventDefault(); var container = $(event.target).closest(".inline-formset"); - var set = container.children(".inline-element-list"); + var set = container.children(".control-elements").children(".inline-element-list").first(); var prefix = container.data("prefix"); if (!prefix) { throw "No prefix found for formset"; @@ -212,6 +212,10 @@ jQuery(function ($) { // And a help popover addPopover($elem.find("textarea,input")); + + // Focus the first input + $elem.find("input,textarea").first().focus(); + }).on("click", ".remove-inline-element", function (event) { event.preventDefault(); $(this) diff --git a/modules/admin/app/assets/js/datasets/components/_manager-convert.vue b/modules/admin/app/assets/js/datasets/components/_manager-convert.vue index 3f180bf347..062ead04e0 100644 --- a/modules/admin/app/assets/js/datasets/components/_manager-convert.vue +++ b/modules/admin/app/assets/js/datasets/components/_manager-convert.vue @@ -20,7 +20,6 @@ import _takeWhile from 'lodash/takeWhile'; import _find from 'lodash/find'; import {DataTransformation} from "../types"; - let initialConvertState = function (config) { return { ingesting: {}, diff --git a/modules/admin/app/assets/js/dmeditor/__mocks__/api.ts b/modules/admin/app/assets/js/dmeditor/__mocks__/api.ts index 83edd95ea5..df03adcec5 100644 --- a/modules/admin/app/assets/js/dmeditor/__mocks__/api.ts +++ b/modules/admin/app/assets/js/dmeditor/__mocks__/api.ts @@ -1,4 +1,5 @@ import { + EntityType, EntityTypeMetadata, EntityTypeMetadataInfo, FieldMetadata, @@ -17,9 +18,9 @@ export default class EntityTypeMetadataApi { this.etData = { Country: { entityType: "Country", - name: "Country", - description: "Country description", - created: "2021-06-01" + name: "Country", + description: "Country description", + created: "2021-06-01" } }; @@ -31,6 +32,7 @@ export default class EntityTypeMetadataApi { name: "History", description: "Test", usage: "mandatory", + defaultVal: null, seeAlso: ["seeAlso"], created: "2021-06-01" } @@ -39,34 +41,34 @@ export default class EntityTypeMetadataApi { } - list(): Promise> { + list(): Promise> { return Promise.resolve(this.etData); } - get(entityType: string): Promise { + get(entityType: EntityType): Promise { return Promise.resolve(this.etData[entityType] || null); } - save(entityType: string, data: EntityTypeMetadataInfo): Promise { + save(entityType: EntityType, data: EntityTypeMetadataInfo): Promise { return Promise.resolve({ entityType, ...data } as EntityTypeMetadata); } - delete(entityType: string): Promise { + delete(entityType: EntityType): Promise { return Promise.resolve(true); } - listFields(entityType?: string): Promise> { + listFields(entityType?: EntityType): Promise> { return Promise.resolve(this.fData); } - getField(entityType: string, id: string): Promise { + getField(entityType: EntityType, id: string): Promise { return Promise.resolve(this.fData[entityType].find(f => f.id === id) || null); } - saveField(entityType: string, id: string, data: FieldMetadataInfo): Promise { + saveField(entityType: EntityType, id: string, data: FieldMetadataInfo): Promise { return Promise.resolve({ entityType, id, @@ -74,7 +76,7 @@ export default class EntityTypeMetadataApi { } as FieldMetadata); } - deleteField(entityType: string, id: string): Promise { + deleteField(entityType: EntityType, id: string): Promise { // remove the item from fData: this.fData[entityType] = this.fData[entityType].filter(f => f.id !== id); return Promise.resolve(true); @@ -83,7 +85,7 @@ export default class EntityTypeMetadataApi { templates(): Promise { return Promise.resolve({ Country: { - "" : ["history"] + "_" : ["history"] } } as FieldMetadataTemplates); } diff --git a/modules/admin/app/assets/js/dmeditor/api.ts b/modules/admin/app/assets/js/dmeditor/api.ts index e7a92aba19..9c2bfc3179 100644 --- a/modules/admin/app/assets/js/dmeditor/api.ts +++ b/modules/admin/app/assets/js/dmeditor/api.ts @@ -1,5 +1,6 @@ import axios from "axios"; import { + EntityType, EntityTypeMetadata, EntityTypeMetadataInfo, FieldMetadata, FieldMetadataInfo, FieldMetadataTemplates, @@ -30,36 +31,36 @@ export default class EntityTypeMetadataApi { withCredentials: true, }).then(r => r.data); } - list(): Promise> { - return EntityTypeMetadataApi.call>(this.service.list(), {}); + list(): Promise> { + return EntityTypeMetadataApi.call>(this.service.list(), {}); } - get(entityType: string): Promise> { - return EntityTypeMetadataApi.call>(this.service.list(), {}, {entityType}); + get(entityType: EntityType): Promise> { + return EntityTypeMetadataApi.call>(this.service.list(), {}, {entityType}); } - save(entityType: string, data: EntityTypeMetadataInfo): Promise { + save(entityType: EntityType, data: EntityTypeMetadataInfo): Promise { return EntityTypeMetadataApi.call(this.service.save(entityType), data); } - delete(entityType: string): Promise { + delete(entityType: EntityType): Promise { return EntityTypeMetadataApi.call(this.service.delete(entityType)); } - listFields(entityType?: string): Promise> { + listFields(entityType?: EntityType): Promise> { let params = entityType ? {entityType} : {}; - return EntityTypeMetadataApi.call>(this.service.listFields(), {}, params) + return EntityTypeMetadataApi.call>(this.service.listFields(), {}, params) } - getField(entityType: string, id: string): Promise { + getField(entityType: EntityType, id: string): Promise { return EntityTypeMetadataApi.call(this.service.getField(entityType, id)); } - saveField(entityType: string, id: string, data: FieldMetadataInfo): Promise { + saveField(entityType: EntityType, id: string, data: FieldMetadataInfo): Promise { return EntityTypeMetadataApi.call(this.service.saveField(entityType, id), data); } - deleteField(entityType: string, id: string): Promise { + deleteField(entityType: EntityType, id: string): Promise { return EntityTypeMetadataApi.call(this.service.deleteField(entityType, id)); } diff --git a/modules/admin/app/assets/js/dmeditor/app.spec.ts b/modules/admin/app/assets/js/dmeditor/app.spec.ts index 279bfb3ada..563deec623 100644 --- a/modules/admin/app/assets/js/dmeditor/app.spec.ts +++ b/modules/admin/app/assets/js/dmeditor/app.spec.ts @@ -1,10 +1,14 @@ -import {mount, flushPromises} from '@vue/test-utils'; +import {mount, flushPromises,config} from '@vue/test-utils'; import App from './app.vue'; import ListEt from './components/_list-et.vue'; import EntityTypeMetadataApi from "./api"; jest.mock('./api'); +config.global.mocks = { + '$t': (msg) => msg, // return i18n key +} + const defaultInit = { props: { service: {}, @@ -55,7 +59,9 @@ test('deleting a field', async () => { expect(wrapper.find(".fm-list").exists()).toBe(true); expect(wrapper.find("#fm-Country-history").exists()).toBe(true); - await wrapper.find("#fm-Country-history .fm-delete").trigger("click"); + await wrapper.find("#fm-Country-history .fm-edit").trigger("click"); + expect(wrapper.find("#delete-metadata").exists()).toBe(true); + await wrapper.find("#delete-metadata").trigger("click"); expect(wrapper.find(".confirm-delete-field-metadata").exists()).toBe(true); await wrapper.find(".confirm-delete-field-metadata button.accept").trigger("click"); await flushPromises(); diff --git a/modules/admin/app/assets/js/dmeditor/app.vue b/modules/admin/app/assets/js/dmeditor/app.vue index d1947272cd..9f90a1bd61 100644 --- a/modules/admin/app/assets/js/dmeditor/app.vue +++ b/modules/admin/app/assets/js/dmeditor/app.vue @@ -1,6 +1,9 @@ - - diff --git a/modules/admin/app/assets/js/dmeditor/components/_modal-fm-editor.vue b/modules/admin/app/assets/js/dmeditor/components/_modal-fm-editor.vue index 447b9cf9b1..25a5bdbd5f 100644 --- a/modules/admin/app/assets/js/dmeditor/components/_modal-fm-editor.vue +++ b/modules/admin/app/assets/js/dmeditor/components/_modal-fm-editor.vue @@ -1,29 +1,34 @@