diff --git a/storefront/package-lock.json b/storefront/package-lock.json index 993c52f..4db1971 100644 --- a/storefront/package-lock.json +++ b/storefront/package-lock.json @@ -5063,6 +5063,11 @@ "schema-utils": "^2.6.5" } }, + "file-saver": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.2.tgz", + "integrity": "sha512-Wz3c3XQ5xroCxd1G8b7yL0Ehkf0TC9oYC6buPFkNnU9EnaPlifeAFCyCh+iewXTyFRcg0a6j3J7FmJsIhlhBdw==" + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -6788,6 +6793,11 @@ "verror": "1.10.0" } }, + "jstoxml": { + "version": "1.6.10", + "resolved": "https://registry.npmjs.org/jstoxml/-/jstoxml-1.6.10.tgz", + "integrity": "sha512-Y610p1VGKBGpPGF8QB67JMnEK66J4maFDovguPyJIw7e3Aj3jsmvC1lCl3YckIkgUjFOg4J3q4j+aRaevG0J4g==" + }, "jszip": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.5.0.tgz", diff --git a/storefront/package.json b/storefront/package.json index ba4fc76..43cafd8 100644 --- a/storefront/package.json +++ b/storefront/package.json @@ -23,7 +23,9 @@ "@angular/platform-browser-dynamic": "~10.0.5", "@angular/router": "~10.0.5", "ang-jsoneditor": "^1.9.4", + "file-saver": "^2.0.2", "jsoneditor": "^9.0.3", + "jstoxml": "^1.6.10", "rxjs": "~6.5.5", "tslib": "^2.0.0", "zone.js": "~0.10.3" diff --git a/storefront/src/app/app.module.ts b/storefront/src/app/app.module.ts index 31f375c..bdb9482 100644 --- a/storefront/src/app/app.module.ts +++ b/storefront/src/app/app.module.ts @@ -1,6 +1,6 @@ import {BrowserModule} from '@angular/platform-browser'; import {NgModule} from '@angular/core'; -import {HttpClientModule, HTTP_INTERCEPTORS} from '@angular/common/http'; +import {HTTP_INTERCEPTORS, HttpClientModule} from '@angular/common/http'; import {FlexLayoutModule} from '@angular/flex-layout'; import {AppRoutingModule} from './app-routing.module'; @@ -12,6 +12,7 @@ import {AppComponent} from './app.component'; import {StoreRoomService} from './service/storeroom/store-room.service'; import {DefaultModule} from './layout/default/default.module'; import {HttpErrorInterceptor} from './interceptors/http-error.interceptor'; +import {JsonConverterService} from './service/json-converter/json-converter.service'; @NgModule({ declarations: [ @@ -33,7 +34,8 @@ import {HttpErrorInterceptor} from './interceptors/http-error.interceptor'; provide: HTTP_INTERCEPTORS, useClass: HttpErrorInterceptor, multi: true - } + }, + JsonConverterService ], bootstrap: [AppComponent] }) diff --git a/storefront/src/app/modules/editor/editor.component.html b/storefront/src/app/modules/editor/editor.component.html index 3f8a463..8383010 100644 --- a/storefront/src/app/modules/editor/editor.component.html +++ b/storefront/src/app/modules/editor/editor.component.html @@ -3,9 +3,9 @@
- - - + + +
diff --git a/storefront/src/app/modules/editor/editor.component.ts b/storefront/src/app/modules/editor/editor.component.ts index 563c418..21b7da7 100644 --- a/storefront/src/app/modules/editor/editor.component.ts +++ b/storefront/src/app/modules/editor/editor.component.ts @@ -4,6 +4,7 @@ import {StoreRoomService} from '../../service/storeroom/store-room.service'; import {ActivatedRoute, Router} from '@angular/router'; import {IError} from 'ang-jsoneditor/jsoneditor/jsoneditoroptions'; import {MatSnackBar} from '@angular/material/snack-bar'; +import {JsonConverterService} from '../../service/json-converter/json-converter.service'; @Component({ selector: 'app-editor', @@ -22,7 +23,7 @@ export class EditorComponent implements OnInit, OnDestroy { private metaSchema: any; constructor(private storeroomClient: StoreRoomService, private route: ActivatedRoute, private snackBar: MatSnackBar, - private router: Router) { + private router: Router, private jsonConverterService: JsonConverterService) { this.editorOptions = new JsonEditorOptions(); this.editorOptions2 = new JsonEditorOptions(); this.editorOptions.modes = ['code', 'text', 'tree', 'view']; @@ -31,7 +32,7 @@ export class EditorComponent implements OnInit, OnDestroy { // this.editorOptions.onValidate = () => this.validateSchema(); this.editorOptions.navigationBar = true; // tslint:disable-next-line:max-line-length - this.editorOptions.schema = { $schema: 'http://json-schema.org/draft-07/schema#', $ref: '#/definitions/Welcome', definitions: { Welcome: { type: 'object', additionalProperties: false, properties: { $schema: { type: 'string', format: 'uri', 'qt-uri-protocols': [ 'http' ] }, $id: { type: 'string', format: 'uri', 'qt-uri-protocols': [ 'https' ], 'qt-uri-extensions': [ '.0' ] }, title: { type: 'string' }, description: { type: 'string' }, type: { type: 'string' }, meta: { $ref: '#/definitions/Meta' }, properties: { $ref: '#/definitions/Properties' }, required: { type: 'array', items: { type: 'string' } }, oneof: { type: 'array', items: { $ref: '#/definitions/Oneof' } }, additionalProperties: { type: 'boolean' }, examples: { type: 'array', items: { $ref: '#/definitions/WelcomeExample' } } }, required: [ '$id', '$schema', 'additionalProperties', 'description', 'examples', 'meta', 'oneof', 'properties', 'required', 'title', 'type' ], title: 'Welcome' }, WelcomeExample: { type: 'object', additionalProperties: false, properties: { term: { $ref: '#/definitions/ClassOfOnset' }, classOfOnset: { $ref: '#/definitions/ClassOfOnset' } }, required: [ 'classOfOnset', 'term' ], title: 'WelcomeExample' }, Meta: { type: 'object', additionalProperties: false, properties: { contributors: { type: 'array', items: { $ref: '#/definitions/Contributor' } }, provenance: { type: 'array', items: { $ref: '#/definitions/Contributor' } }, used_by: { type: 'array', items: { $ref: '#/definitions/Contributor' } }, sb_status: { type: 'string' } }, required: [ 'contributors', 'provenance', 'sb_status', 'used_by' ], title: 'Meta' }, Contributor: { type: 'object', additionalProperties: false, properties: { description: { type: 'string' }, id: { type: 'string', 'qt-uri-protocols': [ 'https' ], 'qt-uri-extensions': [ '.rst' ] } }, required: [ 'description' ], title: 'Contributor' }, Oneof: { type: 'object', additionalProperties: false, properties: { properties: { type: 'array', items: { type: 'string' } } }, required: [ 'properties' ], title: 'Oneof' }, Properties: { type: 'object', additionalProperties: false, properties: { term: { $ref: '#/definitions/Term' }, ageOfOnset: { $ref: '#/definitions/AgeOfOnset' }, ageRangeOfOnset: { $ref: '#/definitions/AgeRangeOfOnset' }, classOfOnset: { $ref: '#/definitions/PropertiesClassOfOnset' }, diseaseStage: { $ref: '#/definitions/DiseaseStage' }, tnmFinding: { $ref: '#/definitions/DiseaseStage' } }, required: [ 'ageOfOnset', 'ageRangeOfOnset', 'classOfOnset', 'diseaseStage', 'term', 'tnmFinding' ], title: 'Properties' }, AgeOfOnset: { type: 'object', additionalProperties: false, properties: { allof: { type: 'array', items: { $ref: '#/definitions/AgeOfOnsetAllof' } } }, required: [ 'allof' ], title: 'AgeOfOnset' }, AgeOfOnsetAllof: { type: 'object', additionalProperties: false, properties: { $ref: { type: 'string', format: 'uri', 'qt-uri-protocols': [ 'https' ], 'qt-uri-extensions': [ '.json' ] }, description: { type: 'string' }, examples: { type: 'array', items: { $ref: '#/definitions/Start' } } }, required: [], title: 'AgeOfOnsetAllof' }, Start: { type: 'object', additionalProperties: false, properties: { age: { type: 'string' } }, required: [ 'age' ], title: 'Start' }, AgeRangeOfOnset: { type: 'object', additionalProperties: false, properties: { description: { type: 'string' }, $ref: { type: 'string', format: 'uri', 'qt-uri-protocols': [ 'https' ], 'qt-uri-extensions': [ '.json' ] }, examples: { type: 'array', items: { $ref: '#/definitions/AgeRangeOfOnsetExample' } } }, required: [ '$ref', 'description', 'examples' ], title: 'AgeRangeOfOnset' }, AgeRangeOfOnsetExample: { type: 'object', additionalProperties: false, properties: { start: { $ref: '#/definitions/Start' } }, required: [ 'start' ], title: 'AgeRangeOfOnsetExample' }, PropertiesClassOfOnset: { type: 'object', additionalProperties: false, properties: { description: { type: 'string' }, $ref: { type: 'string', format: 'uri', 'qt-uri-protocols': [ 'https' ], 'qt-uri-extensions': [ '.json' ] }, examples: { type: 'array', items: { $ref: '#/definitions/ClassOfOnset' } } }, required: [ '$ref', 'description', 'examples' ], title: 'PropertiesClassOfOnset' }, DiseaseStage: { type: 'object', additionalProperties: false, properties: { description: { type: 'string' }, type: { type: 'string' }, items: { $ref: '#/definitions/Items' }, examples: { type: 'array', items: { type: 'array', items: { $ref: '#/definitions/ClassOfOnset' } } } }, required: [ 'description', 'examples', 'items', 'type' ], title: 'DiseaseStage' }, ClassOfOnset: { type: 'object', additionalProperties: false, properties: { id: { type: 'string' }, label: { type: 'string' } }, required: [ 'id', 'label' ], title: 'ClassOfOnset' }, Items: { type: 'object', additionalProperties: false, properties: { $ref: { type: 'string', format: 'uri', 'qt-uri-protocols': [ 'https' ], 'qt-uri-extensions': [ '.json' ] } }, required: [ '$ref' ], title: 'Items' }, Term: { type: 'object', additionalProperties: false, properties: { allof: { type: 'array', items: { $ref: '#/definitions/TermAllof' } } }, required: [ 'allof' ], title: 'Term' }, TermAllof: { type: 'object', additionalProperties: false, properties: { $ref: { type: 'string', format: 'uri', 'qt-uri-protocols': [ 'https' ], 'qt-uri-extensions': [ '.json' ] }, description: { type: 'string' }, examples: { type: 'array', items: { $ref: '#/definitions/AllofExample' } } }, required: [], title: 'TermAllof' }, AllofExample: { type: 'object', additionalProperties: false, properties: { id: { type: 'string' } }, required: [ 'id' ], title: 'AllofExample' } } }; + this.editorOptions.schema = { $schema: 'http://json-schema.org/draft-07/schema#', $ref: '#/definitions/MetaSchema', definitions: { MetaSchema: { type: 'object', additionalProperties: false, properties: { $schema: { type: 'string', format: 'uri', 'qt-uri-protocols': [ 'http' ] }, $id: { type: 'string', format: 'uri', 'qt-uri-protocols': [ 'https', 'http' ], 'qt-uri-extensions': [ '.0' ] }, title: { type: 'string' }, description: { type: 'string' }, type: { type: 'string' }, meta: { $ref: '#/definitions/Meta' }, properties: { $ref: '#/definitions/Properties' }, required: { type: 'array', items: { type: 'string' } }, oneof: { type: 'array', items: { $ref: '#/definitions/Oneof' } }, additionalProperties: { type: 'boolean' }, examples: { type: 'array', items: { $ref: '#/definitions/SchemaExample' } } }, required: [ '$id', '$schema', 'additionalProperties', 'description', 'examples', 'meta', 'oneof', 'properties', 'required', 'title', 'type' ], title: 'MetaSchema' }, SchemaExample: { type: 'object', additionalProperties: false, properties: { term: { $ref: '#/definitions/ClassOfOnset' }, classOfOnset: { $ref: '#/definitions/ClassOfOnset' } }, required: [ 'classOfOnset', 'term' ], title: 'SchemaExample' }, Meta: { type: 'object', additionalProperties: false, properties: { contributors: { type: 'array', items: { $ref: '#/definitions/Contributor' } }, provenance: { type: 'array', items: { $ref: '#/definitions/Contributor' } }, used_by: { type: 'array', items: { $ref: '#/definitions/Contributor' } }, sb_status: { type: 'string' } }, required: [ 'contributors', 'provenance', 'sb_status', 'used_by' ], title: 'Meta' }, Contributor: { type: 'object', additionalProperties: false, properties: { description: { type: 'string' }, id: { type: 'string', 'qt-uri-protocols': [ 'https' ], 'qt-uri-extensions': [ '.rst' ] } }, required: [ 'description' ], title: 'Contributor' }, Oneof: { type: 'object', additionalProperties: false, properties: { properties: { type: 'array', items: { type: 'string' } } }, required: [ 'properties' ], title: 'Oneof' }, Properties: { type: 'object', additionalProperties: false, properties: { term: { $ref: '#/definitions/Term' }, ageOfOnset: { $ref: '#/definitions/AgeOfOnset' }, ageRangeOfOnset: { $ref: '#/definitions/AgeRangeOfOnset' }, classOfOnset: { $ref: '#/definitions/PropertiesClassOfOnset' }, diseaseStage: { $ref: '#/definitions/DiseaseStage' }, tnmFinding: { $ref: '#/definitions/DiseaseStage' } }, required: [ 'ageOfOnset', 'ageRangeOfOnset', 'classOfOnset', 'diseaseStage', 'term', 'tnmFinding' ], title: 'Properties' }, AgeOfOnset: { type: 'object', additionalProperties: false, properties: { allof: { type: 'array', items: { $ref: '#/definitions/AgeOfOnsetAllof' } } }, required: [ 'allof' ], title: 'AgeOfOnset' }, AgeOfOnsetAllof: { type: 'object', additionalProperties: false, properties: { $ref: { type: 'string', format: 'uri', 'qt-uri-protocols': [ 'https' ], 'qt-uri-extensions': [ '.json' ] }, description: { type: 'string' }, examples: { type: 'array', items: { $ref: '#/definitions/Start' } } }, required: [], title: 'AgeOfOnsetAllof' }, Start: { type: 'object', additionalProperties: false, properties: { age: { type: 'string' } }, required: [ 'age' ], title: 'Start' }, AgeRangeOfOnset: { type: 'object', additionalProperties: false, properties: { description: { type: 'string' }, $ref: { type: 'string', format: 'uri', 'qt-uri-protocols': [ 'https' ], 'qt-uri-extensions': [ '.json' ] }, examples: { type: 'array', items: { $ref: '#/definitions/AgeRangeOfOnsetExample' } } }, required: [ '$ref', 'description', 'examples' ], title: 'AgeRangeOfOnset' }, AgeRangeOfOnsetExample: { type: 'object', additionalProperties: false, properties: { start: { $ref: '#/definitions/Start' } }, required: [ 'start' ], title: 'AgeRangeOfOnsetExample' }, PropertiesClassOfOnset: { type: 'object', additionalProperties: false, properties: { description: { type: 'string' }, $ref: { type: 'string', format: 'uri', 'qt-uri-protocols': [ 'https' ], 'qt-uri-extensions': [ '.json' ] }, examples: { type: 'array', items: { $ref: '#/definitions/ClassOfOnset' } } }, required: [ '$ref', 'description', 'examples' ], title: 'PropertiesClassOfOnset' }, DiseaseStage: { type: 'object', additionalProperties: false, properties: { description: { type: 'string' }, type: { type: 'string' }, items: { $ref: '#/definitions/Items' }, examples: { type: 'array', items: { type: 'array', items: { $ref: '#/definitions/ClassOfOnset' } } } }, required: [ 'description', 'examples', 'items', 'type' ], title: 'DiseaseStage' }, ClassOfOnset: { type: 'object', additionalProperties: false, properties: { id: { type: 'string' }, label: { type: 'string' } }, required: [ 'id', 'label' ], title: 'ClassOfOnset' }, Items: { type: 'object', additionalProperties: false, properties: { $ref: { type: 'string', format: 'uri', 'qt-uri-protocols': [ 'https' ], 'qt-uri-extensions': [ '.json' ] } }, required: [ '$ref' ], title: 'Items' }, Term: { type: 'object', additionalProperties: false, properties: { allof: { type: 'array', items: { $ref: '#/definitions/TermAllof' } } }, required: [ 'allof' ], title: 'Term' }, TermAllof: { type: 'object', additionalProperties: false, properties: { $ref: { type: 'string', format: 'uri', 'qt-uri-protocols': [ 'https' ], 'qt-uri-extensions': [ '.json' ] }, description: { type: 'string' }, examples: { type: 'array', items: { $ref: '#/definitions/AllofExample' } } }, required: [], title: 'TermAllof' }, AllofExample: { type: 'object', additionalProperties: false, properties: { id: { type: 'string' } }, required: [ 'id' ], title: 'AllofExample' } } }; // tslint:disable-next-line:max-line-length this.data = { $schema: 'http://json-schema.org/draft-07/schema#', $id: 'https://schemablocks.org/schemas/xxxxx', title: 'title', description: 'description', type: 'object', meta: { contributors: [ { description: 'GA4GH Data Working Group' }, { description: 'Jules Jacobsen', id: 'orcid:0000-0002-3265-15918' } ], provenance: [ { description: 'description', id: 'https://github.com/phenopackets/phenopacket-schema/xxxxxx' } ], used_by: [ { description: 'Phenopackets', id: 'https://github.com/phenopackets/phenopacket-schema/xxxx' } ], sb_status: 'implemented' }, properties: { term: { allof: [ { $ref: 'https://schemablocks.org/schemas/sb-phenopackets/v1.0.0/OntologyClass.json' }, { description: 'The identifier of this disease\ne.g. MONDO:0007043, OMIM:101600, Orphanet:710, DOID:14705 (note these are all equivalent)\n', examples: [ { id: 'MONDO:0007043' } ] } ] } }, required: [ 'term' ], additionalProperties: false, examples: [ { term: { id: 'MONDO:0007043', label: 'Pfeiffer syndrome' } } ] }; } @@ -102,6 +103,14 @@ export class EditorComponent implements OnInit, OnDestroy { this.isMetaSchemaViewerDisable = !this.isMetaSchemaViewerDisable; } + public downloadAs(format: string): void { + if (format === 'JSON') { + this.jsonConverterService.jsonToJSONAndDownload(this.editor.get()); + } else if (format === 'XML') { + this.jsonConverterService.jsonToXMLAndDownload(this.editor.get()); + } + } + private validateSchema(): IError[] { console.log('start validating scehma!'); console.log(this.jsonSchema); diff --git a/storefront/src/app/service/json-converter/json-converter.service.spec.ts b/storefront/src/app/service/json-converter/json-converter.service.spec.ts new file mode 100644 index 0000000..0f9c295 --- /dev/null +++ b/storefront/src/app/service/json-converter/json-converter.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { JsonConverterService } from './json-converter.service'; + +describe('JsonConverterService', () => { + let service: JsonConverterService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(JsonConverterService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/storefront/src/app/service/json-converter/json-converter.service.ts b/storefront/src/app/service/json-converter/json-converter.service.ts new file mode 100644 index 0000000..3e0de01 --- /dev/null +++ b/storefront/src/app/service/json-converter/json-converter.service.ts @@ -0,0 +1,33 @@ +import {Injectable} from '@angular/core'; +import * as fileSaver from 'file-saver'; +import {toXML} from 'jstoxml'; + +@Injectable({ + providedIn: 'root' +}) +export class JsonConverterService { + + constructor() { + } + + public jsonToJSONAndDownload(jsonObject: any, fileName?: string): void { + if (!fileName) { + fileName = jsonObject.title + '-' + jsonObject.$id.substring(jsonObject.$id.lastIndexOf('/')) + '.json'; + } + JSON.stringify(jsonObject); + const file = new Blob([JSON.stringify(jsonObject, null, 2)], {type: 'text/javascript'}); + fileSaver.saveAs(file, fileName); + } + + public jsonToXMLAndDownload(jsonObject: any, fileName?: string): void { + if (!fileName) { + fileName = jsonObject.title + '-' + jsonObject.$id.substring(jsonObject.$id.lastIndexOf('/')) + '.xml'; + } + const xmlOptions = { + header: false, + indent: ' ' + }; + const file = new Blob([toXML(jsonObject, xmlOptions)], {type: 'text/xml'}); + fileSaver.saveAs(file, fileName); + } +} diff --git a/storeroom/src/main/resources/test/metaschema.json b/storeroom/src/main/resources/test/metaschema.json index f54e572..3e7e10e 100644 --- a/storeroom/src/main/resources/test/metaschema.json +++ b/storeroom/src/main/resources/test/metaschema.json @@ -1,8 +1,8 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/Welcome", + "$ref": "#/definitions/MetaSchema", "definitions": { - "Welcome": { + "MetaSchema": { "type": "object", "additionalProperties": false, "properties": { @@ -17,7 +17,8 @@ "type": "string", "format": "uri", "qt-uri-protocols": [ - "https" + "https", + "http" ], "qt-uri-extensions": [ ".4" @@ -56,7 +57,7 @@ "examples": { "type": "array", "items": { - "$ref": "#/definitions/WelcomeExample" + "$ref": "#/definitions/SchemaExample" } } }, @@ -73,9 +74,9 @@ "title", "type" ], - "title": "Welcome" + "title": "MetaSchema" }, - "WelcomeExample": { + "SchemaExample": { "type": "object", "additionalProperties": false, "properties": { @@ -90,7 +91,7 @@ "classOfOnset", "term" ], - "title": "WelcomeExample" + "title": "SchemaExample" }, "Meta": { "type": "object", @@ -453,4 +454,4 @@ "title": "AllofExample" } } -} +} \ No newline at end of file