diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 8795316160..fcd35e5a44 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -91,6 +91,7 @@ import { UserMenuComponent } from './user/user-menu/user-menu.component'; import { UsersComponent } from './system/users/users.component'; import { UsersListComponent } from './system/users/users-list/users-list.component'; import { VisualizerComponent } from './project/ontology/ontology-visualizer/visualizer/visualizer.component'; +import { UploadComponent } from './workspace/resource/representation/upload/upload.component'; // translate: AoT requires an exported function for factories export function httpLoaderFactory(httpClient: HttpClient) { @@ -169,6 +170,7 @@ export function httpLoaderFactory(httpClient: HttpClient) { UsersComponent, UsersListComponent, VisualizerComponent, + UploadComponent, ], imports: [ AppRoutingModule, diff --git a/src/app/workspace/resource/representation/upload/upload-file.service.spec.ts b/src/app/workspace/resource/representation/upload/upload-file.service.spec.ts new file mode 100644 index 0000000000..c521c389b4 --- /dev/null +++ b/src/app/workspace/resource/representation/upload/upload-file.service.spec.ts @@ -0,0 +1,99 @@ +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { TestBed } from '@angular/core/testing'; +import { AppInitService, Session, SessionService } from '@dasch-swiss/dsp-ui'; +import { UploadedFileResponse, UploadFileService } from './upload-file.service'; + +describe('UploadFileService', () => { + let service: UploadFileService; + let httpTestingController: HttpTestingController; + + const file = new File(['1'], 'testfile'); + const mockUploadData = new FormData(); + mockUploadData.append('test', file); + + beforeEach(() => { + + const appInitSpy = { + config: { + iiifUrl: 'https://sipi.dasch.swiss/' + } + }; + + const sessionSpy = jasmine.createSpyObj('SessionService', ['getSession']); + + TestBed.configureTestingModule({ + imports: [ + HttpClientTestingModule + ], + providers: [ + { provide: AppInitService, useValue: appInitSpy }, + { provide: SessionService, useValue: sessionSpy }, + ] + }); + + service = TestBed.inject(UploadFileService); + httpTestingController = TestBed.inject(HttpTestingController); + + const sessionServiceSpy = TestBed.inject(SessionService) as jasmine.SpyObj; + + sessionServiceSpy.getSession.and.callFake( + () => { + const session: Session = { + id: 12345, + user: { + name: 'username', + jwt: 'myToken', + lang: 'en', + sysAdmin: false, + projectAdmin: [] + } + }; + + return session; + } + ); + }); + + afterEach(() => { + // after every test, assert that there are no more pending requests. + httpTestingController.verify(); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should return expected file response on upload', done => { + + const expectedResponse: UploadedFileResponse = { + uploadedFiles: [{ + fileType: 'image', + internalFilename: '8R0cJE3TSgB-BssuQyeW1rE.jp2', + originalFilename: 'Screenshot 2020-10-28 at 14.16.34.png', + temporaryUrl: 'http://sipi:1024/tmp/8R0cJE3TSgB-BssuQyeW1rE.jp2' + }] + }; + + service.upload(mockUploadData).subscribe( + res => { + expect(res.uploadedFiles.length).toEqual(1); + expect(res.uploadedFiles[0].internalFilename).toEqual('8R0cJE3TSgB-BssuQyeW1rE.jp2'); + done(); + } + ); + + const httpRequest = httpTestingController.expectOne('https://sipi.dasch.swiss/upload?token=myToken'); + + expect(httpRequest.request.method).toEqual('POST'); + + const expectedFormData = new FormData(); + const mockFile = new File(['1'], 'testfile', { type: 'image/jpeg' }); + + expectedFormData.append(mockFile.name, mockFile); + expect(httpRequest.request.body).toEqual(expectedFormData); + + httpRequest.flush(expectedResponse); + + }); + +}); diff --git a/src/app/workspace/resource/representation/upload/upload-file.service.ts b/src/app/workspace/resource/representation/upload/upload-file.service.ts new file mode 100644 index 0000000000..278795ada8 --- /dev/null +++ b/src/app/workspace/resource/representation/upload/upload-file.service.ts @@ -0,0 +1,45 @@ +import { HttpClient, HttpParams } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { AppInitService, SessionService } from '@dasch-swiss/dsp-ui'; +import { Observable } from 'rxjs'; + +export interface UploadedFile { + fileType: string; + internalFilename: string; + originalFilename: string; + temporaryUrl: string; +} + +export interface UploadedFileResponse { + uploadedFiles: UploadedFile[]; +} + +@Injectable({ + providedIn: 'root' +}) +export class UploadFileService { + + iiifHost: string = (this._init.config['iiifUrl'].substr(-1) === '/') ? this._init.config['iiifUrl'] : this._init.config['iiifUrl'] + '/'; + + constructor( + private readonly _init: AppInitService, + private readonly _http: HttpClient, + private readonly _session: SessionService + ) { } + + /** + * uploads files to SIPI + * @param (file) + */ + upload(file: FormData): Observable { + const baseUrl = `${this.iiifHost}upload`; + + // checks if user is logged in + const jwt = this._session.getSession()?.user.jwt; + const params = new HttpParams().set('token', jwt); + + // --> TODO in order to track the progress change below to true and 'events' + const options = { params, reportProgress: false, observe: 'body' as 'body' }; + return this._http.post(baseUrl, file, options); + } +} diff --git a/src/app/workspace/resource/representation/upload/upload.component.html b/src/app/workspace/resource/representation/upload/upload.component.html new file mode 100644 index 0000000000..63d0a7d317 --- /dev/null +++ b/src/app/workspace/resource/representation/upload/upload.component.html @@ -0,0 +1,64 @@ +
+
+
+ + + cloud_upload + + +
Upload file
+
+ Drag and drop or click to upload +
+
+ + +
+ thumbnail + +
+ +
+ + + + + + + + + + + + + + + + + +
NameSizeLast Modified DateDelete
{{ file.name }}{{ convertBytes(file.size) }}{{ convertDate(file.lastModified) }} + +
+
+
+
+
diff --git a/src/app/workspace/resource/representation/upload/upload.component.scss b/src/app/workspace/resource/representation/upload/upload.component.scss new file mode 100644 index 0000000000..f4c75ca248 --- /dev/null +++ b/src/app/workspace/resource/representation/upload/upload.component.scss @@ -0,0 +1,86 @@ +// overall styles + +.full-width { + width: 100%; +} +.delete-file { + &:hover { + color: red; + } +} + +// drag & drop input +.dd-container { + height: 200px; + margin: 20px 0; + border: 2px solid #000; + border-radius: 2px; + position: relative; + text-align: center; + + &:hover { + cursor: pointer; + background-color: #ddd !important; + } +} +.upload-icon { + width: auto; + font-size: 48px; + padding: 0; + position: inherit; + top: 35%; +} +.title { + bottom: 20%; +} +.bottom-line, +.title { + width: 100%; + position: absolute; + padding: 5px 0; +} +.bottom-line { + bottom: 0; + color: #fff; + background-color: #000; +} + +// thumbnail +.thumbnail { + text-align: center; + + & button { + position: absolute; + padding: 0; + } + + & mat-icon { + position: absolute; + } +} + +// files table +.files-list { + display: flex; + justify-content: space-between; + // width: calc(80% + 4px); + margin: 10px auto; + background: #fff; + color: #000; +} +table { + width: 100%; + border-collapse: collapse; +} +th { + background-color: #9ecbec; + text-align: left; +} +td, +th { + border: 1px solid #000; + padding: 3px; +} +tr td:last-child { + text-align: center; +} diff --git a/src/app/workspace/resource/representation/upload/upload.component.spec.ts b/src/app/workspace/resource/representation/upload/upload.component.spec.ts new file mode 100644 index 0000000000..7f473d7548 --- /dev/null +++ b/src/app/workspace/resource/representation/upload/upload.component.spec.ts @@ -0,0 +1,197 @@ +import { Component, OnInit, ViewChild } from '@angular/core'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { Constants, CreateStillImageFileValue } from '@dasch-swiss/dsp-js'; +import { DspActionModule } from '@dasch-swiss/dsp-ui'; +import { of } from 'rxjs'; +import { UploadFileService } from './upload-file.service'; +import { UploadComponent } from './upload.component'; + +/** + * test host component to simulate parent component. + */ +@Component({ + template: ` + ` +}) +class TestHostComponent implements OnInit { + + @ViewChild('upload') uploadComp: UploadComponent; + + representation = 'stillImage'; + + form: FormGroup; + + constructor(private _fb: FormBuilder) { + } + + ngOnInit() { + this.form = this._fb.group({}); + } + +} + +describe('UploadComponent', () => { + const mockFile = new File(['1'], 'testfile', { type: 'image/jpeg' }); + + const fb = new FormBuilder(); + + let testHostComponent: TestHostComponent; + let testHostFixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + + const uploadServiceSpy = jasmine.createSpyObj('UploadFileService', ['upload']); + + TestBed.configureTestingModule({ + declarations: [ + UploadComponent, + TestHostComponent + ], + imports: [ + DspActionModule, + MatInputModule, + MatSnackBarModule, + ReactiveFormsModule, + MatIconModule + ], + providers: [ + { + provide: UploadFileService, + useValue: uploadServiceSpy + } + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + testHostFixture = TestBed.createComponent(TestHostComponent); + testHostComponent = testHostFixture.componentInstance; + testHostFixture.detectChanges(); + }); + + it('should create', () => { + expect(testHostComponent).toBeTruthy(); + expect(testHostComponent.uploadComp).toBeTruthy(); + }); + + it('should display file type', () => { + expect(testHostComponent.uploadComp.representation).toBeDefined(); + }); + + it('should be created without a file', () => { + expect(testHostComponent.uploadComp.file).toBeFalsy(); + }); + + it('should delete attachment', () => { + testHostComponent.uploadComp.file = mockFile; + testHostComponent.uploadComp.fileControl.setValue(mockFile); + testHostComponent.uploadComp.thumbnailUrl = 'test'; + testHostComponent.uploadComp.deleteAttachment(); + expect(testHostComponent.uploadComp.file).toBeNull(); + expect(testHostComponent.uploadComp.fileControl.value).toBeNull(); + expect(testHostComponent.uploadComp.thumbnailUrl).toBeNull(); + }); + + describe('form', () => { + it('should create form group and file control and add it to the parent form', waitForAsync(() => { + + testHostFixture.whenStable().then(() => { + + expect(testHostComponent.uploadComp.form).toBeDefined(); + expect(testHostComponent.uploadComp.fileControl).toBeTruthy(); + + // check that the form control has been added to the parent form + expect(testHostComponent.form.contains('file')).toBe(true); + + }); + })); + + it('should reset the form', () => { + testHostComponent.uploadComp.form = fb.group({ test: '' }); + testHostComponent.uploadComp.resetForm(); + expect(testHostComponent.uploadComp.form.get('test').value).toBeNull(); + }); + }); + + describe('isFileTypeSupported', () => { + it('should return true for the supported image files', () => { + const fileTypes = ['image/jpeg', 'image/jp2', 'image/tiff', 'image/tiff-fx', 'image/png']; + + for (const type of fileTypes) { + expect(testHostComponent.uploadComp['_isFileTypeSupported'](type)).toBeTruthy(); + } + }); + + it('should return false for unsupported image files', () => { + // --> TODO: add real unsupported filetypes? + const fileTypes = ['image/a', 'image/b', 'image/c', 'image/d', 'image/e']; + for (const type of fileTypes) { + expect(testHostComponent.uploadComp['_isFileTypeSupported'](type)).toBeFalsy(); + } + }); + }); + + describe('isMoreThanOneFile', () => { + it('should return false for one file array', () => { + const filesArray: File[] = []; + filesArray.push(mockFile); + expect(testHostComponent.uploadComp['_isMoreThanOneFile'](filesArray)).toBeFalsy(); + }); + + it('should return false for more than one file', () => { + const filesArray: File[] = []; + filesArray.push(mockFile, mockFile, mockFile); + expect(testHostComponent.uploadComp['_isMoreThanOneFile'](filesArray)).toBeTruthy(); + }); + }); + + describe('addFile', () => { + + it('should make a request to Sipi when a file is added', () => { + + expect(testHostComponent.uploadComp.form.valid).toBe(false); + + const uploadService = TestBed.inject(UploadFileService) as jasmine.SpyObj; + + uploadService.upload.and.returnValue(of({ + uploadedFiles: [ + { + fileType: 'image', + temporaryUrl: 'http://localhost:1024/tmp/8oDdefPSkaz-EG187srxBFZ.jp2', + originalFilename: 'beaver.jpg', + internalFilename: '8oDdefPSkaz-EG187srxBFZ.jp2' + } + ] + })); + + // https://stackoverflow.com/questions/57080760/fake-file-drop-event-for-unit-testing + const drop = { + preventDefault: () => { }, + stopPropagation: () => { }, + target: { files: [mockFile] } + }; + + testHostComponent.uploadComp.addFile(drop); + + expect(testHostComponent.uploadComp.form.valid).toBe(true); + + const createFileVal = testHostComponent.uploadComp.getNewValue(); + + expect(createFileVal instanceof CreateStillImageFileValue).toBe(true); + expect((createFileVal as CreateStillImageFileValue).filename).toEqual('8oDdefPSkaz-EG187srxBFZ.jp2'); + + const expectedFormData = new FormData(); + expectedFormData.append(mockFile.name, mockFile); + + expect(uploadService.upload).toHaveBeenCalledTimes(1); + expect(uploadService.upload).toHaveBeenCalledWith(expectedFormData); + + }); + + }); +}); diff --git a/src/app/workspace/resource/representation/upload/upload.component.ts b/src/app/workspace/resource/representation/upload/upload.component.ts new file mode 100644 index 0000000000..c67089c5f1 --- /dev/null +++ b/src/app/workspace/resource/representation/upload/upload.component.ts @@ -0,0 +1,251 @@ +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; +import { + CreateFileValue, + CreateStillImageFileValue, + UpdateFileValue, + UpdateStillImageFileValue +} from '@dasch-swiss/dsp-js'; +import { NotificationService } from '@dasch-swiss/dsp-ui'; +import { UploadedFile, UploadedFileResponse, UploadFileService } from './upload-file.service'; + +// https://stackoverflow.com/questions/45661010/dynamic-nested-reactive-form-expressionchangedafterithasbeencheckederror +const resolvedPromise = Promise.resolve(null); + + +@Component({ + selector: 'app-upload', + templateUrl: './upload.component.html', + styleUrls: ['./upload.component.scss'] +}) +export class UploadComponent implements OnInit { + + @Input() parentForm?: FormGroup; + + @Input() representation: string; // only StillImageRepresentation supported so far + + @Input() formName: string; + + @Output() fileInfo: EventEmitter = new EventEmitter(); + + file: File; + form: FormGroup; + fileControl: FormControl; + isLoading = false; + thumbnailUrl: string; + + // todo: maybe we can use this list to display which file format is allowed to + supportedImageTypes = ['image/jpeg', 'image/jp2', 'image/tiff', 'image/tiff-fx', 'image/png']; + + // readonly fromLabels = { + // upload: 'Upload file', + // drag_drop: 'Drag and drop or click to upload' + // }; + constructor( + private _fb: FormBuilder, + private _notification: NotificationService, + private _upload: UploadFileService + ) { } + + ngOnInit(): void { + this.initializeForm(); + } + + /** + * adds file and uploads to SIPI, checking before if conditions met + * @param event DragDrop event containing upload files + */ + addFile(event: any): void { + let files: File[] = []; + files = event.target?.files ? event.target.files : event; + + // only one file at a time supported + if (this._isMoreThanOneFile(files)) { + const error = 'ERROR: Only one file allowed at a time'; + this._notification.openSnackBar(error); + this.file = null; + } else { + const formData = new FormData(); + this.file = files[0]; + + // only certain filetypes are supported + if (!this._isFileTypeSupported(this.file.type)) { + const error = 'ERROR: File type not supported'; + this._notification.openSnackBar(error); + this.file = null; + } else { + // show loading indicator only for files > 1MB + this.isLoading = this.file.size > 1048576; + + formData.append(this.file.name, this.file); + this._upload.upload(formData).subscribe( + (res: UploadedFileResponse) => { + const temporaryUrl = res.uploadedFiles[0].temporaryUrl; + const thumbnailUri = '/full/256,/0/default.jpg'; + this.thumbnailUrl = `${temporaryUrl}${thumbnailUri}`; + + this.fileControl.setValue(res.uploadedFiles[0]); + const fileValue = this.getNewValue(); + // console.log('here we should emit the values', res) + // console.log(fileValue); + + if (fileValue) { + this.fileInfo.emit(fileValue); + } + this.isLoading = false; + }, + (e: Error) => { + this._notification.openSnackBar(e.message); + this.isLoading = false; + this.file = null; + this.thumbnailUrl = null; + } + ); + } + + } + } + + /** + * converts file size to display in KB or MB + * @param val file size to be converted + */ + convertBytes(val: number): string { + const kilo = 1024; + const mega = kilo * kilo; + let result: number; + + if (val >= mega) { + result = val / mega; + return `${result.toFixed(2)} MB`; + } else { + result = val / kilo; + return `${result.toFixed(2)} KB`; + } + } + + /** + * converts date to a readable format. + * @param date date to be converted + */ + convertDate(date: number): string { + return new Date(+`Date(${date})`.replace(/\D/g, '')).toLocaleDateString(); + } + + /** + * removes the attachment + */ + deleteAttachment(): void { + this.fileControl.reset(); + } + + /** + * initializes form group + */ + initializeForm(): void { + this.fileControl = new FormControl(null, Validators.required); + + this.fileControl.valueChanges.subscribe( + val => { + // check if the form has been reset + if (val === null) { + this.file = null; + this.thumbnailUrl = null; + } + } + ); + + this.form = this._fb.group({ + file: this.fileControl + }, { updateOn: 'blur' }); + + if (this.parentForm !== undefined) { + resolvedPromise.then(() => { + this.parentForm.addControl('file', this.form); + }); + } + } + + /** + * resets form group + */ + resetForm(): void { + this.form.reset(); + } + + /** + * create a new file value. + */ + getNewValue(): CreateFileValue | false { + + if (!this.form.valid) { + return false; + } + + const filename = this.fileControl.value.internalFilename; + + // --> TODO: handle different file types + + const fileValue = new CreateStillImageFileValue(); + fileValue.filename = filename; + + return fileValue; + + } + + /** + * create an updated file value. + * + * @param id the current file value's id. + */ + getUpdatedValue(id: string): UpdateFileValue | false { + + if (!this.form.valid) { + return false; + } + + const filename = this.fileControl.value.internalFilename; + + // --> TODO: handle different file types + + const fileValue = new UpdateStillImageFileValue(); + fileValue.filename = filename; + fileValue.id = id; + + return fileValue; + + } + + /** + * checks if added file type is supported for certain resource type + * @param fileType file type to be checked + */ + private _isFileTypeSupported(fileType: string): boolean { + return this._supportedFileTypes().includes(fileType); + } + + /** + * returns supported file types list for certain resource type + */ + private _supportedFileTypes(): string[] { + let allowedFileTypes: string[]; + switch (this.representation) { + case 'stillImage': + allowedFileTypes = this.supportedImageTypes; + break; + default: + allowedFileTypes = []; + break; + } + return allowedFileTypes; + } + + /** + * checks if more than one file dropped + * @param files files array to be checked + */ + private _isMoreThanOneFile(files: File[]): boolean { + return files.length > 1; + } + +} diff --git a/src/app/workspace/resource/resource-instance-form/resource-instance-form.component.html b/src/app/workspace/resource/resource-instance-form/resource-instance-form.component.html index 9e95e1c35a..5f2a1e3d1f 100644 --- a/src/app/workspace/resource/resource-instance-form/resource-instance-form.component.html +++ b/src/app/workspace/resource/resource-instance-form/resource-instance-form.component.html @@ -66,6 +66,9 @@
+ + + - +
- diff --git a/src/app/workspace/resource/resource-instance-form/resource-instance-form.component.ts b/src/app/workspace/resource/resource-instance-form/resource-instance-form.component.ts index 35a6686054..bdccc78633 100644 --- a/src/app/workspace/resource/resource-instance-form/resource-instance-form.component.ts +++ b/src/app/workspace/resource/resource-instance-form/resource-instance-form.component.ts @@ -5,6 +5,7 @@ import { ApiResponseData, ApiResponseError, Constants, + CreateFileValue, CreateResource, CreateValue, KnoraApiConnection, @@ -72,6 +73,11 @@ export class ResourceInstanceFormComponent implements OnInit, OnDestroy { properties: ResourcePropertyDefinition[]; ontologyInfo: ResourceClassAndPropertyDefinitions; + // selected resource class has a file value property: display the corresponding upload form + hasFileValue: 'stillImage' | 'movingImage' | 'audio' | 'document' | 'text'; + + fileValue: CreateFileValue; + valueOperationEventSubscription: Subscription; errorMessage: any; @@ -158,16 +164,18 @@ export class ResourceInstanceFormComponent implements OnInit, OnDestroy { }); + if (this.fileValue) { + this.propertiesObj[Constants.HasStillImageFileValue] = [this.fileValue]; + } + createResource.properties = this.propertiesObj; this._dspApiConnection.v2.res.createResource(createResource).subscribe( (res: ReadResource) => { this.resource = res; - // navigate to the resource viewer page - this._router.navigateByUrl('/refresh', { skipLocationChange: true }).then(() => - this._router.navigate(['/resource/' + encodeURIComponent(this.resource.id)]) - ); + const goto = '/resource/' + encodeURIComponent(this.resource.id); + this._router.navigateByUrl(goto, { skipLocationChange: false }); this.closeDialog.emit(); }, @@ -192,7 +200,9 @@ export class ResourceInstanceFormComponent implements OnInit, OnDestroy { (response: ApiResponseData) => { for (const project of response.body.user.projects) { - this.usersProjects.push(project); + if (project.status) { + this.usersProjects.push(project); + } } // notifies the user that he/she is not part of any project @@ -207,7 +217,11 @@ export class ResourceInstanceFormComponent implements OnInit, OnDestroy { } else if (this.session.user.sysAdmin === true) { this._dspApiConnection.admin.projectsEndpoint.getProjects().subscribe( (response: ApiResponseData) => { - this.usersProjects = response.body.projects; + for (const project of response.body.projects) { + if (project.status && project.id !== Constants.SystemProjectIRI && project.id !== Constants.DefaultSharedOntologyIRI) { + this.usersProjects.push(project); + } + } }, (error: ApiResponseError) => { this._errorHandler.showMessage(error); @@ -354,8 +368,13 @@ export class ResourceInstanceFormComponent implements OnInit, OnDestroy { this.selectedResourceClass = onto.classes[resourceClassIri]; - // filter out all props that cannot be edited or are link props - this.properties = onto.getPropertyDefinitionsByType(ResourcePropertyDefinition).filter(prop => prop.isEditable && !prop.isLinkProperty); + // filter out all props that cannot be edited or are link props but also the hasFileValue props + this.properties = onto.getPropertyDefinitionsByType(ResourcePropertyDefinition).filter( + prop => prop.isEditable && !prop.isLinkProperty && prop.id !== Constants.HasStillImageFileValue); + + if (onto.properties[Constants.HasStillImageFileValue]) { + this.hasFileValue = 'stillImage'; + } // notifies the user that the selected resource does not have any properties defined yet. if (!this.selectPropertiesComponent && this.properties.length === 0) { @@ -372,4 +391,8 @@ export class ResourceInstanceFormComponent implements OnInit, OnDestroy { } + setFileValue(file: CreateFileValue) { + this.fileValue = file; + } + } diff --git a/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.scss b/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.scss index d236932b33..5dc2df2458 100644 --- a/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.scss +++ b/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.scss @@ -15,7 +15,7 @@ gap: 16px; .properties { - grid-column: 1 / span 6; + // grid-column: 1 / span 6; margin-top: 16px; .border-bottom { diff --git a/src/app/workspace/resource/resource-instance-form/select-properties/switch-properties/switch-properties.component.ts b/src/app/workspace/resource/resource-instance-form/select-properties/switch-properties/switch-properties.component.ts index a68feb2b87..eb99f75d34 100644 --- a/src/app/workspace/resource/resource-instance-form/select-properties/switch-properties/switch-properties.component.ts +++ b/src/app/workspace/resource/resource-instance-form/select-properties/switch-properties/switch-properties.component.ts @@ -28,6 +28,7 @@ export class SwitchPropertiesComponent implements OnInit { constructor() { } ngOnInit(): void { + // the input isRequiredProp provided by KeyValuePair is stored as a number // a conversion from a number to a boolean is required by the input valueRequiredValidator this.isRequiredProp = !!+this.isRequiredProp; diff --git a/src/app/workspace/resource/resource.component.ts b/src/app/workspace/resource/resource.component.ts index 19b8befdda..91041accc1 100644 --- a/src/app/workspace/resource/resource.component.ts +++ b/src/app/workspace/resource/resource.component.ts @@ -81,10 +81,10 @@ export class ResourceComponent implements OnInit, OnChanges, OnDestroy { private _titleService: Title ) { - if (!this.resourceIri) { - this.resourceIri = this._route.snapshot.params.id; + this._route.params.subscribe(params => { + this.resourceIri = params['id']; this.getResource(this.resourceIri); - } + }); this._router.events.subscribe((event) => { diff --git a/src/config/config.dev.json b/src/config/config.dev.json index 29c5a40fdc..cb1761f4d9 100644 --- a/src/config/config.dev.json +++ b/src/config/config.dev.json @@ -5,5 +5,6 @@ "apiPath": "", "jsonWebToken": "", "logErrors": true, + "iiifUrl": "http://localhost:1024", "geonameToken": "knora" } diff --git a/src/config/config.prod.json b/src/config/config.prod.json index 7b1c14b427..3350fadafc 100644 --- a/src/config/config.prod.json +++ b/src/config/config.prod.json @@ -5,5 +5,6 @@ "apiPath": "", "jsonWebToken": "", "logErrors": false, + "iiifUrl": "https://iiif.dasch.swiss", "geonameToken": "knora" } diff --git a/src/config/config.test-server.json b/src/config/config.test-server.json index f5f577f7c4..2817623045 100644 --- a/src/config/config.test-server.json +++ b/src/config/config.test-server.json @@ -4,5 +4,7 @@ "apiPort": 443, "apiPath": "", "jsonWebToken": "", - "logErrors": true + "logErrors": true, + "iiifUrl": "https://iiif.test.dasch.swiss", + "geonameToken": "knora" }