From c55af200eaa7f45edea0906346db60fac7ab21a2 Mon Sep 17 00:00:00 2001 From: "konstantina.galouni" Date: Thu, 6 Oct 2022 15:50:55 +0300 Subject: [PATCH] [Validator]: First working version - user selects among 3 hardcoded guidelines, pastes an XML and validates it | Added utils folder for input component (copied by openaireLibrary). --- angular.json | 8 +- package.json | 2 +- src/app/app.module.ts | 9 +- .../single-record-validator.component.html | 139 +--- .../single-record-validator.component.ts | 27 +- .../single-record-validator.service.ts | 18 + .../click/click-outside-or-esc.directive.ts | 33 + src/app/shared/utils/click/click.module.ts | 14 + .../utils/click/long-click.directive.ts | 72 ++ src/app/shared/utils/input/input.component.ts | 626 ++++++++++++++++++ src/app/shared/utils/input/input.module.ts | 24 + src/assets/common-assets | 2 +- src/assets/openaire-theme | 2 +- src/environments/environment.ts | 3 +- tsconfig.json | 6 +- 15 files changed, 848 insertions(+), 137 deletions(-) create mode 100644 src/app/pages/single-record-validator/single-record-validator.service.ts create mode 100644 src/app/shared/utils/click/click-outside-or-esc.directive.ts create mode 100644 src/app/shared/utils/click/click.module.ts create mode 100644 src/app/shared/utils/click/long-click.directive.ts create mode 100644 src/app/shared/utils/input/input.component.ts create mode 100644 src/app/shared/utils/input/input.module.ts diff --git a/angular.json b/angular.json index b2f58c0..5c080b6 100644 --- a/angular.json +++ b/angular.json @@ -30,7 +30,10 @@ "styles": [ "src/styles.less" ], - "scripts": [] + "scripts": [ + "node_modules/uikit/dist/js/uikit.min.js", + "node_modules/uikit/dist/js/uikit-icons.min.js" + ] }, "configurations": { "production": { @@ -103,5 +106,8 @@ } } } + }, + "cli": { + "analytics": false } } diff --git a/package.json b/package.json index 3633fa7..56149d6 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.0.0", "scripts": { "ng": "ng", - "start": "ng serve", + "start": "ng serve --disable-host-check --host 0.0.0.0 --port 5100", "build": "ng build", "watch": "ng build --watch --configuration development", "test": "ng test" diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 38c9891..3a97107 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -6,6 +6,9 @@ import { AppComponent } from './app.component'; import { TopmenuComponent } from './shared/topmenu/topmenu.component'; import { SidebarComponent } from './shared/sidebar/sidebar.component'; import { SingleRecordValidatorComponent } from './pages/single-record-validator/single-record-validator.component'; +import {FormsModule, ReactiveFormsModule} from "@angular/forms"; +import {HttpClient, HttpClientModule} from "@angular/common/http"; +import {InputModule} from "./shared/utils/input/input.module"; @NgModule({ declarations: [ @@ -16,7 +19,11 @@ import { SingleRecordValidatorComponent } from './pages/single-record-validator/ ], imports: [ BrowserModule, - AppRoutingModule + AppRoutingModule, + FormsModule, + ReactiveFormsModule, + HttpClientModule, + InputModule ], providers: [], bootstrap: [AppComponent] diff --git a/src/app/pages/single-record-validator/single-record-validator.component.html b/src/app/pages/single-record-validator/single-record-validator.component.html index 18e49ef..175d97b 100644 --- a/src/app/pages/single-record-validator/single-record-validator.component.html +++ b/src/app/pages/single-record-validator/single-record-validator.component.html @@ -1,50 +1,16 @@ - - -
- -
- +
1. Select guidelines (*)
-
-
-
-
-
+
2. Paste metadata record (*)
-
-
-
-
-
- -
-
-
-
-
-
+
-
- +
+
Validation Results
+
+ No validated metadata record yet +
+
+ {{resultString}} +
+
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/app/pages/single-record-validator/single-record-validator.component.ts b/src/app/pages/single-record-validator/single-record-validator.component.ts index b1e265e..ec77763 100644 --- a/src/app/pages/single-record-validator/single-record-validator.component.ts +++ b/src/app/pages/single-record-validator/single-record-validator.component.ts @@ -1,4 +1,7 @@ import { Component, OnInit } from '@angular/core'; +import {UntypedFormBuilder, UntypedFormGroup, Validators} from "@angular/forms"; +import {SingleRecordValidatorService} from "./single-record-validator.service"; +import {Option} from "../../shared/utils/input/input.component"; @Component({ selector: 'app-single-record-validator', @@ -6,10 +9,28 @@ import { Component, OnInit } from '@angular/core'; styleUrls: ['./single-record-validator.component.less'] }) export class SingleRecordValidatorComponent implements OnInit { + public options: Option[] = [ + {label: 'Data Archive Guidelines V2 Profile', value: 'dataArchiveGuidelinesV2Profile'}, + {label: 'Literature Guidelines V3 Profile', value: 'literatureGuidelinesV3Profile'}, + {label: 'Literature Guidelines V4 Profile', value: 'literatureGuidelinesV4Profile'} + ]; + public form: UntypedFormGroup; + public result: any; - constructor() { } - - ngOnInit(): void { + constructor(private fb: UntypedFormBuilder, private validator: SingleRecordValidatorService) { + this.form = this.fb.group({ + guidelines: this.fb.control("", Validators.required), + xml: this.fb.control('', Validators.required) + }); } + ngOnInit(): void {} + + public validate() { + this.validator.validateRecord(this.form.get('xml')?.getRawValue(), this.form.get('guidelines')?.getRawValue()).subscribe( + result => { + this.result = result; + } + ) + } } diff --git a/src/app/pages/single-record-validator/single-record-validator.service.ts b/src/app/pages/single-record-validator/single-record-validator.service.ts new file mode 100644 index 0000000..e68cecc --- /dev/null +++ b/src/app/pages/single-record-validator/single-record-validator.service.ts @@ -0,0 +1,18 @@ +import {Injectable} from "@angular/core"; +import {HttpClient, HttpHeaders} from "@angular/common/http"; +import {Observable} from "rxjs"; +import {environment} from "../../../environments/environment"; + +@Injectable({ + providedIn: "root" +}) +export class SingleRecordValidatorService { + + constructor(private http: HttpClient) {} + + validateRecord(xml: string, guidelinesName: string): Observable { + let url = environment.validatorAPI + "validate?guidelines="+guidelinesName; + let headers = new HttpHeaders({'Content-Type': 'application/json', 'accept': 'application/json'}); + return this.http.post(url, xml, {headers: headers}); + } +} diff --git a/src/app/shared/utils/click/click-outside-or-esc.directive.ts b/src/app/shared/utils/click/click-outside-or-esc.directive.ts new file mode 100644 index 0000000..e536a08 --- /dev/null +++ b/src/app/shared/utils/click/click-outside-or-esc.directive.ts @@ -0,0 +1,33 @@ +import {Directive, ElementRef, EventEmitter, HostListener, Output} from '@angular/core'; + +export interface ClickEvent { + event: any, + clicked: boolean; +} + +@Directive({ + selector: '[click-outside-or-esc]' +}) +export class ClickOutsideOrEsc { + @Output('clickOutside') clickOutside: EventEmitter = new EventEmitter(); + + constructor(private elementRef: ElementRef) {} + + @HostListener('document:click', ['$event']) + click(event) { + if(event.isTrusted) { + this.clickOutside.emit({ + event: event, + clicked: !(this.elementRef && this.elementRef.nativeElement.contains(event.target)) + }); + } + } + + @HostListener('window:keydown.escape', ['$event']) + esc(event: KeyboardEvent) { + this.clickOutside.emit({ + event: event, + clicked: true + }); + } +} diff --git a/src/app/shared/utils/click/click.module.ts b/src/app/shared/utils/click/click.module.ts new file mode 100644 index 0000000..4283a1e --- /dev/null +++ b/src/app/shared/utils/click/click.module.ts @@ -0,0 +1,14 @@ +import {NgModule} from "@angular/core"; +import {ClickOutsideOrEsc} from "./click-outside-or-esc.directive"; +import {LongClick} from "./long-click.directive"; + +@NgModule({ + imports: [], + declarations: [ + ClickOutsideOrEsc, LongClick + ], + exports: [ + ClickOutsideOrEsc, LongClick + ] +}) +export class ClickModule {} diff --git a/src/app/shared/utils/click/long-click.directive.ts b/src/app/shared/utils/click/long-click.directive.ts new file mode 100644 index 0000000..bc8b095 --- /dev/null +++ b/src/app/shared/utils/click/long-click.directive.ts @@ -0,0 +1,72 @@ +import {Directive, EventEmitter, HostListener, Input, Output} from '@angular/core'; + +@Directive({ + selector: '[long-click]' +}) + +export class LongClick { + + @Input() duration: number = 500; + + @Output() onLongPress: EventEmitter = new EventEmitter(); + @Output() onLongPressing: EventEmitter = new EventEmitter(); + @Output() onLongPressEnd: EventEmitter = new EventEmitter(); + + private pressing: boolean; + private longPressing: boolean; + private timeout: any; + private mouseX: number = 0; + private mouseY: number = 0; + + @HostListener('mousedown', ['$event']) + onMouseDown(event) { + // don't do right/middle clicks + if (event.which !== 1) return; + + this.mouseX = event.clientX; + this.mouseY = event.clientY; + + this.pressing = true; + this.longPressing = false; + + this.timeout = setTimeout(() => { + this.longPressing = true; + this.onLongPress.emit(event); + this.loop(event); + }, this.duration); + + this.loop(event); + } + + @HostListener('mousemove', ['$event']) + onMouseMove(event) { + if (this.pressing && !this.longPressing) { + const xThres = (event.clientX - this.mouseX) > 10; + const yThres = (event.clientY - this.mouseY) > 10; + if (xThres || yThres) { + this.endPress(); + } + } + } + + loop(event) { + if (this.longPressing) { + this.timeout = setTimeout(() => { + this.onLongPressing.emit(event); + this.loop(event); + }, 50); + } + } + + endPress() { + clearTimeout(this.timeout); + this.longPressing = false; + this.pressing = false; + this.onLongPressEnd.emit(true); + } + + @HostListener('mouseup') + onMouseUp() { + this.endPress(); + } +} diff --git a/src/app/shared/utils/input/input.component.ts b/src/app/shared/utils/input/input.component.ts new file mode 100644 index 0000000..a01a66f --- /dev/null +++ b/src/app/shared/utils/input/input.component.ts @@ -0,0 +1,626 @@ +import { + AfterViewInit, + ChangeDetectorRef, + Component, + ElementRef, + EventEmitter, + HostListener, + Input, + OnChanges, + OnDestroy, + OnInit, + Output, + SimpleChanges, + ViewChild +} from "@angular/core"; +import {AbstractControl, UntypedFormArray, UntypedFormControl, ValidatorFn} from "@angular/forms"; +// import {HelperFunctions} from "../../utils/HelperFunctions.class"; +import {Subscription} from "rxjs"; +import {ClickEvent} from "../click/click-outside-or-esc.directive"; +// import {EnvProperties} from "../../utils/properties/env-properties"; +// import {properties} from "../../../../environments/environment"; + +export type InputType = 'text' | 'URL' | 'logoURL' | 'autocomplete' | 'autocomplete_soft' | 'textarea' | 'select' | 'chips'; + +export interface Option { + icon?: string, + iconClass?: string, + value: any, + label: string, + tooltip?: string, + disabled?: boolean, + hidden?: boolean +} + +export interface Placeholder { + label: string, + static?: boolean +} + +declare var UIkit; + +/** + * WARNING! dashboard-input selector is @deprecated, use input instead + * + * Autocomplete soft allows values that are not listed in options list. In order to work as expected + * avoid providing options with different label and value. + * + * */ +@Component({ + selector: '[dashboard-input], [input]', + template: ` +
+
+
+
+ +
+
+ + + + + + + + +
{{placeholderInfo.label}}
+
{{getLabel(formControl.value)}}
+
+ +
{{noValueSelected}}
+
{{getLabel(formControl.value)}}
+
+
+ + +
{{getLabel(formAsControl.value)}}
+ +
{{noValueSelected}}
+
{{getLabel(formControl.value)}}
+
+
+ + + + +
+
+
+ {{getLabel(chip.value)}} + +
+
+
+ +
+
+ + {{(formAsArray.length - 1)}} more +
+
+
+
+ + + + + + + +
+
+
+ +
+
+
+ +
+ + {{formControl.errors.error}} + Please provide a valid URL (e.g. https://example.com) + + + + + + + + Note: Prefer urls like "https://example.com/my-secure-image.png" + instead of "http://example.com/my-image.png". + Browsers may not load non secure content. + + + + + + ` +}) +export class InputComponent implements OnInit, OnDestroy, AfterViewInit, OnChanges { + private static INPUT_COUNTER: number = 0; + /** Deprecated options*/ + /** @deprecated */ + @Input('label') label: string; + /** @deprecated */ + @Input() extraLeft: boolean = true; + /** @deprecated */ + @Input() gridSmall: boolean = false; + /** @deprecated */ + @Input() hideControl: boolean = false; + /** @deprecated */ + @Input() flex: 'middle' | 'top' | 'bottom' = 'middle'; + /** @deprecated */ + @Input() iconLeft: boolean = false; + /** @deprecated */ + @Input() removable: boolean = true; + /** @deprecated */ + @Input() smallChip: boolean = false; + /** @deprecated */ + @Input() panelWidth: number = 300; + /** @deprecated */ + @Input() panelClass: string = null; + /** Basic information */ + @Input('formInput') formControl: AbstractControl; + @Input('type') type: InputType = 'text'; + @Input() validators: ValidatorFn[] | ValidatorFn; + @Input() disabled: boolean = false; + @Input() disabledIcon: string = 'lock'; + @Input() value: any | any[]; + @Output() valueChange = new EventEmitter(); + @Input() hint: string; + @Input() tooltip: boolean = false; + @Input() searchable: boolean = false; + /** Text */ + @ViewChild('input') input: ElementRef; + /** Textarea options */ + @ViewChild('textArea') textArea: ElementRef; + @Input('rows') rows: number = 3; + /** Select | Autocomplete | chips available options */ + @Input() selectArrow: string = 'arrow_drop_down'; + @Input() selectedIndex: number = 0; + @Input() selectable: boolean = false; + @Input() noValueSelected: string = 'No option selected'; + /** Chips && Autocomplete*/ + public filteredOptions: Option[] = []; + public searchControl: UntypedFormControl; + /** Use modifier's class(es) to change view of your Input */ + @Input() inputClass: string = 'inner'; + /** Icon on the input */ + @Input() icon: string = null; + /** Chip options */ + @Input() addExtraChips: boolean = false; + @Input() showOptionsOnEmpty: boolean = true; + @Output() focusEmitter: EventEmitter = new EventEmitter(); + /** LogoUrl information */ + public secure: boolean = true; + /** Internal basic information */ + public id: string; + public placeholderInfo: Placeholder = {label: '', static: true}; + public required: boolean = false; + public focused: boolean = false; + public opened: boolean = false; + private initValue: any; + private optionsArray: Option[] = []; + private optionsBreakpoint: number = 6; + private subscriptions: any[] = []; + @ViewChild('inputBox') inputBox: ElementRef; + @ViewChild('optionBox') optionBox: ElementRef; + @ViewChild('searchInput') searchInput: ElementRef; + + @Input() + set placeholder(placeholder: string | Placeholder) { + if (typeof placeholder === 'string') { + this.placeholderInfo = {label: placeholder, static: false}; + } else { + if (placeholder.static && (this.type === 'autocomplete' || this.type === 'chips' || this.hint)) { + placeholder.static = false; + console.debug('Static placeholder is not available in this type of input and if hint is available.'); + } + this.placeholderInfo = placeholder; + } + } + + @Input() + set options(options: (Option | string | number) []) { + this.optionsArray = options.map(option => { + if (typeof option === 'string' || typeof option === 'number') { + return { + label: option.toString(), + value: option + }; + } else { + return option; + } + }); + if(!this.tooltip) { + this.tooltip = this.optionsArray.length > 0; + } + if(this.type === "select") { + if (this.optionsArray.length > this.optionsBreakpoint) { + this.type = 'autocomplete'; + this.showOptionsOnEmpty = true; + this.icon = this.selectArrow; + } + this.selectable = true; + } + } + + constructor(private elementRef: ElementRef, private cdr: ChangeDetectorRef) {} + + @HostListener('window:keydown.arrowUp', ['$event']) + arrowUp(event: KeyboardEvent) { + if (this.opened) { + event.preventDefault(); + if (this.selectedIndex > 0) { + this.selectedIndex--; + this.optionBox.nativeElement.scrollBy(0, -34); + } + } + } + + @HostListener('window:keydown.arrowDown', ['$event']) + arrowDown(event: KeyboardEvent) { + if (this.opened) { + event.preventDefault(); + if (this.selectedIndex < (this.filteredOptions.length - 1)) { + this.selectedIndex++; + this.optionBox.nativeElement.scrollBy(0, 34); + } + } + } + + @HostListener('window:keydown.enter', ['$event']) + enter(event: KeyboardEvent) { + if (this.opened && this.optionBox) { + event.preventDefault(); + if (this.filteredOptions[this.selectedIndex]) { + this.selectOption(this.filteredOptions[this.selectedIndex], event); + } + this.open(false); + event.stopPropagation(); + } else { + this.focus(false, event); + } + } + + click(event: ClickEvent) { + this.focus(!event.clicked, event); + } + + ngOnInit() { + InputComponent.INPUT_COUNTER++; + this.id = 'input-' + InputComponent.INPUT_COUNTER; + if (!this.formControl) { + if (Array.isArray(this.value)) { + this.formControl = new UntypedFormArray([]); + this.value.forEach(value => { + this.formAsArray.push(new UntypedFormControl(value, this.validators)); + }); + } else { + this.formControl = new UntypedFormControl(this.value); + this.formControl.setValidators(this.validators); + } + if (this.disabled) { + this.formControl.disable(); + } + } + } + + ngAfterViewInit() { + this.reset(); + } + + ngOnChanges(changes: SimpleChanges) { + if (this.formControl) { + if (changes.value) { + this.formControl.setValue(this.value); + } + if (changes.formControl || changes.validators || changes.options) { + this.reset(); + } + if (changes.disabled) { + if (this.disabled) { + this.formControl.disable(); + } else { + this.formControl.enable(); + } + } + } + } + + ngOnDestroy(): void { + this.unsubscribe(); + } + + get formAsControl(): UntypedFormControl { + if (this.formControl instanceof UntypedFormControl) { + return this.formControl; + } else { + return null; + } + } + + get formAsArray(): UntypedFormArray { + if (this.formControl instanceof UntypedFormArray) { + return this.formControl; + } else { + return null; + } + } + + reset() { + this.secure = true; + this.unsubscribe(); + this.initValue = this.copy(this.formControl.value); + if (this.type === 'logoURL') { + this.secure = (!this.initValue || this.initValue.includes('https://')); + } + if (this.optionsArray) { + this.filteredOptions = this.filter(''); + this.cdr.detectChanges(); + } + if (this.type === 'chips' || this.type === 'autocomplete') { + if (!this.searchControl) { + this.searchControl = new UntypedFormControl('', this.validators); + } + this.subscriptions.push(this.searchControl.valueChanges.subscribe(value => { + this.filteredOptions = this.filter(value); + this.cdr.detectChanges(); + if (this.focused) { + this.open(true); + setTimeout(() => { + this.searchInput.nativeElement.focus(); + this.searchInput.nativeElement.value = value; + }, 0); + } + })); + } + if (this.formControl.validator) { + let validator = this.formControl.validator({} as AbstractControl); + this.required = (validator && validator.required); + } + this.subscriptions.push(this.formControl.valueChanges.subscribe(value => { + if (this.formControl.enabled) { + value = (value === '') ? null : value; + if (this.type === 'logoURL') { + this.secure = (!value || value.includes('https://')); + } + if (this.initValue === value || (this.initValue === '' && value === null)) { + this.formControl.markAsPristine(); + } else { + this.formControl.markAsDirty(); + } + if (this.type === 'autocomplete_soft') { + this.filteredOptions = this.filter(value); + this.cdr.detectChanges(); + if (this.focused) { + this.open(true); + } + } + if ((this.value && value && this.value !== value) || (!this.value && value) || this.value && !value) { + this.valueChange.emit(this.formControl.value); + } + } + })); + if (this.input) { + this.input.nativeElement.disabled = this.formControl.disabled; + } + } + + unsubscribe() { + this.subscriptions.forEach(subscription => { + if (subscription instanceof Subscription) { + subscription.unsubscribe(); + } + }); + } + + remove(index: number, event) { + if (this.focused) { + this.formAsArray.removeAt(index); + this.formAsArray.markAsDirty(); + this.focus(true); + this.searchControl.setValue(''); + event.stopPropagation(); + } + } + + private filter(value: string): Option[] { + let options = this.optionsArray.filter(option => !option.hidden); + if (this.type === "chips") { + options = options.filter(option => !this.formAsArray.value.find(value => this.equals(option.value, value))); + } + if ((!value || value.length == 0)) { + this.selectedIndex = 0; + return (this.showOptionsOnEmpty) ? options : []; + } + const filterValue = value.toString().toLowerCase(); + options = options.filter(option => option.label.toLowerCase().indexOf(filterValue) != -1); + this.selectedIndex = options.findIndex(option => option.value === this.formControl.value); + if (this.selectedIndex === -1) { + this.selectedIndex = 0; + } + return options; + } + + add(event) { + if (this.addExtraChips && this.searchControl.value && this.searchControl.valid) { + if (event && event.stopPropagation) { + event.stopPropagation(); + } + this.formAsArray.push(new UntypedFormControl(this.searchControl.value, this.validators)); + this.formAsArray.markAsDirty(); + } + this.searchControl.setValue(''); + } + + getLabel(value: any): string { + let option = this.optionsArray.find(option => this.equals(option.value, value)); + return (option) ? option.label : (value); + } + + getTooltip(value: any): string { + let option = this.optionsArray.find(option => this.equals(option.value, value)); + return (option) ? (option.tooltip ? option.tooltip : option.label) : (value); + } + + focus(value: boolean, event = null) { + if (this.focused) { + this.formControl.markAsTouched(); + } + this.focused = value; + this.cdr.detectChanges(); + if (this.focused) { + if (this.input) { + this.input.nativeElement.focus(); + } else if (this.textArea) { + this.textArea.nativeElement.focus(); + } else if (this.searchInput) { + this.searchInput.nativeElement.focus(); + } + if (this.selectArrow) { + this.open(!this.opened); + } else if (this.type !== 'autocomplete' || this.showOptionsOnEmpty || !this.formControl.value) { + this.open(true); + } + } else { + this.open(false); + if (this.input) { + this.input.nativeElement.blur(); + } else if (this.textArea) { + this.textArea.nativeElement.blur(); + } else if (this.searchInput) { + this.searchInput.nativeElement.blur(); + } + if (this.searchControl) { + this.add(event); + } + } + this.focusEmitter.emit(this.focused); + } + + open(value: boolean) { + this.opened = value && this.formControl.enabled; + this.cdr.detectChanges(); + if (this.optionBox && this.opened) { + this.selectedIndex = this.filteredOptions.findIndex(option => option.value === this.formControl.value); + if (this.selectedIndex === -1 && this.type !== 'autocomplete_soft') { + this.selectedIndex = 0; + } + UIkit.dropdown(this.optionBox.nativeElement).show(); + } else { + if (this.optionBox) { + UIkit.dropdown(this.optionBox.nativeElement).hide(); + this.focused = false; + } + } + } + + resetSearch(event: any) { + event.stopPropagation(); + this.searchControl.setValue(''); + this.focus(true, event); + } + + resetValue(event: any) { + event.stopPropagation(); + this.formControl.setValue(''); + this.focus(true, event); + } + + selectOption(option: Option, event) { + if (this.formControl.enabled) { + if (this.formAsControl) { + this.formAsControl.setValue(option.value); + } else if (this.formAsArray) { + this.formAsArray.push(new UntypedFormControl(option.value)); + this.formAsArray.markAsDirty(); + event.stopPropagation(); + this.focus(true); + this.searchControl.setValue(''); + } + } + } + + + + // External Helper Methods + public equals(object1, object2) { + return object1 === object2 || JSON.stringify(object1) === JSON.stringify(object2); + } + + public copy(obj: any): any { + let copy; + + // Handle the 3 simple types, and null or undefined + if (null == obj || "object" != typeof obj) return obj; + + // Handle Date + if (obj instanceof Date) { + copy = new Date(); + copy.setTime(obj.getTime()); + return copy; + } + + // Handle Array + if (obj instanceof Array) { + copy = []; + for (let i = 0, len = obj.length; i < len; i++) { + copy[i] = this.copy(obj[i]); + } + return copy; + } + + // Handle Map + if (obj instanceof Map) { + return new Map(obj.entries()); + } + + // Handle Object + if (obj instanceof Object) { + copy = {}; + for (let attr in obj) { + if (obj.hasOwnProperty(attr)) { + copy[attr] = this.copy(obj[attr]); + } + } + return copy; + } + throw new Error("Unable to copy obj! Its type isn't supported."); + } +} diff --git a/src/app/shared/utils/input/input.module.ts b/src/app/shared/utils/input/input.module.ts new file mode 100644 index 0000000..29a0622 --- /dev/null +++ b/src/app/shared/utils/input/input.module.ts @@ -0,0 +1,24 @@ +import {NgModule} from '@angular/core'; +import {InputComponent} from "./input.component"; +import {CommonModule} from "@angular/common"; +import {RouterModule} from "@angular/router"; +import {FormsModule, ReactiveFormsModule} from "@angular/forms"; +import {ClickModule} from "../click/click.module"; + +@NgModule({ + imports: [ + CommonModule, + RouterModule, + FormsModule, + ReactiveFormsModule, + ClickModule + ], + exports: [ + InputComponent + ], + declarations: [ + InputComponent + ] +}) +export class InputModule { +} diff --git a/src/assets/common-assets b/src/assets/common-assets index c645362..936fac2 160000 --- a/src/assets/common-assets +++ b/src/assets/common-assets @@ -1 +1 @@ -Subproject commit c645362896b10ae00563f38337aa0011c640cb20 +Subproject commit 936fac297322fa252af930cab3c0e69efe57c1a5 diff --git a/src/assets/openaire-theme b/src/assets/openaire-theme index 8b8db18..8bb758b 160000 --- a/src/assets/openaire-theme +++ b/src/assets/openaire-theme @@ -1 +1 @@ -Subproject commit 8b8db18d0b34ed61d76936f0e44ef856a5f6df5c +Subproject commit 8bb758b340d05b50b65da48863ff7ed69fdc122b diff --git a/src/environments/environment.ts b/src/environments/environment.ts index f56ff47..558a791 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -3,7 +3,8 @@ // The list of file replacements can be found in `angular.json`. export const environment = { - production: false + production: false, + validatorAPI: "http://duffy.di.uoa.gr:8080/uoa-validator-api/" }; /* diff --git a/tsconfig.json b/tsconfig.json index ff06eae..9da5caf 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,9 +5,9 @@ "baseUrl": "./", "outDir": "./dist/out-tsc", "forceConsistentCasingInFileNames": true, - "strict": true, + "strict": false, "noImplicitOverride": true, - "noPropertyAccessFromIndexSignature": true, + "noPropertyAccessFromIndexSignature": false, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "sourceMap": true, @@ -27,6 +27,6 @@ "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": true, "strictInputAccessModifiers": true, - "strictTemplates": true + "strictTemplates": false } }