[Library | Trunk]: Some changes on input component

git-svn-id: https://svn.driver.research-infrastructures.eu/driver/dnet40/modules/uoa-services-library/trunk/ng-openaire-library/src/app@60413 d315682c-612b-4755-9ff5-7f18f6832af3
This commit is contained in:
k.triantafyllou 2021-02-12 11:31:12 +00:00
parent fd8a89ffe5
commit 878ea565ad
8 changed files with 184 additions and 60 deletions

View File

@ -110,7 +110,7 @@
type="text" label="Class Name" placeholder="Write a name">
</div>
<div dashboard-input [formInput]="classForm.get('pages')" placeholder="Add a page"
type="chips" [options]="allPages" label="Pages" chipLabel="name">
type="chips" [options]="allPages" label="Pages">
</div>
<div dashboard-input type="select" label="Portal Type" placeholder="Choose a type"
[formInput]="classForm.get('portalType')" [options]="portalUtils.portalTypes"></div>

View File

@ -144,7 +144,7 @@
label="Type" [options]="typeOptions">
</div>
<div dashboard-input [formInput]="pageForm.get('entities')" placeholder="Add an entity"
type="chips" [options]="allEntities" label="Entities" chipLabel="name">
type="chips" [options]="allEntities" label="Entities">
</div>
<div dashboard-input type="select" label="Portal Type" placeholder="Choose a type"
[formInput]="pageForm.get('portalType')" [options]="portalUtils.portalTypes"></div>

View File

@ -17,7 +17,7 @@ import {distinctUntilChanged} from "rxjs/operators";
<div #header id="page_content_header">
<ng-content select="[header]"></ng-content>
</div>
<div id="page_content_inner" [ngStyle]="{'margin-top.px': height.toString()}">
<div id="page_content_inner" [ngStyle]="{'margin-top.px': (height)?height.toString():'0'}">
<ng-content select="[inner]"></ng-content>
</div>
</div>

View File

@ -1,10 +1,21 @@
import {Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewChild} from "@angular/core";
import {
Component, ElementRef,
EventEmitter, HostListener,
Input,
OnChanges,
OnDestroy,
OnInit,
Output,
SimpleChanges,
ViewChild
} from "@angular/core";
import {AbstractControl, FormArray, FormControl} from "@angular/forms";
import {HelperFunctions} from "../../utils/HelperFunctions.class";
import {Observable, of, Subscription} from "rxjs";
import {MatSelect} from "@angular/material/select";
import {MatAutocompleteSelectedEvent} from "@angular/material/autocomplete";
import {map, startWith} from "rxjs/operators";
import {MatChipInputEvent} from "@angular/material/chips";
export interface Option {
@ -20,7 +31,7 @@ export interface Option {
<div *ngIf="label && type != 'checkbox'"
class="uk-text-bold uk-form-label uk-margin-small-bottom">{{label + (required ? ' *' : '')}}</div>
<div *ngIf="hint" class="uk-margin-bottom uk-text-small uk-form-hint">{{hint}}</div>
<div class="uk-grid uk-flex uk-flex-middle" [class.uk-grid-small]="gridSmall" uk-grid>
<div class="uk-grid uk-flex" [ngClass]="'uk-flex-' + flex" [class.uk-grid-small]="gridSmall" uk-grid>
<ng-content></ng-content>
<div [class.uk-hidden]="hideControl" class="uk-width-expand uk-position-relative"
[class.uk-flex-first]="!extraLeft">
@ -35,20 +46,28 @@ export interface Option {
</span>
</ng-template>
<ng-template [ngIf]="type === 'text'">
<input class="uk-input input-box uk-text-small"
[attr.uk-tooltip]="formControl.disabled?'title: This field is not editable; pos: bottom-left':''"
[placeholder]="placeholder" [formControl]="formControl"
[class.uk-form-danger]="formControl.invalid && formControl.touched">
<div [ngClass]="inputClass"
[class.uk-form-danger]="formControl.invalid && formControl.touched"
[attr.uk-tooltip]="formControl.disabled?'title: This field is not editable; pos: bottom-left':''">
<input class="uk-input uk-text-small"
[placeholder]="placeholder" [formControl]="formControl">
</div>
</ng-template>
<ng-template [ngIf]="type === 'textarea'">
<textarea class="uk-textarea input-box uk-text-small"
[attr.uk-tooltip]="formControl.disabled?'title: This field is not editable; pos: bottom-left':''"
[rows]="rows" [placeholder]="placeholder"
[formControl]="formControl" [class.uk-form-danger]="formControl.invalid && formControl.touched">
</textarea>
<div [ngClass]="inputClass"
[class.uk-form-danger]="formControl.invalid && formControl.touched"
[attr.uk-tooltip]="formControl.disabled?'title: This field is not editable; pos: bottom-left':''">
<textarea class="uk-textarea uk-text-small"
[rows]="rows" [placeholder]="placeholder"
[formControl]="formControl">
</textarea>
<div class="tools" [class.focused]="focused">
<ng-content select="[options]"></ng-content>
</div>
</div>
</ng-template>
<ng-template [ngIf]="type === 'select'">
<div class="input-box"
<div [ngClass]="inputClass"
[attr.uk-tooltip]="formControl.disabled?'title: This field is not editable; pos: bottom-left':null"
[class.clickable]="formControl.enabled"
[class.uk-form-danger]="formControl.invalid && formControl.touched" (click)="openSelect()">
@ -65,7 +84,7 @@ export interface Option {
</div>
</ng-template>
<ng-template [ngIf]="type === 'chips'">
<div class="input-box"
<div [ngClass]="inputClass"
[attr.uk-tooltip]="formControl.disabled?'title: This field is not editable; pos: bottom-left':null"
[class.clickable]="formControl.enabled"
[class.uk-form-danger]="formControl.invalid && formControl.touched" (click)="openSelect()">
@ -73,11 +92,14 @@ export interface Option {
<mat-chip-list #chipList aria-label="Page selection">
<mat-chip *ngFor="let chip of formAsArray.controls; let i=index"
[removable]="removable">
{{chip.value[chipLabel]}}
<span class="uk-width-expand uk-text-truncate">{{getLabel(chip.value)}}</span>
<icon name="remove_circle" class="mat-chip-remove" (click)="removed(i)"></icon>
</mat-chip>
<div class="uk-width-expand uk-position-relative uk-text-small chip-input">
<input #searchInput class="uk-width-1-1" [formControl]="searchControl" [matAutocomplete]="auto" [matChipInputFor]="chipList">
<input #searchInput class="uk-width-1-1" [formControl]="searchControl" [matAutocomplete]="auto"
[matChipInputFor]="chipList" autofocus
[matChipInputAddOnBlur]="addExtraChips && searchControl.value"
(matChipInputTokenEnd)="add($event)">
<div *ngIf="placeholder && !searchControl.value" class="placeholder uk-width-1-1"
(click)="searchInput.focus()">{{placeholder}}</div>
</div>
@ -101,31 +123,49 @@ export interface Option {
styleUrls: ['input.component.css']
})
export class InputComponent implements OnInit, OnDestroy, OnChanges {
/** Basic information */
@Input('formInput') formControl: AbstractControl;
@Input('type') type: 'text' | 'textarea' | 'select' | 'checkbox' | 'chips' = 'text';
@Input('label') label: string;
@Input('rows') rows: number = 3;
/** Select | chips available options */
@Input('options') options: Option[];
@Input('hint') hint = null;
@Input('placeholder') placeholder = '';
@ViewChild('select') select: MatSelect;
@Input() inputClass: string = 'input-box';
/** Extra element Right or Left of the input */
@Input() extraLeft: boolean = true;
@Input() gridSmall: boolean = false;
@Input() hideControl: boolean = false;
@Input() flex: 'middle' | 'top' | 'bottom' = 'middle';
/** Icon Right or Left on the input */
@Input() icon: string = null;
@Input() iconLeft: boolean = false;
/** Custom messages */
@Input() warning: string = null;
@Input() note: string = null;
/** Chip options */
@Input() removable: boolean = true;
@Input() chipLabel: string = null;
public filteredOptions: Observable<Option[]>;
public searchControl: FormControl;
@Input() addExtraChips: boolean = false;
@Output() focusEmitter: EventEmitter<boolean> = new EventEmitter<boolean>();
/** Internal basic information */
public required: boolean = false;
private initValue: any;
/** Chips */
public filteredOptions: Observable<Option[]>;
public searchControl: FormControl;
private subscriptions: any[] = [];
@ViewChild('searchInput') searchInput;
@ViewChild('select') select: MatSelect;
@ViewChild('searchInput') searchInput: ElementRef;
focused: boolean = false;
constructor() {
constructor(private elementRef: ElementRef) {
}
@HostListener('document:click', ['$event'])
clickOut(event) {
this.focused = !!this.elementRef.nativeElement.contains(event.target);
this.focusEmitter.emit(this.focused);
}
ngOnInit(): void {
@ -133,19 +173,19 @@ export class InputComponent implements OnInit, OnDestroy, OnChanges {
}
ngOnChanges(changes: SimpleChanges) {
if(changes.formControl) {
if (changes.formControl) {
this.reset();
}
}
get formAsArray(): FormArray {
return (<FormArray> this.formControl);
return (<FormArray>this.formControl);
}
reset() {
this.unsubscribe();
this.initValue = HelperFunctions.copy(this.formControl.value);
if(this.options && this.type === 'chips') {
if (this.options && this.type === 'chips') {
this.filteredOptions = of(this.options);
this.searchControl = new FormControl('');
this.filteredOptions = this.searchControl.valueChanges.pipe(startWith(''),
@ -156,8 +196,8 @@ export class InputComponent implements OnInit, OnDestroy, OnChanges {
this.required = (validator && validator.required);
}
this.subscriptions.push(this.formControl.valueChanges.subscribe(value => {
value = (value === '')?null:value;
if(this.initValue === value || (this.initValue === '' && value === null)) {
value = (value === '') ? null : value;
if (this.initValue === value || (this.initValue === '' && value === null)) {
this.formControl.markAsPristine();
}
}));
@ -168,14 +208,14 @@ export class InputComponent implements OnInit, OnDestroy, OnChanges {
unsubscribe() {
this.subscriptions.forEach(subscription => {
if(subscription instanceof Subscription) {
if (subscription instanceof Subscription) {
subscription.unsubscribe();
}
});
}
openSelect() {
if(this.select) {
if (this.select) {
this.select.open();
}
}
@ -191,21 +231,42 @@ export class InputComponent implements OnInit, OnDestroy, OnChanges {
removed(index: number) {
this.formAsArray.removeAt(index);
this.formAsArray.markAsDirty();
this.searchControl.setValue('');
this.searchInput.nativeElement.focus();
this.searchInput.nativeElement.value = '';
this.stopPropagation();
}
selected(event: MatAutocompleteSelectedEvent): void {
this.formAsArray.push(new FormControl(event.option.value));
this.formAsArray.markAsDirty();
this.searchControl.setValue('');
this.searchInput.nativeElement.focus();
this.searchInput.nativeElement.value = '';
this.stopPropagation();
}
private filter(value: string): Option[] {
let options = this.options.filter(option => !this.formAsArray.value.find(value => option.label === value[this.chipLabel]));
let options = this.options.filter(option => !this.formAsArray.value.find(value => option.value === value));
if (!value || value.length == 0) {
return options;
}
const filterValue = value.toString().toLowerCase();
return options.filter(option => option.label.toLowerCase().indexOf(filterValue) != -1);
}
add(event: MatChipInputEvent) {
if (this.addExtraChips && event.value) {
this.stopPropagation();
this.formAsArray.push(new FormControl(event.value));
this.formAsArray.markAsDirty();
this.searchControl.setValue('');
this.searchInput.nativeElement.value = '';
}
}
getLabel(value: any) {
let option = this.options.find(option => option.value === value);
return (option) ? option.label : value;
}
}

View File

@ -227,28 +227,15 @@ export class Composer {
public static composeEmailForMonitorDashboard(name: string, recipient: string, role: "manager" | "member") {
let email: Email = new Email();
email.subject = 'OpenAIRE Monitor Dashboard | ' + name;
email.body = '<p>Dear ((__user__)),</p>' +
'<p>You have been invited to be a ' + role +' of the OpenAIRE Monitor Dashboard for the ' + name + '.</p>' +
'<p>Click <a href="((__link__))">this URL</a> and use the verification code below to accept the invitation.</p>' +
'<p>The verification code is <b>((__code__))</b>.</p>' +
(role === "manager"?
'<p>As a manager of the OpenAIRE Monitor Dashboard, you will have access to the administration part of the dashboard, ' +
'where you will be able to customize and manage the profile of the ' + name + '.</p>':
'<p>As a member of the OpenAIRE Monitor Dashboard, you will have access to the restricted access areas of the profile for the ' + name + '.') +
'<p>Please contact us at <a href="mailto:' + properties.helpdeskEmail+'">' + properties.helpdeskEmail +
'</a> if you have any questions or concerns.</p>' +
'<p>Kind Regards<br>The OpenAIRE Team</p>' +
'<p><a href="' + properties.domain + '">OpenAIRE Monitor</a></p>'
email.body = this.composeMessageForMonitorDashboard(name, role);
email.recipient = recipient;
return email;
}
public static composeEmailForCommunityDashboard(name: string, recipient: string, role: "manager" | "member") {
let email: Email = new Email();
email.subject = 'OpenAIRE Monitor Dashboard | ' + name;
email.body = '<p>Dear ((__user__)),</p>' +
public static composeMessageForMonitorDashboard(name: string, role: "manager" | "member", user: string = null, invitation: any = null) {
let message = '<p>Dear ((__user__)),</p>' +
'<p>You have been invited to be a ' + role +' of the OpenAIRE Monitor Dashboard for the ' + name + '.</p>' +
'<p>Click <a href="((__link__))">this URL</a> and use the verification code below to accept the invitation.</p>' +
'<p>Click <a href="((__link__))" target="_blank">this URL</a> and use the verification code below to accept the invitation.</p>' +
'<p>The verification code is <b>((__code__))</b>.</p>' +
(role === "manager"?
'<p>As a manager of the OpenAIRE Monitor Dashboard, you will have access to the administration part of the dashboard, ' +
@ -257,8 +244,43 @@ export class Composer {
'<p>Please contact us at <a href="mailto:' + properties.helpdeskEmail+'">' + properties.helpdeskEmail +
'</a> if you have any questions or concerns.</p>' +
'<p>Kind Regards<br>The OpenAIRE Team</p>' +
'<p><a href="' + properties.domain + '">OpenAIRE Monitor</a></p>'
'<p><a href="' + properties.domain + '" target="_blank">OpenAIRE Monitor</a></p>';
if(user) {
message = message.replace('((__user__))', user);
}
if(invitation) {
message = message.replace('((__link__))', invitation.link).replace('((__code__))', invitation.code);
}
return message;
}
public static composeEmailForCommunityDashboard(name: string, recipient: string, role: "manager" | "member") {
let email: Email = new Email();
email.subject = 'OpenAIRE Monitor Dashboard | ' + name;
email.body = this.composeMessageForCommunityDashboard(name, role);
email.recipient = recipient;
return email;
}
public static composeMessageForCommunityDashboard(name: string, role: "manager" | "member", user: string = null, invitation: any = null) {
let message = '<p>Dear ((__user__)),</p>' +
'<p>You have been invited to be a ' + role +' of the OpenAIRE Monitor Dashboard for the ' + name + '.</p>' +
'<p>Click <a href="((__link__))" target="_blank">this URL</a> and use the verification code below to accept the invitation.</p>' +
'<p>The verification code is <b>((__code__))</b>.</p>' +
(role === "manager"?
'<p>As a manager of the OpenAIRE Monitor Dashboard, you will have access to the administration part of the dashboard, ' +
'where you will be able to customize and manage the profile of the ' + name + '.</p>':
'<p>As a member of the OpenAIRE Monitor Dashboard, you will have access to the restricted access areas of the profile for the ' + name + '.') +
'<p>Please contact us at <a href="mailto:' + properties.helpdeskEmail+'">' + properties.helpdeskEmail +
'</a> if you have any questions or concerns.</p>' +
'<p>Kind Regards<br>The OpenAIRE Team</p>' +
'<p><a href="' + properties.domain + '" target="_blank">OpenAIRE Monitor</a></p>';
if(user) {
message = message.replace('((__user__))', user);
}
if(invitation) {
message = message.replace('((__link__))', invitation.link).replace('((__code__))', invitation.code);
}
return message;
}
}

View File

@ -116,3 +116,8 @@ export const done = {
name: 'done',
data: '<svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 0 24 24" width="20"><path d="M0 0h24v24H0z" fill="none"/><path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"/></svg>'
}
export const mail = {
name: 'mail',
data: '<svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 0 24 24" width="20"><path d="M0 0h24v24H0z" fill="none"/><path d="M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z"/></svg>'
}

View File

@ -129,4 +129,5 @@ export interface EnvProperties {
b2noteAPIURL?: string;
adminPortalURL?: string;
sushiliteURL?: string;
notificationsAPIURL?: string;
}

View File

@ -5,8 +5,8 @@ export class Dates {
public static yearMin = 1800;
public static yearMax = (new Date().getFullYear()) + 10;
public static currentYear = (new Date().getFullYear());
public static isValidYear(yearString, yearMin=this.yearMin, yearMax=this.yearMax) {
public static isValidYear(yearString, yearMin = this.yearMin, yearMax = this.yearMax) {
// First check for the pattern
if (!/^\d{4}$/.test(yearString))
return false;
@ -99,6 +99,40 @@ export class Dates {
return null;
}
}
public static timeSince(date: Date) {
let seconds = Math.floor((new Date().getTime() - new Date(date).getTime()) / 1000);
let interval = seconds / (365*24*60*60);
if (interval > 1) {
let years = Math.floor(interval);
return (years > 1?(years + ' years ago'):'a year ago');
}
interval = seconds / (7*24*60*60);
if (interval > 1) {
let weeks = Math.floor(interval);
return (weeks > 1?(weeks + ' weeks ago'):'a week ago');
}
interval = seconds / (24*60*60);
if (interval > 1) {
let days = Math.floor(interval);
return (days > 1?(days + ' days ago'):'a day ago');
}
interval = seconds / (60*60);
if (interval > 1) {
let hours = Math.floor(interval);
return (hours > 1?(hours + ' hours ago'):'an hour ago');
}
interval = seconds / 60;
if (interval > 1) {
let minutes = Math.floor(interval);
return (minutes > 1?(minutes + ' minutes ago'):'a minute ago');
}
seconds = Math.floor(interval);
return (seconds > 1?(seconds + ' seconds ago'):' just now');
}
}
export class DOI {
@ -135,14 +169,14 @@ export class Identifier {
for (let id of words) {
if (id.length > 0) {
let identifier: Identifier = this.getIdentifierFromString(id);
if(identifier) {
if (identifier) {
identifiers.push(identifier);
}
}
}
return identifiers;
}
public static getIdentifierFromString(pid: string): Identifier {
if (Identifier.isValidDOI(pid)) {
return {"class": "doi", "id": pid};
@ -238,7 +272,7 @@ export class StringUtils {
public static validateEmails(emails: string): boolean {
return (emails.split(',')
.map(email => Validators.email(<AbstractControl>{ value: email.trim() }))
.map(email => Validators.email(<AbstractControl>{value: email.trim()}))
.find(_ => _ !== null) === undefined);
}
@ -257,7 +291,7 @@ export class StringUtils {
}
public static urlValidator(): ValidatorFn {
return Validators.pattern(this.urlRegex);
return Validators.pattern(this.urlRegex);
}
public static sliceString(mystr, size: number): string {
@ -301,7 +335,7 @@ export class StringUtils {
/**
* Checks if a text contains a word
*/
public static containsWord(text: string, word: string):boolean {
public static containsWord(text: string, word: string): boolean {
return (text && text.toLowerCase().includes(word));
}
@ -321,10 +355,11 @@ export class StringUtils {
];
return (country && countries.indexOf(country) != -1);
}
public static isOpenAIREID(id:string){
if(id && id.length == 46){
public static isOpenAIREID(id: string) {
if (id && id.length == 46) {
let exp1 = /^.{12}::([0-9a-z]{32})$/g;
return (id.match(exp1)!=null);
return (id.match(exp1) != null);
}
return false;
}