diff --git a/client/src/app/admin/admin.component.html b/client/src/app/admin/admin.component.html index 5c884c3b52f4a5225b2b6596965fb0ac8845b72c..d89e40d276b3a1a09da13739e70ccd4b9fdcacab 100644 --- a/client/src/app/admin/admin.component.html +++ b/client/src/app/admin/admin.component.html @@ -1,13 +1,16 @@ -<header> - <app-admin-navbar - [isAuthenticated]="isAuthenticated | async" - [userProfile]="userProfile | async" - [authenticationEnabled]="getAuthenticationEnabled()" - (login)="login()" - (logout)="logout()" - (openEditProfile)="openEditProfile()"> - </app-admin-navbar> -</header> -<main role="main" class="container-fluid pb-4"> - <router-outlet></router-outlet> -</main> +<div class="d-flex flex-column h-100"> + <header> + <app-admin-navbar + [isAuthenticated]="isAuthenticated | async" + [userProfile]="userProfile | async" + [authenticationEnabled]="getAuthenticationEnabled()" + (login)="login()" + (logout)="logout()" + (openEditProfile)="openEditProfile()"> + </app-admin-navbar> + </header> + <main role="main" class="container-fluid pb-4"> + <router-outlet></router-outlet> + </main> + <app-footer class="footer mt-auto"></app-footer> +</div> diff --git a/client/src/app/admin/instance/components/footer-logo-form.component.html b/client/src/app/admin/instance/components/footer-logo-form.component.html new file mode 100644 index 0000000000000000000000000000000000000000..bdf494d65f0cbf9e0f80d9e7a440d98978c6f807 --- /dev/null +++ b/client/src/app/admin/instance/components/footer-logo-form.component.html @@ -0,0 +1,9 @@ +<form [formGroup]="form" novalidate> + <div class="values"> + <input type="text" class="form-control" name="href" placeholder="Href" formControlName="href"> + <input type="text" class="form-control" name="title" placeholder="title" formControlName="title"> + <input type="text" class="form-control" name="file" placeholder="File" formControlName="file"> + <input type="number" class="form-control" name="display" placeholder="Display" formControlName="display"> + <ng-content></ng-content> + </div> +</form> diff --git a/client/src/app/admin/instance/components/footer-logo-form.component.scss b/client/src/app/admin/instance/components/footer-logo-form.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..3cb1593cb2a5dcc37d49dce2930ae49158e09b2f --- /dev/null +++ b/client/src/app/admin/instance/components/footer-logo-form.component.scss @@ -0,0 +1,4 @@ +.values input { + display: inline-block; + width: 22%; +} diff --git a/client/src/app/admin/instance/components/footer-logo-form.component.ts b/client/src/app/admin/instance/components/footer-logo-form.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..13b8730e77121ebf9732570b6241f8a5d5f3d9b7 --- /dev/null +++ b/client/src/app/admin/instance/components/footer-logo-form.component.ts @@ -0,0 +1,13 @@ +import { Component, ChangeDetectionStrategy, Input } from '@angular/core'; + +import { UntypedFormGroup } from '@angular/forms'; + +@Component({ + selector: 'app-footer-logo-form', + templateUrl: 'footer-logo-form.component.html', + styleUrls: [ 'footer-logo-form.component.scss' ], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class FooterLogoFormComponent { + @Input() form: UntypedFormGroup; +} diff --git a/client/src/app/admin/instance/components/footer-logos-list.component.html b/client/src/app/admin/instance/components/footer-logos-list.component.html new file mode 100644 index 0000000000000000000000000000000000000000..672f6aa31e62e82573fb8cdaf93905354e57a23a --- /dev/null +++ b/client/src/app/admin/instance/components/footer-logos-list.component.html @@ -0,0 +1,11 @@ +<app-footer-logo-form *ngFor="let logo of form.controls; index as i" [form]="getLogoFormGroup(logo)"> + <button (click)="removeLogo(i)" class="btn btn-default"> + <span class="fas fa-trash-alt"></span> + </button> +</app-footer-logo-form> + +<app-footer-logo-form [form]="newLogoFormGroup" #f> + <button (click)="addLogo()" [disabled]="!f.form.valid || f.form.pristine" class="btn btn-default"> + <span class="fas fa-plus"></span> + </button> +</app-footer-logo-form> diff --git a/client/src/app/admin/instance/components/footer-logos-list.component.ts b/client/src/app/admin/instance/components/footer-logos-list.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..aa048c866de2c9cc72265d866ba66efd126e9adc --- /dev/null +++ b/client/src/app/admin/instance/components/footer-logos-list.component.ts @@ -0,0 +1,51 @@ +import { Component, ChangeDetectionStrategy, OnInit, Input } from '@angular/core'; +import { UntypedFormArray, UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms'; + +import { Logo } from 'src/app/metamodel/models'; + +@Component({ + selector: 'app-footer-logos-list', + templateUrl: 'footer-logos-list.component.html', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class FooterLogosListComponent implements OnInit { + @Input() form: UntypedFormArray; + @Input() logoList: Logo[]; + + newLogoFormGroup: UntypedFormGroup; + + ngOnInit() { + if (this.form.controls.length < 1 && this.logoList && this.logoList.length > 0) { + for (const logo of this.logoList) { + const logoForm = this.buildFormGroup(); + logoForm.patchValue(logo); + this.form.push(logoForm); + } + } + this.newLogoFormGroup = this.buildFormGroup(); + } + + buildFormGroup() { + return new UntypedFormGroup({ + href: new UntypedFormControl('', [Validators.required]), + title: new UntypedFormControl('', [Validators.required]), + file: new UntypedFormControl('', [Validators.required]), + display: new UntypedFormControl('', [Validators.required]) + }); + } + + getLogoFormGroup(logo) { + return logo as UntypedFormGroup; + } + + addLogo() { + this.form.push(this.newLogoFormGroup); + this.newLogoFormGroup = this.buildFormGroup(); + this.form.markAsDirty(); + } + + removeLogo(index: number) { + this.form.removeAt(index); + this.form.markAsDirty(); + } +} diff --git a/client/src/app/admin/instance/components/index.ts b/client/src/app/admin/instance/components/index.ts index 1927331f8ed08d985f937a214193e830e0ac2221..d2ae1f97a4b0ae2f1bd028c198d58357680cf8f7 100644 --- a/client/src/app/admin/instance/components/index.ts +++ b/client/src/app/admin/instance/components/index.ts @@ -9,12 +9,16 @@ import { InstanceCardComponent } from './instance-card.component'; import { InstanceFormComponent } from './instance-form.component'; +import { FooterLogoFormComponent } from './footer-logo-form.component'; +import { FooterLogosListComponent } from './footer-logos-list.component'; import { InstanceGroupTableComponent } from './instance-group-table.component'; import { InstanceGroupFormComponent } from './instance-group-form.component'; export const dummiesComponents = [ InstanceCardComponent, InstanceFormComponent, + FooterLogoFormComponent, + FooterLogosListComponent, InstanceGroupTableComponent, InstanceGroupFormComponent ]; diff --git a/client/src/app/admin/instance/components/instance-form.component.html b/client/src/app/admin/instance/components/instance-form.component.html index d40292375abe4b193da3383898381d76e2c1cfe6..ce57ac4658f0ff48a275322fd2394a27a7081318 100644 --- a/client/src/app/admin/instance/components/instance-form.component.html +++ b/client/src/app/admin/instance/components/instance-form.component.html @@ -209,6 +209,13 @@ <input type="text" class="form-control" id="footer_text_color_input" [value]="form.value.footer_text_color" formControlName="footer_text_color"> </div> </div> + <div class="form-group"> + <label for="footer_logos">Footer logos</label> + <app-footer-logos-list + [form]="footerLogosFormArray" + [logoList]="getFooterLogoListByDisplay()"> + </app-footer-logos-list> + </div> </accordion-group> <accordion-group heading="Design family" [isOpen]="false"> <div class="form-row"> diff --git a/client/src/app/admin/instance/components/instance-form.component.ts b/client/src/app/admin/instance/components/instance-form.component.ts index 1c4f5fd54ee0cfce41984211666b1fbe08ca5c6b..c7811f1f886c3360ecb4f072f2956a7160238d4e 100644 --- a/client/src/app/admin/instance/components/instance-form.component.ts +++ b/client/src/app/admin/instance/components/instance-form.component.ts @@ -8,7 +8,7 @@ */ import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core'; -import { UntypedFormGroup, UntypedFormControl, Validators } from '@angular/forms'; +import { UntypedFormGroup, UntypedFormArray, UntypedFormControl, Validators } from '@angular/forms'; import { Instance } from 'src/app/metamodel/models'; import { FileInfo } from 'src/app/admin/store/models'; @@ -25,6 +25,8 @@ export class InstanceFormComponent implements OnInit { @Output() loadRootDirectory: EventEmitter<string> = new EventEmitter(); @Output() onSubmit: EventEmitter<Instance> = new EventEmitter(); + footerLogosFormArray = new UntypedFormArray([]); + public form = new UntypedFormGroup({ name: new UntypedFormControl('', [Validators.required]), label: new UntypedFormControl('', [Validators.required]), @@ -50,6 +52,7 @@ export class InstanceFormComponent implements OnInit { footer_background_color: new UntypedFormControl('#F8F9FA'), footer_border_top_color: new UntypedFormControl('#DEE2E6'), footer_text_color: new UntypedFormControl('#000000'), + footer_logos: this.footerLogosFormArray, family_border_color: new UntypedFormControl('#DFDFDF'), family_header_background_color: new UntypedFormControl('#F7F7F7'), family_title_color: new UntypedFormControl('#007BFF'), @@ -158,6 +161,10 @@ export class InstanceFormComponent implements OnInit { } } + getFooterLogoListByDisplay() { + return [...this.instance.footer_logos].sort((a, b) => a.display - b.display); + } + submit() { if (this.instance) { this.onSubmit.emit({ diff --git a/client/src/app/admin/instance/dataset/components/attribute/criteria/option-form.component.ts b/client/src/app/admin/instance/dataset/components/attribute/criteria/option-form.component.ts index f35a1803d6e48ba813ea83a30aae77b259e6de4b..ad45bb972b25cebe532052fdc934a2a5699d0610 100644 --- a/client/src/app/admin/instance/dataset/components/attribute/criteria/option-form.component.ts +++ b/client/src/app/admin/instance/dataset/components/attribute/criteria/option-form.component.ts @@ -8,6 +8,7 @@ */ import { Component, Input, ChangeDetectionStrategy } from '@angular/core'; + import { UntypedFormGroup } from '@angular/forms'; @Component({ diff --git a/client/src/app/admin/instance/dataset/components/attribute/criteria/tr-criteria.component.ts b/client/src/app/admin/instance/dataset/components/attribute/criteria/tr-criteria.component.ts index 3127807b6497a225d93d91f73190505088a5c3c2..6af3661858c28e1a772ceddf45d373e475cb85c1 100644 --- a/client/src/app/admin/instance/dataset/components/attribute/criteria/tr-criteria.component.ts +++ b/client/src/app/admin/instance/dataset/components/attribute/criteria/tr-criteria.component.ts @@ -30,9 +30,8 @@ export class TrCriteriaComponent implements OnInit { @Output() loadAttributeDistinctList: EventEmitter<{}> = new EventEmitter(); searchTypeList = searchTypeList; - - optionsFormArray = new UntypedFormArray([]); operatorList = searchTypeOperators; + optionsFormArray = new UntypedFormArray([]); public form = new UntypedFormGroup({ name: new UntypedFormControl({ value: '', disabled: true }), diff --git a/client/src/app/core/containers/app.component.html b/client/src/app/core/containers/app.component.html index 3cd4c5e07197c521203f6f6075784371d82cf6b3..20c3962160f9c12aa7d642ea6c742471b87b8660 100644 --- a/client/src/app/core/containers/app.component.html +++ b/client/src/app/core/containers/app.component.html @@ -3,43 +3,3 @@ <span class="sr-only">Loading...</span> </div> <router-outlet *ngIf="(instanceListIsLoaded | async) && (instanceGroupListIsLoaded | async)"></router-outlet> -<footer class="footer mt-auto"> - <div class="container my-3"> - <div class="row justify-content-center font-weight-bold mb-1"> - © ANIS 2014 - {{ year }} - </div> - <div class="row justify-content-center font-weight-bold mb-4"> - Currently based on ANIS v{{ anisClientVersion }}. - </div> - <div class="row justify-content-center font-weight-bold mb-4"> - Code licensed CeCILL. - </div> - <div class="row justify-content-around"> - <div class="col mb-3 text-center"> - <a href="http://cesam.lam.fr" title="Centre de données Astrophysique de Marseille"> - <img class="img-fluid" src="assets/logo_cesam_s.png" alt="CeSAM" /> - </a> - </div> - <div class="col text-center"> - <a href="http://lam.fr" title="Laboratoire d'Astrophysique de Marseille"> - <img class="img-fluid" src="assets/logo_lam_s.png" alt="LAM" /> - </a> - </div> - <div class="col text-center"> - <a href="http://www.univ-amu.fr" title="Aix*Marseille Université"> - <img class="img-fluid" src="assets/logo_amu_s.png" alt="AMU" /> - </a> - </div> - <div class="col text-center"> - <a href="http://www.insu.cnrs.fr" title="Institut National des Sciences de l'Univers"> - <img class="img-fluid" src="assets/logo_insu_s.png" alt="INSU" /> - </a> - </div> - <div class="col text-center"> - <a href="http://anis.lam.fr" title="AstroNomical Information System"> - <img class="img-fluid" src="assets/cesam_anis40.png" alt="ANIS" /> - </a> - </div> - </div> - </div> -</footer> diff --git a/client/src/app/core/containers/app.component.ts b/client/src/app/core/containers/app.component.ts index bdeae085da022d8650343978b8832335358c2aa9..d7eb360fecb7c501b0f51195033987dd92f11057 100644 --- a/client/src/app/core/containers/app.component.ts +++ b/client/src/app/core/containers/app.component.ts @@ -28,8 +28,6 @@ import { AppConfigService } from 'src/app/app-config.service'; templateUrl: './app.component.html' }) export class AppComponent implements OnInit { - public anisClientVersion: string = '3.7.0'; - public year: number = (new Date()).getFullYear(); public instanceListIsLoading: Observable<boolean>; public instanceListIsLoaded: Observable<boolean>; public instanceGroupListIsLoading: Observable<boolean>; diff --git a/client/src/app/instance/components/index.ts b/client/src/app/instance/components/index.ts index 74d75b58fafa3e513a25c4bc99b1a732210378b8..838f0f1507340ef8ffd1ec1c9245edddcdc02b85 100644 --- a/client/src/app/instance/components/index.ts +++ b/client/src/app/instance/components/index.ts @@ -8,11 +8,13 @@ */ import { InstanceNavbarComponent } from './instance-navbar.component'; +import { InstanceFooterComponent } from './instance-footer.component'; import { WebpageFamilyNavComponent } from './webpage-family-nav.component'; import { WebpageFamilyNavMobileComponent } from './webpage-family-nav-mobile.component'; export const dummiesComponents = [ InstanceNavbarComponent, + InstanceFooterComponent, WebpageFamilyNavComponent, WebpageFamilyNavMobileComponent ]; diff --git a/client/src/app/instance/components/instance-footer.component.html b/client/src/app/instance/components/instance-footer.component.html new file mode 100644 index 0000000000000000000000000000000000000000..4bf4243154b32f2153f93201f5f6fb145335a386 --- /dev/null +++ b/client/src/app/instance/components/instance-footer.component.html @@ -0,0 +1,21 @@ +<footer> + <div class="container my-3"> + <div class="row justify-content-center font-weight-bold mb-1"> + © ANIS 2014 - {{ year }} + </div> + <div class="row justify-content-center font-weight-bold mb-4"> + Currently based on ANIS v{{ anisClientVersion }}. + </div> + <div class="row justify-content-center font-weight-bold mb-4"> + Code licensed CeCILL. + </div> + <div class="row justify-content-around"> + <div *ngFor="let logo of instance.footer_logos" class="col mb-3 text-center"> + <a [href]="logo.href" [title]="logo.title"> + <img *ngIf="instance.public" class="img-fluid" [src]="getLogoHref(logo)" alt="Not found" /> + <img *ngIf="!instance.public" class="img-fluid" [src]="getLogoHref(logo) | authImage | async" alt="Not found" /> + </a> + </div> + </div> + </div> +</footer> \ No newline at end of file diff --git a/client/src/app/instance/components/instance-footer.component.ts b/client/src/app/instance/components/instance-footer.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..5d6b156ef8ecb43567563ab4d877973597a43573 --- /dev/null +++ b/client/src/app/instance/components/instance-footer.component.ts @@ -0,0 +1,25 @@ +import { Component, ChangeDetectionStrategy, Input } from '@angular/core'; + +import { Instance, Logo } from 'src/app/metamodel/models'; + +@Component({ + selector: 'app-instance-footer', + templateUrl: 'instance-footer.component.html', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class InstanceFooterComponent { + @Input() instance: Instance; + @Input() apiUrl: string; + + public anisClientVersion: string = '3.7.0'; + public year: number = (new Date()).getFullYear(); + + /** + * Returns logo href. + * + * @return string + */ + getLogoHref(logo: Logo): string { + return `${this.apiUrl}/instance/${this.instance.name}/file-explorer${logo.file}`; + } +} diff --git a/client/src/app/instance/instance.component.html b/client/src/app/instance/instance.component.html index 200ce1c7dd6dc5df008c3ec8bc9481e4d48b289c..9d69f3aa1f46db930204e3224dbcec6e648055c0 100644 --- a/client/src/app/instance/instance.component.html +++ b/client/src/app/instance/instance.component.html @@ -1,4 +1,4 @@ -<ng-container> +<div class="d-flex flex-column h-100"> <header> <app-instance-navbar [isAuthenticated]="isAuthenticated | async" @@ -19,4 +19,8 @@ <main role="main" class="container-fluid pb-4"> <router-outlet></router-outlet> </main> -</ng-container> + <app-instance-footer class="footer mt-auto" + [instance]="instance | async" + [apiUrl]="getApiUrl()"> + </app-instance-footer> +</div> diff --git a/client/src/app/instance/instance.component.spec.ts b/client/src/app/instance/instance.component.spec.ts index a5c6cb643a59d7fd6e817f8f60d5f1ebba47e631..d8a865e9d2ab1470378518f47d8a33a4d555a9f5 100644 --- a/client/src/app/instance/instance.component.spec.ts +++ b/client/src/app/instance/instance.component.spec.ts @@ -99,6 +99,7 @@ describe('[Instance] InstanceComponent', () => { footer_background_color: '#F8F9FA', footer_border_top_color: '#DEE2E6', footer_text_color: '#000000', + footer_logos: null, family_border_color: '#DFDFDF', family_header_background_color: '#F7F7F7', family_title_color: '#007BFF', diff --git a/client/src/app/instance/search/components/progress-bar.component.spec.ts b/client/src/app/instance/search/components/progress-bar.component.spec.ts index f50d456f5a24792f791372c711975494036c408c..730864615b03d5dbd6e8f87570abfc5edb05c748 100644 --- a/client/src/app/instance/search/components/progress-bar.component.spec.ts +++ b/client/src/app/instance/search/components/progress-bar.component.spec.ts @@ -72,6 +72,7 @@ describe('[Instance][Search][Component] ProgressBarComponent', () => { footer_background_color: '#F8F9FA', footer_border_top_color: '#DEE2E6', footer_text_color: '#000000', + footer_logos: null, family_border_color: '#DFDFDF', family_header_background_color: '#F7F7F7', family_title_color: '#007BFF', @@ -148,6 +149,7 @@ describe('[Instance][Search][Component] ProgressBarComponent', () => { footer_background_color: '#F8F9FA', footer_border_top_color: '#DEE2E6', footer_text_color: '#000000', + footer_logos: null, family_border_color: '#DFDFDF', family_header_background_color: '#F7F7F7', family_title_color: '#007BFF', diff --git a/client/src/app/instance/store/effects/search-multiple.effects.spec.ts b/client/src/app/instance/store/effects/search-multiple.effects.spec.ts index 8b91fb5a8d14359944c555d8eb6ffdb157558936..f7b60cec59b2748ee032ba7cb1127eb095f21ceb 100644 --- a/client/src/app/instance/store/effects/search-multiple.effects.spec.ts +++ b/client/src/app/instance/store/effects/search-multiple.effects.spec.ts @@ -160,6 +160,7 @@ describe('[Instance][Store] SearchMultipleEffects', () => { family_title_bold: false, family_background_color: '#FFFFFF', family_color: '#212529', + footer_logos: null, progress_bar_title: 'Dataset search', progress_bar_title_color: '#000000', progress_bar_subtitle: 'Select a dataset, add criteria, select output columns and display the result.', @@ -273,6 +274,7 @@ describe('[Instance][Store] SearchMultipleEffects', () => { footer_background_color: '#F8F9FA', footer_border_top_color: '#DEE2E6', footer_text_color: '#000000', + footer_logos: null, family_border_color: '#DFDFDF', family_header_background_color: '#F7F7F7', family_title_color: '#007BFF', diff --git a/client/src/app/metamodel/models/index.ts b/client/src/app/metamodel/models/index.ts index 12e9e400a19a77764cc1c28aad386786e3b0fec9..6fba669fd1b90350ed484c3975ff7195c70f82d3 100644 --- a/client/src/app/metamodel/models/index.ts +++ b/client/src/app/metamodel/models/index.ts @@ -25,3 +25,4 @@ export * from './detail-config.model'; export * from './file.model'; export * from './webpage.model'; export * from './webpage-family'; +export * from './logo.model'; diff --git a/client/src/app/metamodel/models/instance.model.ts b/client/src/app/metamodel/models/instance.model.ts index 7ed9788727d2dc22d5072927881603505cc86617..3cefffed59694e9e93b1f495dc1cf10e10896326 100644 --- a/client/src/app/metamodel/models/instance.model.ts +++ b/client/src/app/metamodel/models/instance.model.ts @@ -7,6 +7,8 @@ * file that was distributed with this source code. */ +import { Logo } from './logo.model'; + /** * Interface for instance. * @@ -37,6 +39,7 @@ export interface Instance { footer_background_color: string; footer_border_top_color: string; footer_text_color: string; + footer_logos: Logo[]; family_border_color: string; family_header_background_color: string; family_title_color: string; diff --git a/client/src/app/metamodel/models/logo.model.ts b/client/src/app/metamodel/models/logo.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..9da48b8a3f5e5a432b1871b5bf382c803aa92f18 --- /dev/null +++ b/client/src/app/metamodel/models/logo.model.ts @@ -0,0 +1,20 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Interface for logo. + * + * @interface Logo + */ +export interface Logo { + href: string; + title: string; + file: string; + display: number; +} diff --git a/client/src/app/portal/containers/portal-home.component.html b/client/src/app/portal/containers/portal-home.component.html index ba726a547cb4689f290c2be833c770c860bb8301..343f6bd4dadc0d6b0842d9e244aba03f9838e8fa 100644 --- a/client/src/app/portal/containers/portal-home.component.html +++ b/client/src/app/portal/containers/portal-home.component.html @@ -1,38 +1,41 @@ -<header> - <app-portal-navbar - [isAuthenticated]="isAuthenticated | async" - [userProfile]="userProfile | async" - [userRoles]="userRoles | async" - [authenticationEnabled]="getAuthenticationEnabled()" - [adminRoles]="getAdminRoles()" - (login)="login()" - (logout)="logout()" - (openEditProfile)="openEditProfile()"> - </app-portal-navbar> -</header> -<main role="main" class="container-fluid pb-4"> - <div class="container"> - <ng-container *ngIf="(instanceList | async).length === 0"> - <div class="col-12 lead text-center font-weight-bold"> - Oops! No instances available... - <span *ngIf="!(isAuthenticated | async)"> - Try to sign in to access to protected instances. - </span> - </div> - </ng-container> - - <div class="row justify-content-center"> - <div class="col-auto mb-3" *ngFor="let instance of (instanceList | async)"> - <app-instance-card - [instance]="instance" - [authenticationEnabled]="getAuthenticationEnabled()" - [isAuthenticated]="isAuthenticated | async" - [userRoles]="userRoles | async" - [adminRoles]="getAdminRoles()" - [instanceGroupList]="instanceGroupList | async" - [apiUrl]="getApiUrl()"> - </app-instance-card> +<div class="d-flex flex-column h-100"> + <header> + <app-portal-navbar + [isAuthenticated]="isAuthenticated | async" + [userProfile]="userProfile | async" + [userRoles]="userRoles | async" + [authenticationEnabled]="getAuthenticationEnabled()" + [adminRoles]="getAdminRoles()" + (login)="login()" + (logout)="logout()" + (openEditProfile)="openEditProfile()"> + </app-portal-navbar> + </header> + <main role="main" class="container-fluid pb-4"> + <div class="container"> + <ng-container *ngIf="(instanceList | async).length === 0"> + <div class="col-12 lead text-center font-weight-bold"> + Oops! No instances available... + <span *ngIf="!(isAuthenticated | async)"> + Try to sign in to access to protected instances. + </span> + </div> + </ng-container> + + <div class="row justify-content-center"> + <div class="col-auto mb-3" *ngFor="let instance of (instanceList | async)"> + <app-instance-card + [instance]="instance" + [authenticationEnabled]="getAuthenticationEnabled()" + [isAuthenticated]="isAuthenticated | async" + [userRoles]="userRoles | async" + [adminRoles]="getAdminRoles()" + [instanceGroupList]="instanceGroupList | async" + [apiUrl]="getApiUrl()"> + </app-instance-card> + </div> </div> </div> - </div> -</main> + </main> + <app-footer class="footer mt-auto"></app-footer> +</div> diff --git a/client/src/app/shared/components/footer.component.html b/client/src/app/shared/components/footer.component.html new file mode 100644 index 0000000000000000000000000000000000000000..4492f7132d15fb6f53e368c003e95e5bffc0ed76 --- /dev/null +++ b/client/src/app/shared/components/footer.component.html @@ -0,0 +1,40 @@ +<footer> + <div class="container my-3"> + <div class="row justify-content-center font-weight-bold mb-1"> + © ANIS 2014 - {{ year }} + </div> + <div class="row justify-content-center font-weight-bold mb-4"> + Currently based on ANIS v{{ anisClientVersion }}. + </div> + <div class="row justify-content-center font-weight-bold mb-4"> + Code licensed CeCILL. + </div> + <div class="row justify-content-around"> + <div class="col mb-3 text-center"> + <a href="http://cesam.lam.fr" title="Centre de données Astrophysique de Marseille"> + <img class="img-fluid" src="assets/logo_cesam_s.png" alt="CeSAM" /> + </a> + </div> + <div class="col text-center"> + <a href="http://lam.fr" title="Laboratoire d'Astrophysique de Marseille"> + <img class="img-fluid" src="assets/logo_lam_s.png" alt="LAM" /> + </a> + </div> + <div class="col text-center"> + <a href="http://www.univ-amu.fr" title="Aix*Marseille Université"> + <img class="img-fluid" src="assets/logo_amu_s.png" alt="AMU" /> + </a> + </div> + <div class="col text-center"> + <a href="http://www.insu.cnrs.fr" title="Institut National des Sciences de l'Univers"> + <img class="img-fluid" src="assets/logo_insu_s.png" alt="INSU" /> + </a> + </div> + <div class="col text-center"> + <a href="http://anis.lam.fr" title="AstroNomical Information System"> + <img class="img-fluid" src="assets/cesam_anis40.png" alt="ANIS" /> + </a> + </div> + </div> + </div> +</footer> \ No newline at end of file diff --git a/client/src/app/shared/components/footer.component.ts b/client/src/app/shared/components/footer.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..2f5697fa31a82a38f802ac58c41dfd8e5884381a --- /dev/null +++ b/client/src/app/shared/components/footer.component.ts @@ -0,0 +1,11 @@ +import { Component, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + selector: 'app-footer', + templateUrl: 'footer.component.html', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class FooterComponent { + public anisClientVersion: string = '3.7.0'; + public year: number = (new Date()).getFullYear(); +} diff --git a/client/src/app/shared/components/index.ts b/client/src/app/shared/components/index.ts index 18a3063846b295a531f6311a1637ce0c83265947..e332eb5a04713460840acd1e9d40d995f39b0fd1 100644 --- a/client/src/app/shared/components/index.ts +++ b/client/src/app/shared/components/index.ts @@ -7,8 +7,10 @@ * file that was distributed with this source code. */ -import { SpinnerComponent } from "./spinner.component"; +import { SpinnerComponent } from './spinner.component'; +import { FooterComponent } from './footer.component'; export const sharedComponents = [ - SpinnerComponent + SpinnerComponent, + FooterComponent ]; diff --git a/client/src/index.html b/client/src/index.html index 38450df159b25845e1b994fd60679c2732d8e63c..6d603b0b5ccc230cdbfd54dbee60c26d129e7b73 100644 --- a/client/src/index.html +++ b/client/src/index.html @@ -8,6 +8,6 @@ <link rel="icon" id="favicon" type="image/x-icon" href="favicon.ico"> </head> <body class="h-100"> - <app-root class="d-flex flex-column h-100"></app-root> + <app-root></app-root> </body> </html> diff --git a/client/src/test-data.ts b/client/src/test-data.ts index 85092fa1dba246bb3267fff0c3523ef60dab531f..dc09c8afe37d94dd5600d03b95e7df6c913cdacd 100644 --- a/client/src/test-data.ts +++ b/client/src/test-data.ts @@ -69,6 +69,7 @@ export const INSTANCE_LIST: Instance[] = [ footer_background_color: '#F8F9FA', footer_border_top_color: '#DEE2E6', footer_text_color: '#000000', + footer_logos: null, family_border_color: '#DFDFDF', family_header_background_color: '#F7F7F7', family_title_color: '#007BFF', @@ -139,6 +140,7 @@ export const INSTANCE_LIST: Instance[] = [ footer_background_color: '#F8F9FA', footer_border_top_color: '#DEE2E6', footer_text_color: '#000000', + footer_logos: null, family_border_color: '#DFDFDF', family_header_background_color: '#F7F7F7', family_title_color: '#007BFF', @@ -211,6 +213,7 @@ export const INSTANCE: Instance = { footer_background_color: '#F8F9FA', footer_border_top_color: '#DEE2E6', footer_text_color: '#000000', + footer_logos: null, family_border_color: '#DFDFDF', family_header_background_color: '#F7F7F7', family_title_color: '#007BFF', diff --git a/conf-dev/create-db.sh b/conf-dev/create-db.sh index a3cbfadb8ac959c617126e3f5cb1520210098ca0..81147de34e5466b53fa5374ffece91972cd56784 100644 --- a/conf-dev/create-db.sh +++ b/conf-dev/create-db.sh @@ -8,7 +8,7 @@ set -e curl -d '{"label":"Test","dbname":"anis_test","dbtype":"pdo_pgsql","dbhost":"db","dbport":5432,"dblogin":"anis","dbpassword":"anis"}' --header 'Content-Type: application/json' -X POST http://localhost/database # Add default instance -curl -d '{"name":"default","label":"Default instance","description":"Instance for the test","scientific_manager":"M. Durand","instrument":"Multiple","wavelength_domain":"Visible imaging / Spectroscopy","display":10,"data_path":"\/DEFAULT","files_path":"\/INSTANCE_FILES","public":true,"portal_logo":"","design_color":"#7AC29A","design_background_color":"#ffffff","design_logo":"/logo.png","design_favicon":"/favicon.ico","navbar_background_color":"#F8F9FA","navbar_border_bottom_color":"#DEE2E6","navbar_color_href":"#000000","navbar_font_family":"Roboto, sans-serif","navbar_sign_in_btn_color":"#28A745","navbar_user_btn_color":"#7AC29A","footer_background_color":"#F8F9FA","footer_border_top_color":"#DEE2E6","footer_text_color":"#000000","family_border_color":"#DFDFDF","family_header_background_color":"#F7F7F7","family_title_color":"#007BFF","family_title_bold":false,"family_background_color":"#FFFFFF","family_color":"#212529","progress_bar_title":"Dataset search","progress_bar_title_color":"#000000","progress_bar_subtitle":"Select a dataset, add criteria, select output columns and display the result.","progress_bar_subtitle_color":"#6C757D","progress_bar_color":"#E9ECEF","progress_bar_active_color":"#7AC29A","progress_bar_circle_color":"#FFFFFF","progress_bar_circle_icon_color":"#CCCCCC","progress_bar_circle_icon_active_color":"#FFFFFF","progress_bar_text_color":"#91B2BF","result_header_background_color":"#E9ECEF","result_header_text_color":"#000000","result_header_btn_color":"#007BFF","result_header_btn_hover_color":"#0069D9","result_header_btn_text_color":"#FFFFFF","result_datatable_bordered":true,"result_datatable_border_color":"#DEE2E6","result_datatable_header_background_color":"#FFFFFF","result_datatable_header_text_color":"#000000","result_datatable_rows_background_color":"#FFFFFF","result_datatable_rows_text_color":"#000000","result_datatable_sorted_color":"#C5C5C5","result_datatable_sorted_active_color":"#000000","result_datatable_link_color":"#007BFF","result_datatable_link_hover_color":"#0056B3","result_datatable_rows_selected_color":"#7AC29A","samp_enabled":true,"back_to_portal":true,"user_menu_enabled":true,"search_by_criteria_allowed":true,"search_by_criteria_label":"Search","search_multiple_allowed":false,"search_multiple_label":"Search multiple","search_multiple_all_datasets_selected":false,"documentation_allowed":false,"documentation_label":"Documentation"}' --header 'Content-Type: application/json' -X POST http://localhost/instance +curl -d '{"name":"default","label":"Default instance","description":"Instance for the test","scientific_manager":"M. Durand","instrument":"Multiple","wavelength_domain":"Visible imaging / Spectroscopy","display":10,"data_path":"\/DEFAULT","files_path":"\/INSTANCE_FILES","public":true,"portal_logo":"","design_color":"#7AC29A","design_background_color":"#ffffff","design_logo":"/logo.png","design_favicon":"/favicon.ico","navbar_background_color":"#F8F9FA","navbar_border_bottom_color":"#DEE2E6","navbar_color_href":"#000000","navbar_font_family":"Roboto, sans-serif","navbar_sign_in_btn_color":"#28A745","navbar_user_btn_color":"#7AC29A","footer_background_color":"#F8F9FA","footer_border_top_color":"#DEE2E6","footer_text_color":"#000000","footer_logos":[{"href":"http:\/\/lam.fr","title":"Laboratoire d'\''Astrophysique de Marseille","file":"\/logo_lam_s.png","display":20},{"href":"http:\/\/www.univ-amu.fr","title":"Aix*Marseille Universit\u00e9","file":"\/logo_amu_s.png","display":30},{"href":"http:\/\/anis.lam.fr","title":"AstroNomical Information System","file":"\/cesam_anis40.png","display":50},{"href":"http:\/\/cesam.lam.fr","title":"Centre de donn\u00e9es Astrophysique de Marseille","file":"\/logo_cesam_s.png","display":10},{"href":"http:\/\/www.insu.cnrs.fr","title":"Institut National des Sciences de l'\''Univers","file":"\/logo_insu_s.png","display":40}],"family_border_color":"#DFDFDF","family_header_background_color":"#F7F7F7","family_title_color":"#007BFF","family_title_bold":false,"family_background_color":"#FFFFFF","family_color":"#212529","progress_bar_title":"Dataset search","progress_bar_title_color":"#000000","progress_bar_subtitle":"Select a dataset, add criteria, select output columns and display the result.","progress_bar_subtitle_color":"#6C757D","progress_bar_color":"#E9ECEF","progress_bar_active_color":"#7AC29A","progress_bar_circle_color":"#FFFFFF","progress_bar_circle_icon_color":"#CCCCCC","progress_bar_circle_icon_active_color":"#FFFFFF","progress_bar_text_color":"#91B2BF","result_header_background_color":"#E9ECEF","result_header_text_color":"#000000","result_header_btn_color":"#007BFF","result_header_btn_hover_color":"#0069D9","result_header_btn_text_color":"#FFFFFF","result_datatable_bordered":true,"result_datatable_border_color":"#DEE2E6","result_datatable_header_background_color":"#FFFFFF","result_datatable_header_text_color":"#000000","result_datatable_rows_background_color":"#FFFFFF","result_datatable_rows_text_color":"#000000","result_datatable_sorted_color":"#C5C5C5","result_datatable_sorted_active_color":"#000000","result_datatable_link_color":"#007BFF","result_datatable_link_hover_color":"#0056B3","result_datatable_rows_selected_color":"#7AC29A","samp_enabled":true,"back_to_portal":true,"user_menu_enabled":true,"search_by_criteria_allowed":true,"search_by_criteria_label":"Search","search_multiple_allowed":false,"search_multiple_label":"Search multiple","search_multiple_all_datasets_selected":false,"documentation_allowed":false,"documentation_label":"Documentation"}' --header 'Content-Type: application/json' -X POST http://localhost/instance # Add dataset families curl -d '{"label":"Default dataset family","display":10,"opened":true}' --header 'Content-Type: application/json' -X POST http://localhost/instance/default/dataset-family @@ -80,7 +80,7 @@ curl -d '{"id":9,"name":"fits_png","label":"fits_png","form_label":"fits_png","d curl -d '{"id":10,"name":"id_obspack","label":"id_obspack","form_label":"id_obspack","description":null,"primary_key":false,"type":"integer","search_type":null,"operator":null,"dynamic_operator":null,"min":null,"max":null,"options":null,"placeholder_min":null,"placeholder_max":null,"criteria_display":100,"output_display":100,"detail_renderer":null,"detail_renderer_config":null,"selected":true,"renderer":"link","renderer_config":{"href":"/instance/default/search/result/iris_obspack?s=100&a=1;2;3&c=1::eq::$value","display":"text","text":"$value","icon":"fas fa-link","blank":false},"order_by":true,"archive":false,"detail_display":100,"vo_utype":null,"vo_ucd":null,"vo_unit":null,"vo_description":null,"vo_datatype":null,"vo_size":null,"id_criteria_family":null,"id_output_category":4,"id_detail_output_category":null}' --header 'Content-Type: application/json' -X POST http://localhost/dataset/observations/attribute # Add observations detail config -curl -d '{"content":"<h1 class=\"text-center\">\n <app-display-value-by-attribute\n [object]=\"context.object\"\n [attributeList]=\"context.attributeList\"\n [attributeId]=\"7\">\n </app-display-value-by-attribute>\n</h1>\n<div class=\"row\">\n <div class=\"col-md-8 col-sm-12 col-xs-12\">\n <app-display-image\n [object]=\"context.object\"\n [attributeList]=\"context.attributeList\"\n [dataset]=\"context.dataset\"\n [attributeImageId]=\"9\"\n [type]=\"'''fits'''\">\n </app-display-image>\n </div>\n <div class=\"col-md-4 col-sm-12 col-xs-12\">\n <app-display-ra-dec\n [object]=\"context.object\"\n [attributeList]=\"context.attributeList\"\n [attributeRaId]=\"2\"\n [attributeDecId]=\"3\">\n </app-display-ra-dec>\n <app-display-object-by-output-category\n [object]=\"context.object\"\n [dataset]=\"context.dataset\"\n [instance]=\"context.instance\"\n [attributeList]=\"context.attributeList\"\n [outputCategoryList]=\"context.outputCategoryList\"\n [queryParams]=\"context.queryParams\"\n [outputCategoryId]=\"4\">\n </app-display-object-by-output-category>\n </div>\n</div>\n","style_sheet":"h1 {\n color: red;\n}\n\n.panel-heading.card-header {\n background-color: #dee2ff;\n}\n\n.panel .btn.btn-link .text-primary {\n color: red !important;\n}\n"}' --header 'Content-Type: application/json' -X POST http://localhost/dataset/observations/detail-config +curl -d '{"content":"<h1 class=\"text-center\">\n <app-display-value-by-attribute\n [object]=\"context.object\"\n [attributeList]=\"context.attributeList\"\n [attributeId]=\"7\">\n </app-display-value-by-attribute>\n</h1>\n<div class=\"row\">\n <div class=\"col-md-8 col-sm-12 col-xs-12\">\n <app-display-image\n [object]=\"context.object\"\n [attributeList]=\"context.attributeList\"\n [dataset]=\"context.dataset\"\n [attributeImageId]=\"9\"\n [type]=\"'\''fits'\''\">\n </app-display-image>\n </div>\n <div class=\"col-md-4 col-sm-12 col-xs-12\">\n <app-display-ra-dec\n [object]=\"context.object\"\n [attributeList]=\"context.attributeList\"\n [attributeRaId]=\"2\"\n [attributeDecId]=\"3\">\n </app-display-ra-dec>\n <app-display-object-by-output-category\n [object]=\"context.object\"\n [dataset]=\"context.dataset\"\n [instance]=\"context.instance\"\n [attributeList]=\"context.attributeList\"\n [outputCategoryList]=\"context.outputCategoryList\"\n [queryParams]=\"context.queryParams\"\n [outputCategoryId]=\"4\">\n </app-display-object-by-output-category>\n </div>\n</div>\n","style_sheet":"h1 {\n color: red;\n}\n\n.panel-heading.card-header {\n background-color: #dee2ff;\n}\n\n.panel .btn.btn-link .text-primary {\n color: red !important;\n}\n"}' --header 'Content-Type: application/json' -X POST http://localhost/dataset/observations/detail-config # Add vvds_f02_udeep attributes curl -d '{"label":"Default","display":10,"opened":true}' --header 'Content-Type: application/json' -X POST http://localhost/dataset/vvds_f02_udeep/criteria-family diff --git a/server/n b/server/n deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/server/src/Action/InstanceAction.php b/server/src/Action/InstanceAction.php index 263d64d2d3bb36c146687514ea9e44e4bbca33a5..161d571cb87f869d6cbde10cd1b1e05b37da3064 100644 --- a/server/src/Action/InstanceAction.php +++ b/server/src/Action/InstanceAction.php @@ -87,6 +87,7 @@ final class InstanceAction extends AbstractAction 'footer_background_color', 'footer_border_top_color', 'footer_text_color', + 'footer_logos', 'family_border_color', 'family_header_background_color', 'family_title_color', @@ -187,6 +188,7 @@ final class InstanceAction extends AbstractAction $instance->setFooterBackgroundColor($parsedBody['footer_background_color']); $instance->setFooterBorderTopColor($parsedBody['footer_border_top_color']); $instance->setFooterTextColor($parsedBody['footer_text_color']); + $instance->setFooterLogos($parsedBody['footer_logos']); $instance->setFamilyBorderColor($parsedBody['family_border_color']); $instance->setFamilyHeaderBackgroundColor($parsedBody['family_header_background_color']); $instance->setFamilyTitleColor($parsedBody['family_title_color']); diff --git a/server/src/Action/InstanceListAction.php b/server/src/Action/InstanceListAction.php index 85e675ff9f324cd1d4492ec3c6abaaae7645f607..634e5ad8b55a3c55cacbf72dad30b2636299f469 100644 --- a/server/src/Action/InstanceListAction.php +++ b/server/src/Action/InstanceListAction.php @@ -87,6 +87,7 @@ final class InstanceListAction extends AbstractAction 'footer_background_color', 'footer_border_top_color', 'footer_text_color', + 'footer_logos', 'family_border_color', 'family_header_background_color', 'family_title_color', @@ -181,6 +182,7 @@ final class InstanceListAction extends AbstractAction $instance->setFooterBackgroundColor($parsedBody['footer_background_color']); $instance->setFooterBorderTopColor($parsedBody['footer_border_top_color']); $instance->setFooterTextColor($parsedBody['footer_text_color']); + $instance->setFooterLogos($parsedBody['footer_logos']); $instance->setFamilyBorderColor($parsedBody['family_border_color']); $instance->setFamilyHeaderBackgroundColor($parsedBody['family_header_background_color']); $instance->setFamilyTitleColor($parsedBody['family_title_color']); diff --git a/server/src/Entity/Instance.php b/server/src/Entity/Instance.php index 75b0c8c07277cb7e59be4d840bd3b0fa670a35cd..b896534c9da1e6ea962bf6be685fc7288561a5ef 100644 --- a/server/src/Entity/Instance.php +++ b/server/src/Entity/Instance.php @@ -192,6 +192,13 @@ class Instance implements \JsonSerializable */ protected $footerTextColor; + /** + * @var string + * + * @Column(type="json", name="footer_logos", nullable=true) + */ + protected $footerLogos; + /** * @var string * @@ -753,6 +760,16 @@ class Instance implements \JsonSerializable $this->footerTextColor = $footerTextColor; } + public function getFooterLogos() + { + return $this->footerLogos; + } + + public function setFooterLogos($footerLogos) + { + $this->footerLogos = $footerLogos; + } + public function getFamilyBorderColor() { return $this->familyBorderColor; @@ -1214,6 +1231,7 @@ class Instance implements \JsonSerializable 'footer_background_color' => $this->getFooterBackgroundColor(), 'footer_border_top_color' => $this->getFooterBorderTopColor(), 'footer_text_color' => $this->getFooterTextColor(), + 'footer_logos' => $this->getFooterLogos(), 'family_border_color' => $this->getFamilyBorderColor(), 'family_header_background_color' => $this->getFamilyHeaderBackgroundColor(), 'family_title_color' => $this->getFamilyTitleColor(), diff --git a/server/tests/Action/InstanceActionTest.php b/server/tests/Action/InstanceActionTest.php index ce1a65bfc0b6a519b742fd2e9f9e5c7eea77fbad..42e08350e93a33b9197daf6521133bab8b9504b5 100644 --- a/server/tests/Action/InstanceActionTest.php +++ b/server/tests/Action/InstanceActionTest.php @@ -101,6 +101,7 @@ final class InstanceActionTest extends TestCase 'footer_background_color' => '#F8F9FA', 'footer_border_top_color' => '#DEE2E6', 'footer_text_color' => '#000000', + 'footer_logos' => null, 'family_border_color' => '#DFDFDF', 'family_header_background_color' => '#F7F7F7', 'family_title_color' => '#007BFF', diff --git a/server/tests/Action/InstanceListActionTest.php b/server/tests/Action/InstanceListActionTest.php index 7f924946100e9f26b0ca20b83c19b987570f96fb..f9379469b2a89baf7f97cf3ca5e9b35562fa70d9 100644 --- a/server/tests/Action/InstanceListActionTest.php +++ b/server/tests/Action/InstanceListActionTest.php @@ -85,6 +85,7 @@ final class InstanceListActionTest extends TestCase 'footer_background_color' => '#F8F9FA', 'footer_border_top_color' => '#DEE2E6', 'footer_text_color' => '#000000', + 'footer_logos' => null, 'family_border_color' => '#DFDFDF', 'family_header_background_color' => '#F7F7F7', 'family_title_color' => '#007BFF',